: 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 } bool Simulator::ICacheMatch(void* one, void* two) { DCHECK_EQ(reinterpret_cast(one) & CachePage::kPageMask, 0); DCHECK_EQ(reinterpret_cast(two) & CachePage::kPageMask, 0); return one == two; } static uint32_t ICacheHash(void* key) { return static_cast(reinterpret_cast(key)) >> 2; } static bool AllOnOnePage(uintptr_t start, int size) { intptr_t start_page = (start & ~CachePage::kPageMask); intptr_t end_page = ((start + size) & ~CachePage::kPageMask); return start_page == end_page; } void Simulator::set_last_debugger_input(char* input) { DeleteArray(last_debugger_input_); last_debugger_input_ = input; } void Simulator::SetRedirectInstruction(Instruction* instruction) { instruction->SetInstructionBits(rtCallRedirInstr); } void Simulator::FlushICache(base::CustomMatcherHashMap* i_cache, void* start_addr, size_t size) { intptr_t start = reinterpret_cast(start_addr); int intra_line = (start & CachePage::kLineMask); start -= intra_line; size += intra_line; size = ((size - 1) | CachePage::kLineMask) + 1; int offset = (start & CachePage::kPageMask); while (!AllOnOnePage(start, size - 1)) { int bytes_to_flush = CachePage::kPageSize - offset; FlushOnePage(i_cache, start, bytes_to_flush); start += bytes_to_flush; size -= bytes_to_flush; DCHECK_EQ(0, start & CachePage::kPageMask); offset = 0; } if (size != 0) { FlushOnePage(i_cache, start, size); } } CachePage* Simulator::GetCachePage(base::CustomMatcherHashMap* i_cache, void* page) { base::CustomMatcherHashMap::Entry* entry = i_cache->LookupOrInsert(page, ICacheHash(page)); if (entry->value == nullptr) { CachePage* new_page = new CachePage(); entry->value = new_page; } return reinterpret_cast(entry->value); } // Flush from start up to and not including start + size. void Simulator::FlushOnePage(base::CustomMatcherHashMap* i_cache, intptr_t start, int size) { DCHECK_LE(size, CachePage::kPageSize); DCHECK(AllOnOnePage(start, size - 1)); DCHECK_EQ(start & CachePage::kLineMask, 0); DCHECK_EQ(size & CachePage::kLineMask, 0); void* page = reinterpret_cast(start & (~CachePage::kPageMask)); int offset = (start & CachePage::kPageMask); CachePage* cache_page = GetCachePage(i_cache, page); char* valid_bytemap = cache_page->ValidityByte(offset); memset(valid_bytemap, CachePage::LINE_INVALID, size >> CachePage::kLineShift); } void Simulator::CheckICache(base::CustomMatcherHashMap* i_cache, Instruction* instr) { intptr_t address = reinterpret_cast(instr); void* page = reinterpret_cast(address & (~CachePage::kPageMask)); void* line = reinterpret_cast(address & (~CachePage::kLineMask)); int offset = (address & CachePage::kPageMask); CachePage* cache_page = GetCachePage(i_cache, page); char* cache_valid_byte = cache_page->ValidityByte(offset); bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID); char* cached_line = cache_page->CachedData(offset & ~CachePage::kLineMask); if (cache_hit) { // Check that the data in memory matches the contents of the I-cache. CHECK_EQ(0, memcmp(reinterpret_cast(instr), cache_page->CachedData(offset), kInstrSize)); } else { // Cache miss. Load memory into the cache. memcpy(cached_line, line, CachePage::kLineLength); *cache_valid_byte = CachePage::LINE_VALID; } } Simulator::Simulator(Isolate* isolate) : isolate_(isolate) { // Set up simulator support first. Some of this information is needed to // setup the architecture state. stack_ = reinterpret_cast(malloc(stack_size_)); pc_modified_ = false; icount_ = 0; break_count_ = 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 < kNumSimuRegisters; i++) { registers_[i] = 0; } for (int i = 0; i < kNumFPURegisters; i++) { FPUregisters_[2 * i] = 0; FPUregisters_[2 * i + 1] = 0; // upper part for MSA ASE } if (IsMipsArchVariant(kMips32r6)) { FCSR_ = kFCSRNaN2008FlagMask; MSACSR_ = 0; } else { DCHECK(IsMipsArchVariant(kMips32r1) || IsMipsArchVariant(kMips32r2)); FCSR_ = 0; } // The sp is initialized to point to the bottom (high address) of the // allocated stack area. To be safe in potential stack underflows we leave // some buffer below. registers_[sp] = reinterpret_cast(stack_) + stack_size_ - 64; // The ra and pc are initialized to a known bad value that will cause an // access violation if the simulator ever tries to execute it. registers_[pc] = bad_ra; registers_[ra] = bad_ra; last_debugger_input_ = nullptr; } Simulator::~Simulator() { free(stack_); } // Get the active Simulator for the current thread. Simulator* Simulator::current(Isolate* isolate) { v8::internal::Isolate::PerIsolateThreadData* isolate_data = isolate->FindOrAllocatePerThreadDataForThisThread(); DCHECK_NOT_NULL(isolate_data); Simulator* sim = isolate_data->simulator(); if (sim == nullptr) { // TODO(146): delete the simulator object when a thread/isolate goes away. sim = new Simulator(isolate); isolate_data->set_simulator(sim); } return sim; } // Sets the register in the architecture state. It will also deal with updating // Simulator internal state for special registers such as PC. void Simulator::set_register(int reg, int32_t value) { DCHECK((reg >= 0) && (reg < kNumSimuRegisters)); if (reg == pc) { pc_modified_ = true; } // Zero register always holds 0. registers_[reg] = (reg == 0) ? 0 : value; } void Simulator::set_dw_register(int reg, const int* dbl) { DCHECK((reg >= 0) && (reg < kNumSimuRegisters)); registers_[reg] = dbl[0]; registers_[reg + 1] = dbl[1]; } void Simulator::set_fpu_register(int fpureg, int64_t value) { DCHECK(IsFp64Mode()); DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); FPUregisters_[fpureg * 2] = value; } void Simulator::set_fpu_register_word(int fpureg, int32_t value) { // Set ONLY lower 32-bits, leaving upper bits untouched. // TODO(plind): big endian issue. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* pword = reinterpret_cast(&FPUregisters_[fpureg * 2]); *pword = value; } void Simulator::set_fpu_register_hi_word(int fpureg, int32_t value) { // Set ONLY upper 32-bits, leaving lower bits untouched. // TODO(plind): big endian issue. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* phiword = (reinterpret_cast(&FPUregisters_[fpureg * 2])) + 1; *phiword = value; } void Simulator::set_fpu_register_float(int fpureg, float value) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg * 2]) = value; } void Simulator::set_fpu_register_double(int fpureg, double value) { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg * 2]) = value; } else { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); int64_t i64 = bit_cast(value); set_fpu_register_word(fpureg, i64 & 0xFFFFFFFF); set_fpu_register_word(fpureg + 1, i64 >> 32); } } // Get the register from the architecture state. This function does handle // the special case of accessing the PC register. int32_t Simulator::get_register(int reg) const { DCHECK((reg >= 0) && (reg < kNumSimuRegisters)); if (reg == 0) return 0; else return registers_[reg] + ((reg == pc) ? Instruction::kPCReadOffset : 0); } double Simulator::get_double_from_register_pair(int reg) { // TODO(plind): bad ABI stuff, refactor or remove. DCHECK((reg >= 0) && (reg < 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. char buffer[2 * sizeof(registers_[0])]; memcpy(buffer, ®isters_[reg], 2 * sizeof(registers_[0])); memcpy(&dm_val, buffer, 2 * sizeof(registers_[0])); return(dm_val); } int64_t Simulator::get_fpu_register(int fpureg) const { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return FPUregisters_[fpureg * 2]; } else { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); uint64_t i64; i64 = static_cast(get_fpu_register_word(fpureg)); i64 |= static_cast(get_fpu_register_word(fpureg + 1)) << 32; return static_cast(i64); } } int32_t Simulator::get_fpu_register_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg * 2] & 0xFFFFFFFF); } int32_t Simulator::get_fpu_register_signed_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg * 2] & 0xFFFFFFFF); } int32_t Simulator::get_fpu_register_hi_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast((FPUregisters_[fpureg * 2] >> 32) & 0xFFFFFFFF); } float Simulator::get_fpu_register_float(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(const_cast(&FPUregisters_[fpureg * 2])); } double Simulator::get_fpu_register_double(int fpureg) const { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(&FPUregisters_[fpureg * 2]); } else { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); int64_t i64; i64 = static_cast(get_fpu_register_word(fpureg)); i64 |= static_cast(get_fpu_register_word(fpureg + 1)) << 32; return bit_cast(i64); } } template void Simulator::get_msa_register(int wreg, T* value) { DCHECK((wreg >= 0) && (wreg < kNumMSARegisters)); memcpy(value, FPUregisters_ + wreg * 2, kSimd128Size); } template void Simulator::set_msa_register(int wreg, const T* value) { DCHECK((wreg >= 0) && (wreg < kNumMSARegisters)); memcpy(FPUregisters_ + wreg * 2, value, kSimd128Size); } // 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) { if (!IsMipsSoftFloatABI) { *x = get_fpu_register_double(12); *y = get_fpu_register_double(14); *z = get_register(a2); } else { // TODO(plind): bad ABI stuff, refactor or remove. // We use a char buffer to get around the strict-aliasing rules which // otherwise allow the compiler to optimize away the copy. char buffer[sizeof(*x)]; int32_t* reg_buffer = reinterpret_cast(buffer); // Registers a0 and a1 -> x. reg_buffer[0] = get_register(a0); reg_buffer[1] = get_register(a1); memcpy(x, buffer, sizeof(buffer)); // Registers a2 and a3 -> y. reg_buffer[0] = get_register(a2); reg_buffer[1] = get_register(a3); memcpy(y, buffer, sizeof(buffer)); // Register 2 -> z. reg_buffer[0] = get_register(a2); memcpy(z, buffer, sizeof(*z)); } } // The return value is either in v0/v1 or f0. void Simulator::SetFpResult(const double& result) { if (!IsMipsSoftFloatABI) { set_fpu_register_double(0, result); } else { char buffer[2 * sizeof(registers_[0])]; int32_t* reg_buffer = reinterpret_cast(buffer); memcpy(buffer, &result, sizeof(buffer)); // Copy result to v0 and v1. set_register(v0, reg_buffer[0]); set_register(v1, reg_buffer[1]); } } // Helper functions for setting and testing the FCSR register's bits. void Simulator::set_fcsr_bit(uint32_t cc, bool value) { if (value) { FCSR_ |= (1 << cc); } else { FCSR_ &= ~(1 << cc); } } bool Simulator::test_fcsr_bit(uint32_t cc) { return FCSR_ & (1 << cc); } void Simulator::set_fcsr_rounding_mode(FPURoundingMode mode) { FCSR_ |= mode & kFPURoundingModeMask; } void Simulator::set_msacsr_rounding_mode(FPURoundingMode mode) { MSACSR_ |= mode & kFPURoundingModeMask; } unsigned int Simulator::get_fcsr_rounding_mode() { return FCSR_ & kFPURoundingModeMask; } unsigned int Simulator::get_msacsr_rounding_mode() { return MSACSR_ & kFPURoundingModeMask; } void Simulator::set_fpu_register_word_invalid_result(float original, float rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register_word(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result(float original, float rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result64(float original, float rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded >= max_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResult); } else if (rounded < min_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPU64InvalidResult); } } void Simulator::set_fpu_register_word_invalid_result(double original, double rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register_word(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result(double original, double rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result64(double original, double rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded >= max_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResult); } else if (rounded < min_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPU64InvalidResult); } } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round_error(double original, double rounded) { bool ret = false; double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded > max_int32 || rounded < min_int32) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round64_error(double original, double rounded) { bool ret = false; // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded >= max_int64 || rounded < min_int64) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round_error(float original, float rounded) { bool ret = false; double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < FLT_MIN && rounded > -FLT_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded > max_int32 || rounded < min_int32) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round64_error(float original, float rounded) { bool ret = false; // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < FLT_MIN && rounded > -FLT_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded >= max_int64 || rounded < min_int64) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } void Simulator::round_according_to_fcsr(double toRound, double& rounded, int32_t& rounded_int, double fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RD (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (get_fcsr_rounding_mode()) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } void Simulator::round_according_to_fcsr(float toRound, float& rounded, int32_t& rounded_int, float fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RD (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (get_fcsr_rounding_mode()) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.f; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } template void Simulator::round_according_to_msacsr(T_fp toRound, T_fp& rounded, T_int& rounded_int) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RD (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (get_msacsr_rounding_mode()) { case kRoundToNearest: rounded = std::floor(toRound + 0.5); rounded_int = static_cast(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--; rounded -= 1; } break; case kRoundToZero: rounded = trunc(toRound); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(toRound); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(toRound); rounded_int = static_cast(rounded); break; } } void Simulator::round64_according_to_fcsr(double toRound, double& rounded, int64_t& rounded_int, double fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or. // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward +infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RN (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (FCSR_ & 3) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } void Simulator::round64_according_to_fcsr(float toRound, float& rounded, int64_t& rounded_int, float fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or. // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward +infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RN (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (FCSR_ & 3) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.f; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } // 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. void Simulator::TraceRegWr(int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int32_t fmt_int32; float fmt_float; } v; v.fmt_int32 = value; switch (t) { case WORD: SNPrintF(trace_buf_, "%08" PRIx32 " (%" PRIu64 ") int32:%" PRId32 " uint32:%" PRIu32, value, icount_, value, value); break; case FLOAT: SNPrintF(trace_buf_, "%08" PRIx32 " (%" PRIu64 ") flt:%e", v.fmt_int32, icount_, v.fmt_float); break; default: UNREACHABLE(); } } } void Simulator::TraceRegWr(int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int64_t fmt_int64; double fmt_double; } v; v.fmt_int64 = value; switch (t) { case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " (%" PRIu64 ") int64:%" PRId64 " uint64:%" PRIu64, value, icount_, value, value); break; case DOUBLE: SNPrintF(trace_buf_, "%016" PRIx64 " (%" PRIu64 ") dbl:%e", v.fmt_int64, icount_, v.fmt_double); break; default: UNREACHABLE(); } } } template void Simulator::TraceMSARegWr(T* value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { uint8_t b[16]; uint16_t h[8]; uint32_t w[4]; uint64_t d[2]; float f[4]; double df[2]; } v; memcpy(v.b, value, kSimd128Size); switch (t) { case BYTE: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); break; case HALF: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); break; case WORD: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") int32[0..3]:%" PRId32 " %" PRId32 " %" PRId32 " %" PRId32, v.d[0], v.d[1], icount_, v.w[0], v.w[1], v.w[2], v.w[3]); break; case DWORD: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); break; case FLOAT: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") flt[0..3]:%e %e %e %e", v.d[0], v.d[1], icount_, v.f[0], v.f[1], v.f[2], v.f[3]); break; case DOUBLE: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") dbl[0..1]:%e %e", v.d[0], v.d[1], icount_, v.df[0], v.df[1]); break; default: UNREACHABLE(); } } } template void Simulator::TraceMSARegWr(T* value) { if (::v8::internal::FLAG_trace_sim) { union { uint8_t b[kMSALanesByte]; uint16_t h[kMSALanesHalf]; uint32_t w[kMSALanesWord]; uint64_t d[kMSALanesDword]; float f[kMSALanesWord]; double df[kMSALanesDword]; } v; memcpy(v.b, value, kMSALanesByte); if (std::is_same::value) { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") int32[0..3]:%" PRId32 " %" PRId32 " %" PRId32 " %" PRId32, v.d[0], v.d[1], icount_, v.w[0], v.w[1], v.w[2], v.w[3]); } else if (std::is_same::value) { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") flt[0..3]:%e %e %e %e", v.d[0], v.d[1], icount_, v.f[0], v.f[1], v.f[2], v.f[3]); } else if (std::is_same::value) { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") dbl[0..1]:%e %e", v.d[0], v.d[1], icount_, v.df[0], v.df[1]); } else { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); } } } // TODO(plind): consider making icount_ printing a flag option. void Simulator::TraceMemRd(int32_t addr, int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int32_t fmt_int32; float fmt_float; } v; v.fmt_int32 = value; switch (t) { case WORD: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") int32:%" PRId32 " uint32:%" PRIu32, value, addr, icount_, value, value); break; case FLOAT: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") flt:%e", v.fmt_int32, addr, icount_, v.fmt_float); break; default: UNREACHABLE(); } } } void Simulator::TraceMemWr(int32_t addr, int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case BYTE: SNPrintF(trace_buf_, " %02" PRIx8 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case HALF: SNPrintF(trace_buf_, " %04" PRIx16 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case WORD: SNPrintF(trace_buf_, "%08" PRIx32 " --> [%08" PRIx32 "] (%" PRIu64 ")", value, addr, icount_); break; default: UNREACHABLE(); } } } template void Simulator::TraceMemRd(int32_t addr, T value) { if (::v8::internal::FLAG_trace_sim) { switch (sizeof(T)) { case 1: SNPrintF(trace_buf_, "%08" PRIx8 " <-- [%08" PRIx32 "] (%" PRIu64 ") int8:%" PRId8 " uint8:%" PRIu8, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; case 2: SNPrintF(trace_buf_, "%08" PRIx16 " <-- [%08" PRIx32 "] (%" PRIu64 ") int16:%" PRId16 " uint16:%" PRIu16, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; case 4: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") int32:%" PRId32 " uint32:%" PRIu32, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; case 8: SNPrintF(trace_buf_, "%08" PRIx64 " <-- [%08" PRIx32 "] (%" PRIu64 ") int64:%" PRId64 " uint64:%" PRIu64, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; default: UNREACHABLE(); } } } template void Simulator::TraceMemWr(int32_t addr, T value) { if (::v8::internal::FLAG_trace_sim) { switch (sizeof(T)) { case 1: SNPrintF(trace_buf_, " %02" PRIx8 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case 2: SNPrintF(trace_buf_, " %04" PRIx16 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case 4: SNPrintF(trace_buf_, "%08" PRIx32 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case 8: SNPrintF(trace_buf_, "%16" PRIx64 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; default: UNREACHABLE(); } } } void Simulator::TraceMemRd(int32_t addr, int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int64_t fmt_int64; int32_t fmt_int32[2]; float fmt_float[2]; double fmt_double; } v; v.fmt_int64 = value; switch (t) { case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " <-- [%08" PRIx32 "] (%" PRIu64 ") int64:%" PRId64 " uint64:%" PRIu64, v.fmt_int64, addr, icount_, v.fmt_int64, v.fmt_int64); break; case DOUBLE: SNPrintF(trace_buf_, "%016" PRIx64 " <-- [%08" PRIx32 "] (%" PRIu64 ") dbl:%e", v.fmt_int64, addr, icount_, v.fmt_double); break; case FLOAT_DOUBLE: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") flt:%e dbl:%e", v.fmt_int32[1], addr, icount_, v.fmt_float[1], v.fmt_double); break; default: UNREACHABLE(); } } } void Simulator::TraceMemWr(int32_t addr, int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " --> [%08" PRIx32 "] (%" PRIu64 ")", value, addr, icount_); break; default: UNREACHABLE(); } } } int Simulator::ReadW(int32_t addr, Instruction* instr, TraceType t) { if (addr >=0 && addr < 0x400) { // This has to be a nullptr-dereference, drop into debugger. PrintF("Memory read from bad address: 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { intptr_t* ptr = reinterpret_cast(addr); switch (t) { case WORD: TraceMemRd(addr, static_cast(*ptr), t); break; case FLOAT: // This TraceType is allowed but tracing for this value will be omitted. break; default: UNREACHABLE(); } return *ptr; } PrintF("Unaligned read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); return 0; } void Simulator::WriteW(int32_t addr, int value, Instruction* instr) { if (addr >= 0 && addr < 0x400) { // This has to be a nullptr-dereference, drop into debugger. PrintF("Memory write to bad address: 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { intptr_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, WORD); *ptr = value; return; } PrintF("Unaligned write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } double Simulator::ReadD(int32_t addr, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { double* ptr = reinterpret_cast(addr); return *ptr; } PrintF("Unaligned (double) read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } void Simulator::WriteD(int32_t addr, double value, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { double* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned (double) write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); } uint16_t Simulator::ReadHU(int32_t addr, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { uint16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned unsigned halfword read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } int16_t Simulator::ReadH(int32_t addr, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { int16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned signed halfword read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } void Simulator::WriteH(int32_t addr, uint16_t value, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { uint16_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, HALF); *ptr = value; return; } PrintF("Unaligned unsigned halfword write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); } void Simulator::WriteH(int32_t addr, int16_t value, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { int16_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, HALF); *ptr = value; return; } PrintF("Unaligned halfword write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); } uint32_t Simulator::ReadBU(int32_t addr) { uint8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr & 0xFF; } int32_t Simulator::ReadB(int32_t addr) { int8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } void Simulator::WriteB(int32_t addr, uint8_t value) { uint8_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, BYTE); *ptr = value; } void Simulator::WriteB(int32_t addr, int8_t value) { int8_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, BYTE); *ptr = value; } template T Simulator::ReadMem(int32_t addr, Instruction* instr) { int alignment_mask = (1 << sizeof(T)) - 1; if ((addr & alignment_mask) == 0 || IsMipsArchVariant(kMips32r6)) { T* ptr = reinterpret_cast(addr); TraceMemRd(addr, *ptr); return *ptr; } PrintF("Unaligned read of type sizeof(%d) at 0x%08x, pc=0x%08" V8PRIxPTR "\n", sizeof(T), addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } template void Simulator::WriteMem(int32_t addr, T value, Instruction* instr) { int alignment_mask = (1 << sizeof(T)) - 1; if ((addr & alignment_mask) == 0 || IsMipsArchVariant(kMips32r6)) { T* ptr = reinterpret_cast(addr); *ptr = value; TraceMemWr(addr, value); return; } PrintF("Unaligned write of type sizeof(%d) at 0x%08x, pc=0x%08" V8PRIxPTR "\n", sizeof(T), addr, reinterpret_cast(instr)); base::OS::Abort(); } // Returns the limit of the stack area to enable checking for stack overflows. uintptr_t Simulator::StackLimit(uintptr_t c_limit) const { // The simulator uses a separate JS stack. If we have exhausted the C stack, // we also drop down the JS limit to reflect the exhaustion on the JS stack. if (GetCurrentStackPosition() < c_limit) { return reinterpret_cast(get_sp()); } // Otherwise the limit is the JS stack. Leave a safety margin of 1024 bytes // to prevent overrunning the stack when pushing values. return reinterpret_cast(stack_) + 1024; } // Unsupported instructions use Format to print an error and stop execution. void Simulator::Format(Instruction* instr, const char* format) { PrintF("Simulator found unsupported instruction:\n 0x%08" PRIxPTR ": %s\n", reinterpret_cast(instr), format); UNIMPLEMENTED_MIPS(); } // Calls into the V8 runtime are based on this very simple interface. // Note: To be able to return two values from some calls the code in runtime.cc // uses the ObjectPair which is essentially two 32-bit values stuffed into a // 64-bit value. With the code below we assume that all runtime calls return // 64 bits of result. If they don't, the v1 result register contains a bogus // value, which is fine because it is caller-saved. typedef int64_t (*SimulatorRuntimeCall)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6, int32_t arg7, int32_t arg8); // These prototypes handle the four types of FP calls. typedef int64_t (*SimulatorRuntimeCompareCall)(double darg0, double darg1); typedef double (*SimulatorRuntimeFPFPCall)(double darg0, double darg1); typedef double (*SimulatorRuntimeFPCall)(double darg0); typedef double (*SimulatorRuntimeFPIntCall)(double darg0, int32_t arg0); // This signature supports direct call in to API function native callback // (refer to InvocationCallback in v8.h). typedef void (*SimulatorRuntimeDirectApiCall)(int32_t arg0); typedef void (*SimulatorRuntimeProfilingApiCall)(int32_t arg0, void* arg1); // This signature supports direct call to accessor getter callback. typedef void (*SimulatorRuntimeDirectGetterCall)(int32_t arg0, int32_t arg1); typedef void (*SimulatorRuntimeProfilingGetterCall)( int32_t arg0, int32_t arg1, void* arg2); // Software interrupt instructions are used by the simulator to call into the // C-based V8 runtime. They are also used for debugging with simulator. void Simulator::SoftwareInterrupt() { // There are several instructions that could get us here, // the break_ instruction, or several variants of traps. All // Are "SPECIAL" class opcode, and are distinuished by function. int32_t func = instr_.FunctionFieldRaw(); uint32_t code = (func == BREAK) ? instr_.Bits(25, 6) : -1; // We first check if we met a call_rt_redirected. if (instr_.InstructionBits() == rtCallRedirInstr) { Redirection* redirection = Redirection::FromInstruction(instr_.instr()); int32_t arg0 = get_register(a0); int32_t arg1 = get_register(a1); int32_t arg2 = get_register(a2); int32_t arg3 = get_register(a3); int32_t* stack_pointer = reinterpret_cast(get_register(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]; int32_t arg6 = stack_pointer[6]; int32_t arg7 = stack_pointer[7]; int32_t arg8 = stack_pointer[8]; STATIC_ASSERT(kMaxCParameters == 9); bool fp_call = (redirection->type() == ExternalReference::BUILTIN_FP_FP_CALL) || (redirection->type() == ExternalReference::BUILTIN_COMPARE_CALL) || (redirection->type() == ExternalReference::BUILTIN_FP_CALL) || (redirection->type() == ExternalReference::BUILTIN_FP_INT_CALL); if (!IsMipsSoftFloatABI) { // With the hard floating point calling convention, double // arguments are passed in FPU registers. Fetch the arguments // from there and call the builtin using soft floating point // convention. switch (redirection->type()) { case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_COMPARE_CALL: if (IsFp64Mode()) { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_hi_word(f12); arg2 = get_fpu_register_word(f14); arg3 = get_fpu_register_hi_word(f14); } else { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_word(f13); arg2 = get_fpu_register_word(f14); arg3 = get_fpu_register_word(f15); } break; case ExternalReference::BUILTIN_FP_CALL: if (IsFp64Mode()) { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_hi_word(f12); } else { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_word(f13); } break; case ExternalReference::BUILTIN_FP_INT_CALL: if (IsFp64Mode()) { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_hi_word(f12); } else { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_word(f13); } arg2 = get_register(a2); break; default: break; } } // This is dodgy but it works because the C entry stubs are never moved. // See comment in codegen-arm.cc and bug 1242173. int32_t saved_ra = get_register(ra); intptr_t external = reinterpret_cast(redirection->external_function()); // Based on CpuFeatures::IsSupported(FPU), Mips will use either hardware // FPU, or gcc soft-float routines. Hardware FPU is simulated in this // simulator. Soft-float has additional abstraction of ExternalReference, // to support serialization. if (fp_call) { double dval0, dval1; // one or two double parameters int32_t ival; // zero or one integer parameters int64_t iresult = 0; // integer return value double dresult = 0; // double return value GetFpArgs(&dval0, &dval1, &ival); SimulatorRuntimeCall generic_target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { switch (redirection->type()) { case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_COMPARE_CALL: PrintF("Call to host function at %p with args %f, %f", reinterpret_cast(FUNCTION_ADDR(generic_target)), dval0, dval1); break; case ExternalReference::BUILTIN_FP_CALL: PrintF("Call to host function at %p with arg %f", reinterpret_cast(FUNCTION_ADDR(generic_target)), dval0); break; case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Call to host function at %p with args %f, %d", reinterpret_cast(FUNCTION_ADDR(generic_target)), dval0, ival); break; default: UNREACHABLE(); break; } } switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: { SimulatorRuntimeCompareCall target = reinterpret_cast(external); iresult = target(dval0, dval1); set_register(v0, static_cast(iresult)); set_register(v1, static_cast(iresult >> 32)); break; } case ExternalReference::BUILTIN_FP_FP_CALL: { SimulatorRuntimeFPFPCall target = reinterpret_cast(external); dresult = target(dval0, dval1); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_CALL: { SimulatorRuntimeFPCall target = reinterpret_cast(external); dresult = target(dval0); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_INT_CALL: { SimulatorRuntimeFPIntCall target = reinterpret_cast(external); dresult = target(dval0, ival); SetFpResult(dresult); break; } default: UNREACHABLE(); break; } if (::v8::internal::FLAG_trace_sim) { switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: PrintF("Returned %08x\n", static_cast(iresult)); break; case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_FP_CALL: case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Returned %f\n", dresult); break; default: UNREACHABLE(); break; } } } else if (redirection->type() == ExternalReference::DIRECT_API_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x\n", reinterpret_cast(external), arg0); } SimulatorRuntimeDirectApiCall target = reinterpret_cast(external); target(arg0); } else if ( redirection->type() == ExternalReference::PROFILING_API_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x %08x\n", reinterpret_cast(external), arg0, arg1); } SimulatorRuntimeProfilingApiCall target = reinterpret_cast(external); target(arg0, Redirection::ReverseRedirection(arg1)); } else if ( redirection->type() == ExternalReference::DIRECT_GETTER_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x %08x\n", reinterpret_cast(external), arg0, arg1); } SimulatorRuntimeDirectGetterCall target = reinterpret_cast(external); target(arg0, arg1); } else if ( redirection->type() == ExternalReference::PROFILING_GETTER_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x %08x %08x\n", reinterpret_cast(external), arg0, arg1, arg2); } SimulatorRuntimeProfilingGetterCall target = reinterpret_cast(external); target(arg0, arg1, Redirection::ReverseRedirection(arg2)); } else { DCHECK(redirection->type() == ExternalReference::BUILTIN_CALL || redirection->type() == ExternalReference::BUILTIN_CALL_PAIR); SimulatorRuntimeCall target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { PrintF( "Call to host function at %p " "args %08x, %08x, %08x, %08x, %08x, %08x, %08x, %08x, %08x\n", reinterpret_cast(FUNCTION_ADDR(target)), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); set_register(v0, static_cast(result)); set_register(v1, static_cast(result >> 32)); } if (::v8::internal::FLAG_trace_sim) { PrintF("Returned %08x : %08x\n", get_register(v1), get_register(v0)); } set_register(ra, saved_ra); set_pc(get_register(ra)); } else if (func == BREAK && code <= kMaxStopCode) { if (IsWatchpoint(code)) { PrintWatchpoint(code); } else { IncreaseStopCounter(code); HandleStop(code, instr_.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: %" PRIu64 ") ----------" "----------------------------------", code, break_count_, icount_); dbg.PrintAllRegs(); // Print registers and continue running. } void Simulator::HandleStop(uint32_t code, Instruction* instr) { // Stop if it is enabled, otherwise go on jumping over the stop // and the message address. if (IsEnabledStop(code)) { MipsDebugger dbg(this); dbg.Stop(instr); } } bool Simulator::IsStopInstruction(Instruction* instr) { int32_t func = instr->FunctionFieldRaw(); uint32_t code = static_cast(instr->Bits(25, 6)); return (func == BREAK) && code > kMaxWatchpointCode && code <= kMaxStopCode; } bool Simulator::IsEnabledStop(uint32_t code) { DCHECK_LE(code, kMaxStopCode); DCHECK_GT(code, kMaxWatchpointCode); return !(watched_stops_[code].count & kStopDisabledBit); } void Simulator::EnableStop(uint32_t code) { if (!IsEnabledStop(code)) { watched_stops_[code].count &= ~kStopDisabledBit; } } void Simulator::DisableStop(uint32_t code) { if (IsEnabledStop(code)) { watched_stops_[code].count |= kStopDisabledBit; } } void Simulator::IncreaseStopCounter(uint32_t code) { DCHECK_LE(code, kMaxStopCode); if ((watched_stops_[code].count & ~(1 << 31)) == 0x7FFFFFFF) { PrintF("Stop counter for code %i has overflowed.\n" "Enabling this code and reseting the counter to 0.\n", code); watched_stops_[code].count = 0; EnableStop(code); } else { watched_stops_[code].count++; } } // Print a stop status. void Simulator::PrintStopInfo(uint32_t code) { if (code <= kMaxWatchpointCode) { PrintF("That is a watchpoint, not a stop.\n"); return; } else if (code > kMaxStopCode) { PrintF("Code too large, only %u stops can be used\n", kMaxStopCode + 1); return; } const char* state = IsEnabledStop(code) ? "Enabled" : "Disabled"; int32_t count = watched_stops_[code].count & ~kStopDisabledBit; // Don't print the state of unused breakpoints. if (count != 0) { if (watched_stops_[code].desc) { PrintF("stop %i - 0x%x: \t%s, \tcounter = %i, \t%s\n", code, code, state, count, watched_stops_[code].desc); } else { PrintF("stop %i - 0x%x: \t%s, \tcounter = %i\n", code, code, state, count); } } } void Simulator::SignalException(Exception e) { FATAL("Error: Exception %i raised.", static_cast(e)); } // Min/Max template functions for Double and Single arguments. template static T FPAbs(T a); template <> double FPAbs(double a) { return fabs(a); } template <> float FPAbs(float a) { return fabsf(a); } template static bool FPUProcessNaNsAndZeros(T a, T b, MaxMinKind kind, T& result) { if (std::isnan(a) && std::isnan(b)) { result = a; } else if (std::isnan(a)) { result = b; } else if (std::isnan(b)) { result = a; } else if (b == a) { // Handle -0.0 == 0.0 case. // std::signbit() returns int 0 or 1 so subtracting MaxMinKind::kMax // negates the result. result = std::signbit(b) - static_cast(kind) ? b : a; } else { return false; } return true; } template static T FPUMin(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { return result; } else { return b < a ? b : a; } } template static T FPUMax(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMax, result)) { return result; } else { return b > a ? b : a; } } template static T FPUMinA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) < FPAbs(b)) { result = a; } else if (FPAbs(b) < FPAbs(a)) { result = b; } else { result = a < b ? a : b; } } return result; } template static T FPUMaxA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) > FPAbs(b)) { result = a; } else if (FPAbs(b) > FPAbs(a)) { result = b; } else { result = a > b ? a : b; } } return result; } enum class KeepSign : bool { no = false, yes }; template ::value, int>::type = 0> T FPUCanonalizeNaNArg(T result, T arg, KeepSign keepSign = KeepSign::no) { DCHECK(std::isnan(arg)); T qNaN = std::numeric_limits::quiet_NaN(); if (keepSign == KeepSign::yes) { return std::copysign(qNaN, result); } return qNaN; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return result; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first, Args... args) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return FPUCanonalizeNaNArgs(result, keepSign, args...); } template T FPUCanonalizeOperation(Func f, T first, Args... args) { return FPUCanonalizeOperation(f, KeepSign::no, first, args...); } template T FPUCanonalizeOperation(Func f, KeepSign keepSign, T first, Args... args) { T result = f(first, args...); if (std::isnan(result)) { result = FPUCanonalizeNaNArgs(result, keepSign, first, args...); } return result; } // Handle execution based on instruction types. void Simulator::DecodeTypeRegisterDRsType() { double ft, fs, fd; uint32_t cc, fcsr_cc; int64_t i64; fs = get_fpu_register_double(fs_reg()); ft = (instr_.FunctionFieldRaw() != MOVF) ? get_fpu_register_double(ft_reg()) : 0.0; fd = get_fpu_register_double(fd_reg()); int64_t ft_int = bit_cast(ft); int64_t fd_int = bit_cast(fd); cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(IsMipsArchVariant(kMips32r6)); double result, temp, temp_result; double upper = std::ceil(fs); double lower = std::floor(fs); switch (get_fcsr_rounding_mode()) { case kRoundToNearest: if (upper - fs < fs - lower) { result = upper; } else if (upper - fs > fs - lower) { result = lower; } else { temp_result = upper / 2; double reminder = modf(temp_result, &temp); if (reminder == 0) { result = upper; } else { result = lower; } } break; case kRoundToZero: result = (fs > 0 ? lower : upper); break; case kRoundToPlusInf: result = upper; break; case kRoundToMinusInf: result = lower; break; } SetFPUDoubleResult(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), (ft_int & 0x1) == 0 ? fs : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), (ft_int & 0x1) != 0 ? fs : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { SetFPUDoubleResult(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); int32_t rt_reg = instr_.RtValue(); int32_t rt = get_register(rt_reg); if (rt != 0) { SetFPUDoubleResult(fd_reg(), fs); } break; } case MOVF: { // Same function field for MOVT.D and MOVF.D uint32_t ft_cc = (ft_reg() >> 2) & 0x7; ft_cc = get_fcsr_condition_bit(ft_cc); if (instr_.Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) SetFPUDoubleResult(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) SetFPUDoubleResult(fd_reg(), fs); } break; } case MIN: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMin(ft, fs)); break; case MAX: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMax(ft, fs)); break; case MINA: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMinA(ft, fs)); break; case MAXA: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMaxA(ft, fs)); break; case ADD_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_D: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), std::fma(fs, ft, fd)); break; case MSUBF_D: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), std::fma(-fs, ft, fd)); break; case MUL_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation([](double fs) { return FPAbs(fs); }, fs)); break; case MOV_D: SetFPUDoubleResult(fd_reg(), fs); break; case NEG_D: SetFPUDoubleResult(fd_reg(), FPUCanonalizeOperation([](double src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation([](double fs) { return std::sqrt(fs); }, fs)); break; case RSQRT_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double fs) { return 1.0 / std::sqrt(fs); }, fs)); break; case RECIP_D: SetFPUDoubleResult(fd_reg(), FPUCanonalizeOperation( [](double fs) { return 1.0 / fs; }, fs)); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case CVT_W_D: { // Convert double to word. double rounded; int32_t result; round_according_to_fcsr(fs, rounded, result, fs); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case ROUND_W_D: // Round double to word (round half to even). { double rounded = std::floor(fs + 0.5); int32_t result = static_cast(rounded); if ((result & 1) != 0 && result - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. result--; } SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case TRUNC_W_D: // Truncate double to word (round towards 0). { double rounded = trunc(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case FLOOR_W_D: // Round double to word towards negative infinity. { double rounded = std::floor(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case CEIL_W_D: // Round double to word towards positive infinity. { double rounded = std::ceil(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case CVT_S_D: // Convert double to float (single). SetFPUFloatResult(fd_reg(), static_cast(fs)); break; case CVT_L_D: { // Mips32r2: Truncate double to 64-bit long-word. if (IsFp64Mode()) { int64_t result; double rounded; round64_according_to_fcsr(fs, rounded, result, fs); SetFPUResult(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; break; } case TRUNC_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = trunc(fs); i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case ROUND_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = std::floor(fs + 0.5); int64_t result = static_cast(rounded); if ((result & 1) != 0 && result - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. result--; } int64_t i64 = static_cast(result); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case FLOOR_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = std::floor(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CEIL_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = std::ceil(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CLASS_D: { // Mips32r6 instruction // Convert double input to uint64_t for easier bit manipulation uint64_t classed = bit_cast(fs); // Extracting sign, exponent and mantissa from the input double uint32_t sign = (classed >> 63) & 1; uint32_t exponent = (classed >> 52) & 0x00000000000007FF; uint64_t mantissa = classed & 0x000FFFFFFFFFFFFF; uint64_t result; double dResult; // Setting flags if input double is negative infinity, // positive infinity, negative zero or positive zero bool negInf = (classed == 0xFFF0000000000000); bool posInf = (classed == 0x7FF0000000000000); bool negZero = (classed == 0x8000000000000000); bool posZero = (classed == 0x0000000000000000); bool signalingNan; bool quietNan; bool negSubnorm; bool posSubnorm; bool negNorm; bool posNorm; // Setting flags if double is NaN signalingNan = false; quietNan = false; if (!negInf && !posInf && exponent == 0x7FF) { quietNan = ((mantissa & 0x0008000000000000) != 0) && ((mantissa & (0x0008000000000000 - 1)) == 0); signalingNan = !quietNan; } // Setting flags if double is subnormal number posSubnorm = false; negSubnorm = false; if ((exponent == 0) && (mantissa != 0)) { DCHECK(sign == 0 || sign == 1); posSubnorm = (sign == 0); negSubnorm = (sign == 1); } // Setting flags if double is normal number posNorm = false; negNorm = false; if (!posSubnorm && !negSubnorm && !posInf && !negInf && !signalingNan && !quietNan && !negZero && !posZero) { DCHECK(sign == 0 || sign == 1); posNorm = (sign == 0); negNorm = (sign == 1); } // Calculating result according to description of CLASS.D instruction result = (posZero << 9) | (posSubnorm << 8) | (posNorm << 7) | (posInf << 6) | (negZero << 5) | (negSubnorm << 4) | (negNorm << 3) | (negInf << 2) | (quietNan << 1) | signalingNan; DCHECK_NE(result, 0); dResult = bit_cast(result); SetFPUDoubleResult(fd_reg(), dResult); break; } case C_F_D: { set_fcsr_bit(fcsr_cc, false); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; } default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterWRsType() { float fs = get_fpu_register_float(fs_reg()); float ft = get_fpu_register_float(ft_reg()); int32_t alu_out = 0x12345678; switch (instr_.FunctionFieldRaw()) { case CVT_S_W: // Convert word to float (single). alu_out = get_fpu_register_signed_word(fs_reg()); SetFPUFloatResult(fd_reg(), static_cast(alu_out)); break; case CVT_D_W: // Convert word to double. alu_out = get_fpu_register_signed_word(fs_reg()); SetFPUDoubleResult(fd_reg(), static_cast(alu_out)); break; case CMP_AF: SetFPUWordResult(fd_reg(), 0); break; case CMP_UN: if (std::isnan(fs) || std::isnan(ft)) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_EQ: if (fs == ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_UEQ: if ((fs == ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_LT: if (fs < ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_ULT: if ((fs < ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_LE: if (fs <= ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_ULE: if ((fs <= ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_OR: if (!std::isnan(fs) && !std::isnan(ft)) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_UNE: if ((fs != ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_NE: if (fs != ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterSRsType() { float fs, ft, fd; fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); fd = get_fpu_register_float(fd_reg()); int32_t ft_int = bit_cast(ft); int32_t fd_int = bit_cast(fd); uint32_t cc, fcsr_cc; cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(IsMipsArchVariant(kMips32r6)); float result, temp_result; double temp; float upper = std::ceil(fs); float lower = std::floor(fs); switch (get_fcsr_rounding_mode()) { case kRoundToNearest: if (upper - fs < fs - lower) { result = upper; } else if (upper - fs > fs - lower) { result = lower; } else { temp_result = upper / 2; float reminder = modf(temp_result, &temp); if (reminder == 0) { result = upper; } else { result = lower; } } break; case kRoundToZero: result = (fs > 0 ? lower : upper); break; case kRoundToPlusInf: result = upper; break; case kRoundToMinusInf: result = lower; break; } SetFPUFloatResult(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case ADD_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_S: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult(fd_reg(), std::fma(fs, ft, fd)); break; case MSUBF_S: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult(fd_reg(), std::fma(-fs, ft, fd)); break; case MUL_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_S: SetFPUFloatResult(fd_reg(), FPUCanonalizeOperation( [](float fs) { return FPAbs(fs); }, fs)); break; case MOV_S: SetFPUFloatResult(fd_reg(), fs); break; case NEG_S: SetFPUFloatResult(fd_reg(), FPUCanonalizeOperation([](float src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float src) { return std::sqrt(src); }, fs)); break; case RSQRT_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation( [](float src) { return 1.0 / std::sqrt(src); }, fs)); break; case RECIP_S: SetFPUFloatResult(fd_reg(), FPUCanonalizeOperation( [](float src) { return 1.0 / src; }, fs)); break; case C_F_D: set_fcsr_bit(fcsr_cc, false); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case CVT_D_S: SetFPUDoubleResult(fd_reg(), static_cast(fs)); break; case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case CLASS_S: { // Mips32r6 instruction // Convert float input to uint32_t for easier bit manipulation float fs = get_fpu_register_float(fs_reg()); uint32_t classed = bit_cast(fs); // Extracting sign, exponent and mantissa from the input float uint32_t sign = (classed >> 31) & 1; uint32_t exponent = (classed >> 23) & 0x000000FF; uint32_t mantissa = classed & 0x007FFFFF; uint32_t result; float fResult; // Setting flags if input float is negative infinity, // positive infinity, negative zero or positive zero bool negInf = (classed == 0xFF800000); bool posInf = (classed == 0x7F800000); bool negZero = (classed == 0x80000000); bool posZero = (classed == 0x00000000); bool signalingNan; bool quietNan; bool negSubnorm; bool posSubnorm; bool negNorm; bool posNorm; // Setting flags if float is NaN signalingNan = false; quietNan = false; if (!negInf && !posInf && (exponent == 0xFF)) { quietNan = ((mantissa & 0x00200000) == 0) && ((mantissa & (0x00200000 - 1)) == 0); signalingNan = !quietNan; } // Setting flags if float is subnormal number posSubnorm = false; negSubnorm = false; if ((exponent == 0) && (mantissa != 0)) { DCHECK(sign == 0 || sign == 1); posSubnorm = (sign == 0); negSubnorm = (sign == 1); } // Setting flags if float is normal number posNorm = false; negNorm = false; if (!posSubnorm && !negSubnorm && !posInf && !negInf && !signalingNan && !quietNan && !negZero && !posZero) { DCHECK(sign == 0 || sign == 1); posNorm = (sign == 0); negNorm = (sign == 1); } // Calculating result according to description of CLASS.S instruction result = (posZero << 9) | (posSubnorm << 8) | (posNorm << 7) | (posInf << 6) | (negZero << 5) | (negSubnorm << 4) | (negNorm << 3) | (negInf << 2) | (quietNan << 1) | signalingNan; DCHECK_NE(result, 0); fResult = bit_cast(result); SetFPUFloatResult(fd_reg(), fResult); break; } case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult( fd_reg(), (ft_int & 0x1) == 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult( fd_reg(), (ft_int & 0x1) != 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { SetFPUFloatResult(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() != 0) { SetFPUFloatResult(fd_reg(), fs); } break; } case MOVF: { // Same function field for MOVT.D and MOVF.D uint32_t ft_cc = (ft_reg() >> 2) & 0x7; ft_cc = get_fcsr_condition_bit(ft_cc); if (instr_.Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) SetFPUFloatResult(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) SetFPUFloatResult(fd_reg(), fs); } break; } case TRUNC_W_S: { // Truncate single to word (round towards 0). float rounded = trunc(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case TRUNC_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float rounded = trunc(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case FLOOR_W_S: // Round double to word towards negative infinity. { float rounded = std::floor(fs); int32_t result = static_cast
\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 } bool Simulator::ICacheMatch(void* one, void* two) { DCHECK_EQ(reinterpret_cast(one) & CachePage::kPageMask, 0); DCHECK_EQ(reinterpret_cast(two) & CachePage::kPageMask, 0); return one == two; } static uint32_t ICacheHash(void* key) { return static_cast(reinterpret_cast(key)) >> 2; } static bool AllOnOnePage(uintptr_t start, int size) { intptr_t start_page = (start & ~CachePage::kPageMask); intptr_t end_page = ((start + size) & ~CachePage::kPageMask); return start_page == end_page; } void Simulator::set_last_debugger_input(char* input) { DeleteArray(last_debugger_input_); last_debugger_input_ = input; } void Simulator::SetRedirectInstruction(Instruction* instruction) { instruction->SetInstructionBits(rtCallRedirInstr); } void Simulator::FlushICache(base::CustomMatcherHashMap* i_cache, void* start_addr, size_t size) { intptr_t start = reinterpret_cast(start_addr); int intra_line = (start & CachePage::kLineMask); start -= intra_line; size += intra_line; size = ((size - 1) | CachePage::kLineMask) + 1; int offset = (start & CachePage::kPageMask); while (!AllOnOnePage(start, size - 1)) { int bytes_to_flush = CachePage::kPageSize - offset; FlushOnePage(i_cache, start, bytes_to_flush); start += bytes_to_flush; size -= bytes_to_flush; DCHECK_EQ(0, start & CachePage::kPageMask); offset = 0; } if (size != 0) { FlushOnePage(i_cache, start, size); } } CachePage* Simulator::GetCachePage(base::CustomMatcherHashMap* i_cache, void* page) { base::CustomMatcherHashMap::Entry* entry = i_cache->LookupOrInsert(page, ICacheHash(page)); if (entry->value == nullptr) { CachePage* new_page = new CachePage(); entry->value = new_page; } return reinterpret_cast(entry->value); } // Flush from start up to and not including start + size. void Simulator::FlushOnePage(base::CustomMatcherHashMap* i_cache, intptr_t start, int size) { DCHECK_LE(size, CachePage::kPageSize); DCHECK(AllOnOnePage(start, size - 1)); DCHECK_EQ(start & CachePage::kLineMask, 0); DCHECK_EQ(size & CachePage::kLineMask, 0); void* page = reinterpret_cast(start & (~CachePage::kPageMask)); int offset = (start & CachePage::kPageMask); CachePage* cache_page = GetCachePage(i_cache, page); char* valid_bytemap = cache_page->ValidityByte(offset); memset(valid_bytemap, CachePage::LINE_INVALID, size >> CachePage::kLineShift); } void Simulator::CheckICache(base::CustomMatcherHashMap* i_cache, Instruction* instr) { intptr_t address = reinterpret_cast(instr); void* page = reinterpret_cast(address & (~CachePage::kPageMask)); void* line = reinterpret_cast(address & (~CachePage::kLineMask)); int offset = (address & CachePage::kPageMask); CachePage* cache_page = GetCachePage(i_cache, page); char* cache_valid_byte = cache_page->ValidityByte(offset); bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID); char* cached_line = cache_page->CachedData(offset & ~CachePage::kLineMask); if (cache_hit) { // Check that the data in memory matches the contents of the I-cache. CHECK_EQ(0, memcmp(reinterpret_cast(instr), cache_page->CachedData(offset), kInstrSize)); } else { // Cache miss. Load memory into the cache. memcpy(cached_line, line, CachePage::kLineLength); *cache_valid_byte = CachePage::LINE_VALID; } } Simulator::Simulator(Isolate* isolate) : isolate_(isolate) { // Set up simulator support first. Some of this information is needed to // setup the architecture state. stack_ = reinterpret_cast(malloc(stack_size_)); pc_modified_ = false; icount_ = 0; break_count_ = 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 < kNumSimuRegisters; i++) { registers_[i] = 0; } for (int i = 0; i < kNumFPURegisters; i++) { FPUregisters_[2 * i] = 0; FPUregisters_[2 * i + 1] = 0; // upper part for MSA ASE } if (IsMipsArchVariant(kMips32r6)) { FCSR_ = kFCSRNaN2008FlagMask; MSACSR_ = 0; } else { DCHECK(IsMipsArchVariant(kMips32r1) || IsMipsArchVariant(kMips32r2)); FCSR_ = 0; } // The sp is initialized to point to the bottom (high address) of the // allocated stack area. To be safe in potential stack underflows we leave // some buffer below. registers_[sp] = reinterpret_cast(stack_) + stack_size_ - 64; // The ra and pc are initialized to a known bad value that will cause an // access violation if the simulator ever tries to execute it. registers_[pc] = bad_ra; registers_[ra] = bad_ra; last_debugger_input_ = nullptr; } Simulator::~Simulator() { free(stack_); } // Get the active Simulator for the current thread. Simulator* Simulator::current(Isolate* isolate) { v8::internal::Isolate::PerIsolateThreadData* isolate_data = isolate->FindOrAllocatePerThreadDataForThisThread(); DCHECK_NOT_NULL(isolate_data); Simulator* sim = isolate_data->simulator(); if (sim == nullptr) { // TODO(146): delete the simulator object when a thread/isolate goes away. sim = new Simulator(isolate); isolate_data->set_simulator(sim); } return sim; } // Sets the register in the architecture state. It will also deal with updating // Simulator internal state for special registers such as PC. void Simulator::set_register(int reg, int32_t value) { DCHECK((reg >= 0) && (reg < kNumSimuRegisters)); if (reg == pc) { pc_modified_ = true; } // Zero register always holds 0. registers_[reg] = (reg == 0) ? 0 : value; } void Simulator::set_dw_register(int reg, const int* dbl) { DCHECK((reg >= 0) && (reg < kNumSimuRegisters)); registers_[reg] = dbl[0]; registers_[reg + 1] = dbl[1]; } void Simulator::set_fpu_register(int fpureg, int64_t value) { DCHECK(IsFp64Mode()); DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); FPUregisters_[fpureg * 2] = value; } void Simulator::set_fpu_register_word(int fpureg, int32_t value) { // Set ONLY lower 32-bits, leaving upper bits untouched. // TODO(plind): big endian issue. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* pword = reinterpret_cast(&FPUregisters_[fpureg * 2]); *pword = value; } void Simulator::set_fpu_register_hi_word(int fpureg, int32_t value) { // Set ONLY upper 32-bits, leaving lower bits untouched. // TODO(plind): big endian issue. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* phiword = (reinterpret_cast(&FPUregisters_[fpureg * 2])) + 1; *phiword = value; } void Simulator::set_fpu_register_float(int fpureg, float value) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg * 2]) = value; } void Simulator::set_fpu_register_double(int fpureg, double value) { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg * 2]) = value; } else { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); int64_t i64 = bit_cast(value); set_fpu_register_word(fpureg, i64 & 0xFFFFFFFF); set_fpu_register_word(fpureg + 1, i64 >> 32); } } // Get the register from the architecture state. This function does handle // the special case of accessing the PC register. int32_t Simulator::get_register(int reg) const { DCHECK((reg >= 0) && (reg < kNumSimuRegisters)); if (reg == 0) return 0; else return registers_[reg] + ((reg == pc) ? Instruction::kPCReadOffset : 0); } double Simulator::get_double_from_register_pair(int reg) { // TODO(plind): bad ABI stuff, refactor or remove. DCHECK((reg >= 0) && (reg < 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. char buffer[2 * sizeof(registers_[0])]; memcpy(buffer, ®isters_[reg], 2 * sizeof(registers_[0])); memcpy(&dm_val, buffer, 2 * sizeof(registers_[0])); return(dm_val); } int64_t Simulator::get_fpu_register(int fpureg) const { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return FPUregisters_[fpureg * 2]; } else { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); uint64_t i64; i64 = static_cast(get_fpu_register_word(fpureg)); i64 |= static_cast(get_fpu_register_word(fpureg + 1)) << 32; return static_cast(i64); } } int32_t Simulator::get_fpu_register_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg * 2] & 0xFFFFFFFF); } int32_t Simulator::get_fpu_register_signed_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg * 2] & 0xFFFFFFFF); } int32_t Simulator::get_fpu_register_hi_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast((FPUregisters_[fpureg * 2] >> 32) & 0xFFFFFFFF); } float Simulator::get_fpu_register_float(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(const_cast(&FPUregisters_[fpureg * 2])); } double Simulator::get_fpu_register_double(int fpureg) const { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(&FPUregisters_[fpureg * 2]); } else { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); int64_t i64; i64 = static_cast(get_fpu_register_word(fpureg)); i64 |= static_cast(get_fpu_register_word(fpureg + 1)) << 32; return bit_cast(i64); } } template void Simulator::get_msa_register(int wreg, T* value) { DCHECK((wreg >= 0) && (wreg < kNumMSARegisters)); memcpy(value, FPUregisters_ + wreg * 2, kSimd128Size); } template void Simulator::set_msa_register(int wreg, const T* value) { DCHECK((wreg >= 0) && (wreg < kNumMSARegisters)); memcpy(FPUregisters_ + wreg * 2, value, kSimd128Size); } // 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) { if (!IsMipsSoftFloatABI) { *x = get_fpu_register_double(12); *y = get_fpu_register_double(14); *z = get_register(a2); } else { // TODO(plind): bad ABI stuff, refactor or remove. // We use a char buffer to get around the strict-aliasing rules which // otherwise allow the compiler to optimize away the copy. char buffer[sizeof(*x)]; int32_t* reg_buffer = reinterpret_cast(buffer); // Registers a0 and a1 -> x. reg_buffer[0] = get_register(a0); reg_buffer[1] = get_register(a1); memcpy(x, buffer, sizeof(buffer)); // Registers a2 and a3 -> y. reg_buffer[0] = get_register(a2); reg_buffer[1] = get_register(a3); memcpy(y, buffer, sizeof(buffer)); // Register 2 -> z. reg_buffer[0] = get_register(a2); memcpy(z, buffer, sizeof(*z)); } } // The return value is either in v0/v1 or f0. void Simulator::SetFpResult(const double& result) { if (!IsMipsSoftFloatABI) { set_fpu_register_double(0, result); } else { char buffer[2 * sizeof(registers_[0])]; int32_t* reg_buffer = reinterpret_cast(buffer); memcpy(buffer, &result, sizeof(buffer)); // Copy result to v0 and v1. set_register(v0, reg_buffer[0]); set_register(v1, reg_buffer[1]); } } // Helper functions for setting and testing the FCSR register's bits. void Simulator::set_fcsr_bit(uint32_t cc, bool value) { if (value) { FCSR_ |= (1 << cc); } else { FCSR_ &= ~(1 << cc); } } bool Simulator::test_fcsr_bit(uint32_t cc) { return FCSR_ & (1 << cc); } void Simulator::set_fcsr_rounding_mode(FPURoundingMode mode) { FCSR_ |= mode & kFPURoundingModeMask; } void Simulator::set_msacsr_rounding_mode(FPURoundingMode mode) { MSACSR_ |= mode & kFPURoundingModeMask; } unsigned int Simulator::get_fcsr_rounding_mode() { return FCSR_ & kFPURoundingModeMask; } unsigned int Simulator::get_msacsr_rounding_mode() { return MSACSR_ & kFPURoundingModeMask; } void Simulator::set_fpu_register_word_invalid_result(float original, float rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register_word(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result(float original, float rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result64(float original, float rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded >= max_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResult); } else if (rounded < min_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPU64InvalidResult); } } void Simulator::set_fpu_register_word_invalid_result(double original, double rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register_word(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result(double original, double rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result64(double original, double rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded >= max_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResult); } else if (rounded < min_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPU64InvalidResult); } } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round_error(double original, double rounded) { bool ret = false; double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded > max_int32 || rounded < min_int32) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round64_error(double original, double rounded) { bool ret = false; // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded >= max_int64 || rounded < min_int64) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round_error(float original, float rounded) { bool ret = false; double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < FLT_MIN && rounded > -FLT_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded > max_int32 || rounded < min_int32) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round64_error(float original, float rounded) { bool ret = false; // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < FLT_MIN && rounded > -FLT_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded >= max_int64 || rounded < min_int64) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } void Simulator::round_according_to_fcsr(double toRound, double& rounded, int32_t& rounded_int, double fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RD (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (get_fcsr_rounding_mode()) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } void Simulator::round_according_to_fcsr(float toRound, float& rounded, int32_t& rounded_int, float fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RD (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (get_fcsr_rounding_mode()) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.f; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } template void Simulator::round_according_to_msacsr(T_fp toRound, T_fp& rounded, T_int& rounded_int) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RD (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (get_msacsr_rounding_mode()) { case kRoundToNearest: rounded = std::floor(toRound + 0.5); rounded_int = static_cast(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--; rounded -= 1; } break; case kRoundToZero: rounded = trunc(toRound); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(toRound); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(toRound); rounded_int = static_cast(rounded); break; } } void Simulator::round64_according_to_fcsr(double toRound, double& rounded, int64_t& rounded_int, double fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or. // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward +infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RN (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (FCSR_ & 3) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } void Simulator::round64_according_to_fcsr(float toRound, float& rounded, int64_t& rounded_int, float fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or. // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward +infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RN (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (FCSR_ & 3) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.f; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } // 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. void Simulator::TraceRegWr(int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int32_t fmt_int32; float fmt_float; } v; v.fmt_int32 = value; switch (t) { case WORD: SNPrintF(trace_buf_, "%08" PRIx32 " (%" PRIu64 ") int32:%" PRId32 " uint32:%" PRIu32, value, icount_, value, value); break; case FLOAT: SNPrintF(trace_buf_, "%08" PRIx32 " (%" PRIu64 ") flt:%e", v.fmt_int32, icount_, v.fmt_float); break; default: UNREACHABLE(); } } } void Simulator::TraceRegWr(int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int64_t fmt_int64; double fmt_double; } v; v.fmt_int64 = value; switch (t) { case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " (%" PRIu64 ") int64:%" PRId64 " uint64:%" PRIu64, value, icount_, value, value); break; case DOUBLE: SNPrintF(trace_buf_, "%016" PRIx64 " (%" PRIu64 ") dbl:%e", v.fmt_int64, icount_, v.fmt_double); break; default: UNREACHABLE(); } } } template void Simulator::TraceMSARegWr(T* value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { uint8_t b[16]; uint16_t h[8]; uint32_t w[4]; uint64_t d[2]; float f[4]; double df[2]; } v; memcpy(v.b, value, kSimd128Size); switch (t) { case BYTE: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); break; case HALF: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); break; case WORD: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") int32[0..3]:%" PRId32 " %" PRId32 " %" PRId32 " %" PRId32, v.d[0], v.d[1], icount_, v.w[0], v.w[1], v.w[2], v.w[3]); break; case DWORD: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); break; case FLOAT: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") flt[0..3]:%e %e %e %e", v.d[0], v.d[1], icount_, v.f[0], v.f[1], v.f[2], v.f[3]); break; case DOUBLE: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") dbl[0..1]:%e %e", v.d[0], v.d[1], icount_, v.df[0], v.df[1]); break; default: UNREACHABLE(); } } } template void Simulator::TraceMSARegWr(T* value) { if (::v8::internal::FLAG_trace_sim) { union { uint8_t b[kMSALanesByte]; uint16_t h[kMSALanesHalf]; uint32_t w[kMSALanesWord]; uint64_t d[kMSALanesDword]; float f[kMSALanesWord]; double df[kMSALanesDword]; } v; memcpy(v.b, value, kMSALanesByte); if (std::is_same::value) { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") int32[0..3]:%" PRId32 " %" PRId32 " %" PRId32 " %" PRId32, v.d[0], v.d[1], icount_, v.w[0], v.w[1], v.w[2], v.w[3]); } else if (std::is_same::value) { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") flt[0..3]:%e %e %e %e", v.d[0], v.d[1], icount_, v.f[0], v.f[1], v.f[2], v.f[3]); } else if (std::is_same::value) { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") dbl[0..1]:%e %e", v.d[0], v.d[1], icount_, v.df[0], v.df[1]); } else { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); } } } // TODO(plind): consider making icount_ printing a flag option. void Simulator::TraceMemRd(int32_t addr, int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int32_t fmt_int32; float fmt_float; } v; v.fmt_int32 = value; switch (t) { case WORD: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") int32:%" PRId32 " uint32:%" PRIu32, value, addr, icount_, value, value); break; case FLOAT: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") flt:%e", v.fmt_int32, addr, icount_, v.fmt_float); break; default: UNREACHABLE(); } } } void Simulator::TraceMemWr(int32_t addr, int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case BYTE: SNPrintF(trace_buf_, " %02" PRIx8 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case HALF: SNPrintF(trace_buf_, " %04" PRIx16 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case WORD: SNPrintF(trace_buf_, "%08" PRIx32 " --> [%08" PRIx32 "] (%" PRIu64 ")", value, addr, icount_); break; default: UNREACHABLE(); } } } template void Simulator::TraceMemRd(int32_t addr, T value) { if (::v8::internal::FLAG_trace_sim) { switch (sizeof(T)) { case 1: SNPrintF(trace_buf_, "%08" PRIx8 " <-- [%08" PRIx32 "] (%" PRIu64 ") int8:%" PRId8 " uint8:%" PRIu8, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; case 2: SNPrintF(trace_buf_, "%08" PRIx16 " <-- [%08" PRIx32 "] (%" PRIu64 ") int16:%" PRId16 " uint16:%" PRIu16, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; case 4: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") int32:%" PRId32 " uint32:%" PRIu32, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; case 8: SNPrintF(trace_buf_, "%08" PRIx64 " <-- [%08" PRIx32 "] (%" PRIu64 ") int64:%" PRId64 " uint64:%" PRIu64, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; default: UNREACHABLE(); } } } template void Simulator::TraceMemWr(int32_t addr, T value) { if (::v8::internal::FLAG_trace_sim) { switch (sizeof(T)) { case 1: SNPrintF(trace_buf_, " %02" PRIx8 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case 2: SNPrintF(trace_buf_, " %04" PRIx16 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case 4: SNPrintF(trace_buf_, "%08" PRIx32 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case 8: SNPrintF(trace_buf_, "%16" PRIx64 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; default: UNREACHABLE(); } } } void Simulator::TraceMemRd(int32_t addr, int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int64_t fmt_int64; int32_t fmt_int32[2]; float fmt_float[2]; double fmt_double; } v; v.fmt_int64 = value; switch (t) { case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " <-- [%08" PRIx32 "] (%" PRIu64 ") int64:%" PRId64 " uint64:%" PRIu64, v.fmt_int64, addr, icount_, v.fmt_int64, v.fmt_int64); break; case DOUBLE: SNPrintF(trace_buf_, "%016" PRIx64 " <-- [%08" PRIx32 "] (%" PRIu64 ") dbl:%e", v.fmt_int64, addr, icount_, v.fmt_double); break; case FLOAT_DOUBLE: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") flt:%e dbl:%e", v.fmt_int32[1], addr, icount_, v.fmt_float[1], v.fmt_double); break; default: UNREACHABLE(); } } } void Simulator::TraceMemWr(int32_t addr, int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " --> [%08" PRIx32 "] (%" PRIu64 ")", value, addr, icount_); break; default: UNREACHABLE(); } } } int Simulator::ReadW(int32_t addr, Instruction* instr, TraceType t) { if (addr >=0 && addr < 0x400) { // This has to be a nullptr-dereference, drop into debugger. PrintF("Memory read from bad address: 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { intptr_t* ptr = reinterpret_cast(addr); switch (t) { case WORD: TraceMemRd(addr, static_cast(*ptr), t); break; case FLOAT: // This TraceType is allowed but tracing for this value will be omitted. break; default: UNREACHABLE(); } return *ptr; } PrintF("Unaligned read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); return 0; } void Simulator::WriteW(int32_t addr, int value, Instruction* instr) { if (addr >= 0 && addr < 0x400) { // This has to be a nullptr-dereference, drop into debugger. PrintF("Memory write to bad address: 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { intptr_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, WORD); *ptr = value; return; } PrintF("Unaligned write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } double Simulator::ReadD(int32_t addr, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { double* ptr = reinterpret_cast(addr); return *ptr; } PrintF("Unaligned (double) read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } void Simulator::WriteD(int32_t addr, double value, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { double* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned (double) write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); } uint16_t Simulator::ReadHU(int32_t addr, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { uint16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned unsigned halfword read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } int16_t Simulator::ReadH(int32_t addr, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { int16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned signed halfword read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } void Simulator::WriteH(int32_t addr, uint16_t value, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { uint16_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, HALF); *ptr = value; return; } PrintF("Unaligned unsigned halfword write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); } void Simulator::WriteH(int32_t addr, int16_t value, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { int16_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, HALF); *ptr = value; return; } PrintF("Unaligned halfword write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); } uint32_t Simulator::ReadBU(int32_t addr) { uint8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr & 0xFF; } int32_t Simulator::ReadB(int32_t addr) { int8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } void Simulator::WriteB(int32_t addr, uint8_t value) { uint8_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, BYTE); *ptr = value; } void Simulator::WriteB(int32_t addr, int8_t value) { int8_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, BYTE); *ptr = value; } template T Simulator::ReadMem(int32_t addr, Instruction* instr) { int alignment_mask = (1 << sizeof(T)) - 1; if ((addr & alignment_mask) == 0 || IsMipsArchVariant(kMips32r6)) { T* ptr = reinterpret_cast(addr); TraceMemRd(addr, *ptr); return *ptr; } PrintF("Unaligned read of type sizeof(%d) at 0x%08x, pc=0x%08" V8PRIxPTR "\n", sizeof(T), addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } template void Simulator::WriteMem(int32_t addr, T value, Instruction* instr) { int alignment_mask = (1 << sizeof(T)) - 1; if ((addr & alignment_mask) == 0 || IsMipsArchVariant(kMips32r6)) { T* ptr = reinterpret_cast(addr); *ptr = value; TraceMemWr(addr, value); return; } PrintF("Unaligned write of type sizeof(%d) at 0x%08x, pc=0x%08" V8PRIxPTR "\n", sizeof(T), addr, reinterpret_cast(instr)); base::OS::Abort(); } // Returns the limit of the stack area to enable checking for stack overflows. uintptr_t Simulator::StackLimit(uintptr_t c_limit) const { // The simulator uses a separate JS stack. If we have exhausted the C stack, // we also drop down the JS limit to reflect the exhaustion on the JS stack. if (GetCurrentStackPosition() < c_limit) { return reinterpret_cast(get_sp()); } // Otherwise the limit is the JS stack. Leave a safety margin of 1024 bytes // to prevent overrunning the stack when pushing values. return reinterpret_cast(stack_) + 1024; } // Unsupported instructions use Format to print an error and stop execution. void Simulator::Format(Instruction* instr, const char* format) { PrintF("Simulator found unsupported instruction:\n 0x%08" PRIxPTR ": %s\n", reinterpret_cast(instr), format); UNIMPLEMENTED_MIPS(); } // Calls into the V8 runtime are based on this very simple interface. // Note: To be able to return two values from some calls the code in runtime.cc // uses the ObjectPair which is essentially two 32-bit values stuffed into a // 64-bit value. With the code below we assume that all runtime calls return // 64 bits of result. If they don't, the v1 result register contains a bogus // value, which is fine because it is caller-saved. typedef int64_t (*SimulatorRuntimeCall)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6, int32_t arg7, int32_t arg8); // These prototypes handle the four types of FP calls. typedef int64_t (*SimulatorRuntimeCompareCall)(double darg0, double darg1); typedef double (*SimulatorRuntimeFPFPCall)(double darg0, double darg1); typedef double (*SimulatorRuntimeFPCall)(double darg0); typedef double (*SimulatorRuntimeFPIntCall)(double darg0, int32_t arg0); // This signature supports direct call in to API function native callback // (refer to InvocationCallback in v8.h). typedef void (*SimulatorRuntimeDirectApiCall)(int32_t arg0); typedef void (*SimulatorRuntimeProfilingApiCall)(int32_t arg0, void* arg1); // This signature supports direct call to accessor getter callback. typedef void (*SimulatorRuntimeDirectGetterCall)(int32_t arg0, int32_t arg1); typedef void (*SimulatorRuntimeProfilingGetterCall)( int32_t arg0, int32_t arg1, void* arg2); // Software interrupt instructions are used by the simulator to call into the // C-based V8 runtime. They are also used for debugging with simulator. void Simulator::SoftwareInterrupt() { // There are several instructions that could get us here, // the break_ instruction, or several variants of traps. All // Are "SPECIAL" class opcode, and are distinuished by function. int32_t func = instr_.FunctionFieldRaw(); uint32_t code = (func == BREAK) ? instr_.Bits(25, 6) : -1; // We first check if we met a call_rt_redirected. if (instr_.InstructionBits() == rtCallRedirInstr) { Redirection* redirection = Redirection::FromInstruction(instr_.instr()); int32_t arg0 = get_register(a0); int32_t arg1 = get_register(a1); int32_t arg2 = get_register(a2); int32_t arg3 = get_register(a3); int32_t* stack_pointer = reinterpret_cast(get_register(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]; int32_t arg6 = stack_pointer[6]; int32_t arg7 = stack_pointer[7]; int32_t arg8 = stack_pointer[8]; STATIC_ASSERT(kMaxCParameters == 9); bool fp_call = (redirection->type() == ExternalReference::BUILTIN_FP_FP_CALL) || (redirection->type() == ExternalReference::BUILTIN_COMPARE_CALL) || (redirection->type() == ExternalReference::BUILTIN_FP_CALL) || (redirection->type() == ExternalReference::BUILTIN_FP_INT_CALL); if (!IsMipsSoftFloatABI) { // With the hard floating point calling convention, double // arguments are passed in FPU registers. Fetch the arguments // from there and call the builtin using soft floating point // convention. switch (redirection->type()) { case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_COMPARE_CALL: if (IsFp64Mode()) { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_hi_word(f12); arg2 = get_fpu_register_word(f14); arg3 = get_fpu_register_hi_word(f14); } else { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_word(f13); arg2 = get_fpu_register_word(f14); arg3 = get_fpu_register_word(f15); } break; case ExternalReference::BUILTIN_FP_CALL: if (IsFp64Mode()) { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_hi_word(f12); } else { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_word(f13); } break; case ExternalReference::BUILTIN_FP_INT_CALL: if (IsFp64Mode()) { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_hi_word(f12); } else { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_word(f13); } arg2 = get_register(a2); break; default: break; } } // This is dodgy but it works because the C entry stubs are never moved. // See comment in codegen-arm.cc and bug 1242173. int32_t saved_ra = get_register(ra); intptr_t external = reinterpret_cast(redirection->external_function()); // Based on CpuFeatures::IsSupported(FPU), Mips will use either hardware // FPU, or gcc soft-float routines. Hardware FPU is simulated in this // simulator. Soft-float has additional abstraction of ExternalReference, // to support serialization. if (fp_call) { double dval0, dval1; // one or two double parameters int32_t ival; // zero or one integer parameters int64_t iresult = 0; // integer return value double dresult = 0; // double return value GetFpArgs(&dval0, &dval1, &ival); SimulatorRuntimeCall generic_target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { switch (redirection->type()) { case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_COMPARE_CALL: PrintF("Call to host function at %p with args %f, %f", reinterpret_cast(FUNCTION_ADDR(generic_target)), dval0, dval1); break; case ExternalReference::BUILTIN_FP_CALL: PrintF("Call to host function at %p with arg %f", reinterpret_cast(FUNCTION_ADDR(generic_target)), dval0); break; case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Call to host function at %p with args %f, %d", reinterpret_cast(FUNCTION_ADDR(generic_target)), dval0, ival); break; default: UNREACHABLE(); break; } } switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: { SimulatorRuntimeCompareCall target = reinterpret_cast(external); iresult = target(dval0, dval1); set_register(v0, static_cast(iresult)); set_register(v1, static_cast(iresult >> 32)); break; } case ExternalReference::BUILTIN_FP_FP_CALL: { SimulatorRuntimeFPFPCall target = reinterpret_cast(external); dresult = target(dval0, dval1); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_CALL: { SimulatorRuntimeFPCall target = reinterpret_cast(external); dresult = target(dval0); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_INT_CALL: { SimulatorRuntimeFPIntCall target = reinterpret_cast(external); dresult = target(dval0, ival); SetFpResult(dresult); break; } default: UNREACHABLE(); break; } if (::v8::internal::FLAG_trace_sim) { switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: PrintF("Returned %08x\n", static_cast(iresult)); break; case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_FP_CALL: case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Returned %f\n", dresult); break; default: UNREACHABLE(); break; } } } else if (redirection->type() == ExternalReference::DIRECT_API_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x\n", reinterpret_cast(external), arg0); } SimulatorRuntimeDirectApiCall target = reinterpret_cast(external); target(arg0); } else if ( redirection->type() == ExternalReference::PROFILING_API_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x %08x\n", reinterpret_cast(external), arg0, arg1); } SimulatorRuntimeProfilingApiCall target = reinterpret_cast(external); target(arg0, Redirection::ReverseRedirection(arg1)); } else if ( redirection->type() == ExternalReference::DIRECT_GETTER_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x %08x\n", reinterpret_cast(external), arg0, arg1); } SimulatorRuntimeDirectGetterCall target = reinterpret_cast(external); target(arg0, arg1); } else if ( redirection->type() == ExternalReference::PROFILING_GETTER_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x %08x %08x\n", reinterpret_cast(external), arg0, arg1, arg2); } SimulatorRuntimeProfilingGetterCall target = reinterpret_cast(external); target(arg0, arg1, Redirection::ReverseRedirection(arg2)); } else { DCHECK(redirection->type() == ExternalReference::BUILTIN_CALL || redirection->type() == ExternalReference::BUILTIN_CALL_PAIR); SimulatorRuntimeCall target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { PrintF( "Call to host function at %p " "args %08x, %08x, %08x, %08x, %08x, %08x, %08x, %08x, %08x\n", reinterpret_cast(FUNCTION_ADDR(target)), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); set_register(v0, static_cast(result)); set_register(v1, static_cast(result >> 32)); } if (::v8::internal::FLAG_trace_sim) { PrintF("Returned %08x : %08x\n", get_register(v1), get_register(v0)); } set_register(ra, saved_ra); set_pc(get_register(ra)); } else if (func == BREAK && code <= kMaxStopCode) { if (IsWatchpoint(code)) { PrintWatchpoint(code); } else { IncreaseStopCounter(code); HandleStop(code, instr_.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: %" PRIu64 ") ----------" "----------------------------------", code, break_count_, icount_); dbg.PrintAllRegs(); // Print registers and continue running. } void Simulator::HandleStop(uint32_t code, Instruction* instr) { // Stop if it is enabled, otherwise go on jumping over the stop // and the message address. if (IsEnabledStop(code)) { MipsDebugger dbg(this); dbg.Stop(instr); } } bool Simulator::IsStopInstruction(Instruction* instr) { int32_t func = instr->FunctionFieldRaw(); uint32_t code = static_cast(instr->Bits(25, 6)); return (func == BREAK) && code > kMaxWatchpointCode && code <= kMaxStopCode; } bool Simulator::IsEnabledStop(uint32_t code) { DCHECK_LE(code, kMaxStopCode); DCHECK_GT(code, kMaxWatchpointCode); return !(watched_stops_[code].count & kStopDisabledBit); } void Simulator::EnableStop(uint32_t code) { if (!IsEnabledStop(code)) { watched_stops_[code].count &= ~kStopDisabledBit; } } void Simulator::DisableStop(uint32_t code) { if (IsEnabledStop(code)) { watched_stops_[code].count |= kStopDisabledBit; } } void Simulator::IncreaseStopCounter(uint32_t code) { DCHECK_LE(code, kMaxStopCode); if ((watched_stops_[code].count & ~(1 << 31)) == 0x7FFFFFFF) { PrintF("Stop counter for code %i has overflowed.\n" "Enabling this code and reseting the counter to 0.\n", code); watched_stops_[code].count = 0; EnableStop(code); } else { watched_stops_[code].count++; } } // Print a stop status. void Simulator::PrintStopInfo(uint32_t code) { if (code <= kMaxWatchpointCode) { PrintF("That is a watchpoint, not a stop.\n"); return; } else if (code > kMaxStopCode) { PrintF("Code too large, only %u stops can be used\n", kMaxStopCode + 1); return; } const char* state = IsEnabledStop(code) ? "Enabled" : "Disabled"; int32_t count = watched_stops_[code].count & ~kStopDisabledBit; // Don't print the state of unused breakpoints. if (count != 0) { if (watched_stops_[code].desc) { PrintF("stop %i - 0x%x: \t%s, \tcounter = %i, \t%s\n", code, code, state, count, watched_stops_[code].desc); } else { PrintF("stop %i - 0x%x: \t%s, \tcounter = %i\n", code, code, state, count); } } } void Simulator::SignalException(Exception e) { FATAL("Error: Exception %i raised.", static_cast(e)); } // Min/Max template functions for Double and Single arguments. template static T FPAbs(T a); template <> double FPAbs(double a) { return fabs(a); } template <> float FPAbs(float a) { return fabsf(a); } template static bool FPUProcessNaNsAndZeros(T a, T b, MaxMinKind kind, T& result) { if (std::isnan(a) && std::isnan(b)) { result = a; } else if (std::isnan(a)) { result = b; } else if (std::isnan(b)) { result = a; } else if (b == a) { // Handle -0.0 == 0.0 case. // std::signbit() returns int 0 or 1 so subtracting MaxMinKind::kMax // negates the result. result = std::signbit(b) - static_cast(kind) ? b : a; } else { return false; } return true; } template static T FPUMin(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { return result; } else { return b < a ? b : a; } } template static T FPUMax(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMax, result)) { return result; } else { return b > a ? b : a; } } template static T FPUMinA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) < FPAbs(b)) { result = a; } else if (FPAbs(b) < FPAbs(a)) { result = b; } else { result = a < b ? a : b; } } return result; } template static T FPUMaxA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) > FPAbs(b)) { result = a; } else if (FPAbs(b) > FPAbs(a)) { result = b; } else { result = a > b ? a : b; } } return result; } enum class KeepSign : bool { no = false, yes }; template ::value, int>::type = 0> T FPUCanonalizeNaNArg(T result, T arg, KeepSign keepSign = KeepSign::no) { DCHECK(std::isnan(arg)); T qNaN = std::numeric_limits::quiet_NaN(); if (keepSign == KeepSign::yes) { return std::copysign(qNaN, result); } return qNaN; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return result; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first, Args... args) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return FPUCanonalizeNaNArgs(result, keepSign, args...); } template T FPUCanonalizeOperation(Func f, T first, Args... args) { return FPUCanonalizeOperation(f, KeepSign::no, first, args...); } template T FPUCanonalizeOperation(Func f, KeepSign keepSign, T first, Args... args) { T result = f(first, args...); if (std::isnan(result)) { result = FPUCanonalizeNaNArgs(result, keepSign, first, args...); } return result; } // Handle execution based on instruction types. void Simulator::DecodeTypeRegisterDRsType() { double ft, fs, fd; uint32_t cc, fcsr_cc; int64_t i64; fs = get_fpu_register_double(fs_reg()); ft = (instr_.FunctionFieldRaw() != MOVF) ? get_fpu_register_double(ft_reg()) : 0.0; fd = get_fpu_register_double(fd_reg()); int64_t ft_int = bit_cast(ft); int64_t fd_int = bit_cast(fd); cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(IsMipsArchVariant(kMips32r6)); double result, temp, temp_result; double upper = std::ceil(fs); double lower = std::floor(fs); switch (get_fcsr_rounding_mode()) { case kRoundToNearest: if (upper - fs < fs - lower) { result = upper; } else if (upper - fs > fs - lower) { result = lower; } else { temp_result = upper / 2; double reminder = modf(temp_result, &temp); if (reminder == 0) { result = upper; } else { result = lower; } } break; case kRoundToZero: result = (fs > 0 ? lower : upper); break; case kRoundToPlusInf: result = upper; break; case kRoundToMinusInf: result = lower; break; } SetFPUDoubleResult(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), (ft_int & 0x1) == 0 ? fs : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), (ft_int & 0x1) != 0 ? fs : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { SetFPUDoubleResult(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); int32_t rt_reg = instr_.RtValue(); int32_t rt = get_register(rt_reg); if (rt != 0) { SetFPUDoubleResult(fd_reg(), fs); } break; } case MOVF: { // Same function field for MOVT.D and MOVF.D uint32_t ft_cc = (ft_reg() >> 2) & 0x7; ft_cc = get_fcsr_condition_bit(ft_cc); if (instr_.Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) SetFPUDoubleResult(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) SetFPUDoubleResult(fd_reg(), fs); } break; } case MIN: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMin(ft, fs)); break; case MAX: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMax(ft, fs)); break; case MINA: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMinA(ft, fs)); break; case MAXA: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMaxA(ft, fs)); break; case ADD_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_D: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), std::fma(fs, ft, fd)); break; case MSUBF_D: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), std::fma(-fs, ft, fd)); break; case MUL_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation([](double fs) { return FPAbs(fs); }, fs)); break; case MOV_D: SetFPUDoubleResult(fd_reg(), fs); break; case NEG_D: SetFPUDoubleResult(fd_reg(), FPUCanonalizeOperation([](double src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation([](double fs) { return std::sqrt(fs); }, fs)); break; case RSQRT_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double fs) { return 1.0 / std::sqrt(fs); }, fs)); break; case RECIP_D: SetFPUDoubleResult(fd_reg(), FPUCanonalizeOperation( [](double fs) { return 1.0 / fs; }, fs)); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case CVT_W_D: { // Convert double to word. double rounded; int32_t result; round_according_to_fcsr(fs, rounded, result, fs); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case ROUND_W_D: // Round double to word (round half to even). { double rounded = std::floor(fs + 0.5); int32_t result = static_cast(rounded); if ((result & 1) != 0 && result - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. result--; } SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case TRUNC_W_D: // Truncate double to word (round towards 0). { double rounded = trunc(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case FLOOR_W_D: // Round double to word towards negative infinity. { double rounded = std::floor(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case CEIL_W_D: // Round double to word towards positive infinity. { double rounded = std::ceil(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case CVT_S_D: // Convert double to float (single). SetFPUFloatResult(fd_reg(), static_cast(fs)); break; case CVT_L_D: { // Mips32r2: Truncate double to 64-bit long-word. if (IsFp64Mode()) { int64_t result; double rounded; round64_according_to_fcsr(fs, rounded, result, fs); SetFPUResult(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; break; } case TRUNC_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = trunc(fs); i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case ROUND_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = std::floor(fs + 0.5); int64_t result = static_cast(rounded); if ((result & 1) != 0 && result - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. result--; } int64_t i64 = static_cast(result); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case FLOOR_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = std::floor(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CEIL_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = std::ceil(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CLASS_D: { // Mips32r6 instruction // Convert double input to uint64_t for easier bit manipulation uint64_t classed = bit_cast(fs); // Extracting sign, exponent and mantissa from the input double uint32_t sign = (classed >> 63) & 1; uint32_t exponent = (classed >> 52) & 0x00000000000007FF; uint64_t mantissa = classed & 0x000FFFFFFFFFFFFF; uint64_t result; double dResult; // Setting flags if input double is negative infinity, // positive infinity, negative zero or positive zero bool negInf = (classed == 0xFFF0000000000000); bool posInf = (classed == 0x7FF0000000000000); bool negZero = (classed == 0x8000000000000000); bool posZero = (classed == 0x0000000000000000); bool signalingNan; bool quietNan; bool negSubnorm; bool posSubnorm; bool negNorm; bool posNorm; // Setting flags if double is NaN signalingNan = false; quietNan = false; if (!negInf && !posInf && exponent == 0x7FF) { quietNan = ((mantissa & 0x0008000000000000) != 0) && ((mantissa & (0x0008000000000000 - 1)) == 0); signalingNan = !quietNan; } // Setting flags if double is subnormal number posSubnorm = false; negSubnorm = false; if ((exponent == 0) && (mantissa != 0)) { DCHECK(sign == 0 || sign == 1); posSubnorm = (sign == 0); negSubnorm = (sign == 1); } // Setting flags if double is normal number posNorm = false; negNorm = false; if (!posSubnorm && !negSubnorm && !posInf && !negInf && !signalingNan && !quietNan && !negZero && !posZero) { DCHECK(sign == 0 || sign == 1); posNorm = (sign == 0); negNorm = (sign == 1); } // Calculating result according to description of CLASS.D instruction result = (posZero << 9) | (posSubnorm << 8) | (posNorm << 7) | (posInf << 6) | (negZero << 5) | (negSubnorm << 4) | (negNorm << 3) | (negInf << 2) | (quietNan << 1) | signalingNan; DCHECK_NE(result, 0); dResult = bit_cast(result); SetFPUDoubleResult(fd_reg(), dResult); break; } case C_F_D: { set_fcsr_bit(fcsr_cc, false); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; } default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterWRsType() { float fs = get_fpu_register_float(fs_reg()); float ft = get_fpu_register_float(ft_reg()); int32_t alu_out = 0x12345678; switch (instr_.FunctionFieldRaw()) { case CVT_S_W: // Convert word to float (single). alu_out = get_fpu_register_signed_word(fs_reg()); SetFPUFloatResult(fd_reg(), static_cast(alu_out)); break; case CVT_D_W: // Convert word to double. alu_out = get_fpu_register_signed_word(fs_reg()); SetFPUDoubleResult(fd_reg(), static_cast(alu_out)); break; case CMP_AF: SetFPUWordResult(fd_reg(), 0); break; case CMP_UN: if (std::isnan(fs) || std::isnan(ft)) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_EQ: if (fs == ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_UEQ: if ((fs == ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_LT: if (fs < ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_ULT: if ((fs < ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_LE: if (fs <= ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_ULE: if ((fs <= ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_OR: if (!std::isnan(fs) && !std::isnan(ft)) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_UNE: if ((fs != ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_NE: if (fs != ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterSRsType() { float fs, ft, fd; fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); fd = get_fpu_register_float(fd_reg()); int32_t ft_int = bit_cast(ft); int32_t fd_int = bit_cast(fd); uint32_t cc, fcsr_cc; cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(IsMipsArchVariant(kMips32r6)); float result, temp_result; double temp; float upper = std::ceil(fs); float lower = std::floor(fs); switch (get_fcsr_rounding_mode()) { case kRoundToNearest: if (upper - fs < fs - lower) { result = upper; } else if (upper - fs > fs - lower) { result = lower; } else { temp_result = upper / 2; float reminder = modf(temp_result, &temp); if (reminder == 0) { result = upper; } else { result = lower; } } break; case kRoundToZero: result = (fs > 0 ? lower : upper); break; case kRoundToPlusInf: result = upper; break; case kRoundToMinusInf: result = lower; break; } SetFPUFloatResult(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case ADD_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_S: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult(fd_reg(), std::fma(fs, ft, fd)); break; case MSUBF_S: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult(fd_reg(), std::fma(-fs, ft, fd)); break; case MUL_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_S: SetFPUFloatResult(fd_reg(), FPUCanonalizeOperation( [](float fs) { return FPAbs(fs); }, fs)); break; case MOV_S: SetFPUFloatResult(fd_reg(), fs); break; case NEG_S: SetFPUFloatResult(fd_reg(), FPUCanonalizeOperation([](float src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float src) { return std::sqrt(src); }, fs)); break; case RSQRT_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation( [](float src) { return 1.0 / std::sqrt(src); }, fs)); break; case RECIP_S: SetFPUFloatResult(fd_reg(), FPUCanonalizeOperation( [](float src) { return 1.0 / src; }, fs)); break; case C_F_D: set_fcsr_bit(fcsr_cc, false); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case CVT_D_S: SetFPUDoubleResult(fd_reg(), static_cast(fs)); break; case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case CLASS_S: { // Mips32r6 instruction // Convert float input to uint32_t for easier bit manipulation float fs = get_fpu_register_float(fs_reg()); uint32_t classed = bit_cast(fs); // Extracting sign, exponent and mantissa from the input float uint32_t sign = (classed >> 31) & 1; uint32_t exponent = (classed >> 23) & 0x000000FF; uint32_t mantissa = classed & 0x007FFFFF; uint32_t result; float fResult; // Setting flags if input float is negative infinity, // positive infinity, negative zero or positive zero bool negInf = (classed == 0xFF800000); bool posInf = (classed == 0x7F800000); bool negZero = (classed == 0x80000000); bool posZero = (classed == 0x00000000); bool signalingNan; bool quietNan; bool negSubnorm; bool posSubnorm; bool negNorm; bool posNorm; // Setting flags if float is NaN signalingNan = false; quietNan = false; if (!negInf && !posInf && (exponent == 0xFF)) { quietNan = ((mantissa & 0x00200000) == 0) && ((mantissa & (0x00200000 - 1)) == 0); signalingNan = !quietNan; } // Setting flags if float is subnormal number posSubnorm = false; negSubnorm = false; if ((exponent == 0) && (mantissa != 0)) { DCHECK(sign == 0 || sign == 1); posSubnorm = (sign == 0); negSubnorm = (sign == 1); } // Setting flags if float is normal number posNorm = false; negNorm = false; if (!posSubnorm && !negSubnorm && !posInf && !negInf && !signalingNan && !quietNan && !negZero && !posZero) { DCHECK(sign == 0 || sign == 1); posNorm = (sign == 0); negNorm = (sign == 1); } // Calculating result according to description of CLASS.S instruction result = (posZero << 9) | (posSubnorm << 8) | (posNorm << 7) | (posInf << 6) | (negZero << 5) | (negSubnorm << 4) | (negNorm << 3) | (negInf << 2) | (quietNan << 1) | signalingNan; DCHECK_NE(result, 0); fResult = bit_cast(result); SetFPUFloatResult(fd_reg(), fResult); break; } case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult( fd_reg(), (ft_int & 0x1) == 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult( fd_reg(), (ft_int & 0x1) != 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { SetFPUFloatResult(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() != 0) { SetFPUFloatResult(fd_reg(), fs); } break; } case MOVF: { // Same function field for MOVT.D and MOVF.D uint32_t ft_cc = (ft_reg() >> 2) & 0x7; ft_cc = get_fcsr_condition_bit(ft_cc); if (instr_.Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) SetFPUFloatResult(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) SetFPUFloatResult(fd_reg(), fs); } break; } case TRUNC_W_S: { // Truncate single to word (round towards 0). float rounded = trunc(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case TRUNC_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float rounded = trunc(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case FLOOR_W_S: // Round double to word towards negative infinity. { float rounded = std::floor(fs); int32_t result = static_cast
: 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 } bool Simulator::ICacheMatch(void* one, void* two) { DCHECK_EQ(reinterpret_cast(one) & CachePage::kPageMask, 0); DCHECK_EQ(reinterpret_cast(two) & CachePage::kPageMask, 0); return one == two; } static uint32_t ICacheHash(void* key) { return static_cast(reinterpret_cast(key)) >> 2; } static bool AllOnOnePage(uintptr_t start, int size) { intptr_t start_page = (start & ~CachePage::kPageMask); intptr_t end_page = ((start + size) & ~CachePage::kPageMask); return start_page == end_page; } void Simulator::set_last_debugger_input(char* input) { DeleteArray(last_debugger_input_); last_debugger_input_ = input; } void Simulator::SetRedirectInstruction(Instruction* instruction) { instruction->SetInstructionBits(rtCallRedirInstr); } void Simulator::FlushICache(base::CustomMatcherHashMap* i_cache, void* start_addr, size_t size) { intptr_t start = reinterpret_cast(start_addr); int intra_line = (start & CachePage::kLineMask); start -= intra_line; size += intra_line; size = ((size - 1) | CachePage::kLineMask) + 1; int offset = (start & CachePage::kPageMask); while (!AllOnOnePage(start, size - 1)) { int bytes_to_flush = CachePage::kPageSize - offset; FlushOnePage(i_cache, start, bytes_to_flush); start += bytes_to_flush; size -= bytes_to_flush; DCHECK_EQ(0, start & CachePage::kPageMask); offset = 0; } if (size != 0) { FlushOnePage(i_cache, start, size); } } CachePage* Simulator::GetCachePage(base::CustomMatcherHashMap* i_cache, void* page) { base::CustomMatcherHashMap::Entry* entry = i_cache->LookupOrInsert(page, ICacheHash(page)); if (entry->value == nullptr) { CachePage* new_page = new CachePage(); entry->value = new_page; } return reinterpret_cast(entry->value); } // Flush from start up to and not including start + size. void Simulator::FlushOnePage(base::CustomMatcherHashMap* i_cache, intptr_t start, int size) { DCHECK_LE(size, CachePage::kPageSize); DCHECK(AllOnOnePage(start, size - 1)); DCHECK_EQ(start & CachePage::kLineMask, 0); DCHECK_EQ(size & CachePage::kLineMask, 0); void* page = reinterpret_cast(start & (~CachePage::kPageMask)); int offset = (start & CachePage::kPageMask); CachePage* cache_page = GetCachePage(i_cache, page); char* valid_bytemap = cache_page->ValidityByte(offset); memset(valid_bytemap, CachePage::LINE_INVALID, size >> CachePage::kLineShift); } void Simulator::CheckICache(base::CustomMatcherHashMap* i_cache, Instruction* instr) { intptr_t address = reinterpret_cast(instr); void* page = reinterpret_cast(address & (~CachePage::kPageMask)); void* line = reinterpret_cast(address & (~CachePage::kLineMask)); int offset = (address & CachePage::kPageMask); CachePage* cache_page = GetCachePage(i_cache, page); char* cache_valid_byte = cache_page->ValidityByte(offset); bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID); char* cached_line = cache_page->CachedData(offset & ~CachePage::kLineMask); if (cache_hit) { // Check that the data in memory matches the contents of the I-cache. CHECK_EQ(0, memcmp(reinterpret_cast(instr), cache_page->CachedData(offset), kInstrSize)); } else { // Cache miss. Load memory into the cache. memcpy(cached_line, line, CachePage::kLineLength); *cache_valid_byte = CachePage::LINE_VALID; } } Simulator::Simulator(Isolate* isolate) : isolate_(isolate) { // Set up simulator support first. Some of this information is needed to // setup the architecture state. stack_ = reinterpret_cast(malloc(stack_size_)); pc_modified_ = false; icount_ = 0; break_count_ = 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 < kNumSimuRegisters; i++) { registers_[i] = 0; } for (int i = 0; i < kNumFPURegisters; i++) { FPUregisters_[2 * i] = 0; FPUregisters_[2 * i + 1] = 0; // upper part for MSA ASE } if (IsMipsArchVariant(kMips32r6)) { FCSR_ = kFCSRNaN2008FlagMask; MSACSR_ = 0; } else { DCHECK(IsMipsArchVariant(kMips32r1) || IsMipsArchVariant(kMips32r2)); FCSR_ = 0; } // The sp is initialized to point to the bottom (high address) of the // allocated stack area. To be safe in potential stack underflows we leave // some buffer below. registers_[sp] = reinterpret_cast(stack_) + stack_size_ - 64; // The ra and pc are initialized to a known bad value that will cause an // access violation if the simulator ever tries to execute it. registers_[pc] = bad_ra; registers_[ra] = bad_ra; last_debugger_input_ = nullptr; } Simulator::~Simulator() { free(stack_); } // Get the active Simulator for the current thread. Simulator* Simulator::current(Isolate* isolate) { v8::internal::Isolate::PerIsolateThreadData* isolate_data = isolate->FindOrAllocatePerThreadDataForThisThread(); DCHECK_NOT_NULL(isolate_data); Simulator* sim = isolate_data->simulator(); if (sim == nullptr) { // TODO(146): delete the simulator object when a thread/isolate goes away. sim = new Simulator(isolate); isolate_data->set_simulator(sim); } return sim; } // Sets the register in the architecture state. It will also deal with updating // Simulator internal state for special registers such as PC. void Simulator::set_register(int reg, int32_t value) { DCHECK((reg >= 0) && (reg < kNumSimuRegisters)); if (reg == pc) { pc_modified_ = true; } // Zero register always holds 0. registers_[reg] = (reg == 0) ? 0 : value; } void Simulator::set_dw_register(int reg, const int* dbl) { DCHECK((reg >= 0) && (reg < kNumSimuRegisters)); registers_[reg] = dbl[0]; registers_[reg + 1] = dbl[1]; } void Simulator::set_fpu_register(int fpureg, int64_t value) { DCHECK(IsFp64Mode()); DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); FPUregisters_[fpureg * 2] = value; } void Simulator::set_fpu_register_word(int fpureg, int32_t value) { // Set ONLY lower 32-bits, leaving upper bits untouched. // TODO(plind): big endian issue. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* pword = reinterpret_cast(&FPUregisters_[fpureg * 2]); *pword = value; } void Simulator::set_fpu_register_hi_word(int fpureg, int32_t value) { // Set ONLY upper 32-bits, leaving lower bits untouched. // TODO(plind): big endian issue. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* phiword = (reinterpret_cast(&FPUregisters_[fpureg * 2])) + 1; *phiword = value; } void Simulator::set_fpu_register_float(int fpureg, float value) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg * 2]) = value; } void Simulator::set_fpu_register_double(int fpureg, double value) { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg * 2]) = value; } else { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); int64_t i64 = bit_cast(value); set_fpu_register_word(fpureg, i64 & 0xFFFFFFFF); set_fpu_register_word(fpureg + 1, i64 >> 32); } } // Get the register from the architecture state. This function does handle // the special case of accessing the PC register. int32_t Simulator::get_register(int reg) const { DCHECK((reg >= 0) && (reg < kNumSimuRegisters)); if (reg == 0) return 0; else return registers_[reg] + ((reg == pc) ? Instruction::kPCReadOffset : 0); } double Simulator::get_double_from_register_pair(int reg) { // TODO(plind): bad ABI stuff, refactor or remove. DCHECK((reg >= 0) && (reg < 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. char buffer[2 * sizeof(registers_[0])]; memcpy(buffer, ®isters_[reg], 2 * sizeof(registers_[0])); memcpy(&dm_val, buffer, 2 * sizeof(registers_[0])); return(dm_val); } int64_t Simulator::get_fpu_register(int fpureg) const { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return FPUregisters_[fpureg * 2]; } else { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); uint64_t i64; i64 = static_cast(get_fpu_register_word(fpureg)); i64 |= static_cast(get_fpu_register_word(fpureg + 1)) << 32; return static_cast(i64); } } int32_t Simulator::get_fpu_register_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg * 2] & 0xFFFFFFFF); } int32_t Simulator::get_fpu_register_signed_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg * 2] & 0xFFFFFFFF); } int32_t Simulator::get_fpu_register_hi_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast((FPUregisters_[fpureg * 2] >> 32) & 0xFFFFFFFF); } float Simulator::get_fpu_register_float(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(const_cast(&FPUregisters_[fpureg * 2])); } double Simulator::get_fpu_register_double(int fpureg) const { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(&FPUregisters_[fpureg * 2]); } else { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); int64_t i64; i64 = static_cast(get_fpu_register_word(fpureg)); i64 |= static_cast(get_fpu_register_word(fpureg + 1)) << 32; return bit_cast(i64); } } template void Simulator::get_msa_register(int wreg, T* value) { DCHECK((wreg >= 0) && (wreg < kNumMSARegisters)); memcpy(value, FPUregisters_ + wreg * 2, kSimd128Size); } template void Simulator::set_msa_register(int wreg, const T* value) { DCHECK((wreg >= 0) && (wreg < kNumMSARegisters)); memcpy(FPUregisters_ + wreg * 2, value, kSimd128Size); } // 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) { if (!IsMipsSoftFloatABI) { *x = get_fpu_register_double(12); *y = get_fpu_register_double(14); *z = get_register(a2); } else { // TODO(plind): bad ABI stuff, refactor or remove. // We use a char buffer to get around the strict-aliasing rules which // otherwise allow the compiler to optimize away the copy. char buffer[sizeof(*x)]; int32_t* reg_buffer = reinterpret_cast(buffer); // Registers a0 and a1 -> x. reg_buffer[0] = get_register(a0); reg_buffer[1] = get_register(a1); memcpy(x, buffer, sizeof(buffer)); // Registers a2 and a3 -> y. reg_buffer[0] = get_register(a2); reg_buffer[1] = get_register(a3); memcpy(y, buffer, sizeof(buffer)); // Register 2 -> z. reg_buffer[0] = get_register(a2); memcpy(z, buffer, sizeof(*z)); } } // The return value is either in v0/v1 or f0. void Simulator::SetFpResult(const double& result) { if (!IsMipsSoftFloatABI) { set_fpu_register_double(0, result); } else { char buffer[2 * sizeof(registers_[0])]; int32_t* reg_buffer = reinterpret_cast(buffer); memcpy(buffer, &result, sizeof(buffer)); // Copy result to v0 and v1. set_register(v0, reg_buffer[0]); set_register(v1, reg_buffer[1]); } } // Helper functions for setting and testing the FCSR register's bits. void Simulator::set_fcsr_bit(uint32_t cc, bool value) { if (value) { FCSR_ |= (1 << cc); } else { FCSR_ &= ~(1 << cc); } } bool Simulator::test_fcsr_bit(uint32_t cc) { return FCSR_ & (1 << cc); } void Simulator::set_fcsr_rounding_mode(FPURoundingMode mode) { FCSR_ |= mode & kFPURoundingModeMask; } void Simulator::set_msacsr_rounding_mode(FPURoundingMode mode) { MSACSR_ |= mode & kFPURoundingModeMask; } unsigned int Simulator::get_fcsr_rounding_mode() { return FCSR_ & kFPURoundingModeMask; } unsigned int Simulator::get_msacsr_rounding_mode() { return MSACSR_ & kFPURoundingModeMask; } void Simulator::set_fpu_register_word_invalid_result(float original, float rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register_word(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result(float original, float rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result64(float original, float rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded >= max_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResult); } else if (rounded < min_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPU64InvalidResult); } } void Simulator::set_fpu_register_word_invalid_result(double original, double rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register_word(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result(double original, double rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result64(double original, double rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded >= max_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResult); } else if (rounded < min_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPU64InvalidResult); } } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round_error(double original, double rounded) { bool ret = false; double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded > max_int32 || rounded < min_int32) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round64_error(double original, double rounded) { bool ret = false; // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded >= max_int64 || rounded < min_int64) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round_error(float original, float rounded) { bool ret = false; double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < FLT_MIN && rounded > -FLT_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded > max_int32 || rounded < min_int32) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round64_error(float original, float rounded) { bool ret = false; // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < FLT_MIN && rounded > -FLT_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded >= max_int64 || rounded < min_int64) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } void Simulator::round_according_to_fcsr(double toRound, double& rounded, int32_t& rounded_int, double fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RD (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (get_fcsr_rounding_mode()) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } void Simulator::round_according_to_fcsr(float toRound, float& rounded, int32_t& rounded_int, float fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RD (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (get_fcsr_rounding_mode()) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.f; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } template void Simulator::round_according_to_msacsr(T_fp toRound, T_fp& rounded, T_int& rounded_int) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RD (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (get_msacsr_rounding_mode()) { case kRoundToNearest: rounded = std::floor(toRound + 0.5); rounded_int = static_cast(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--; rounded -= 1; } break; case kRoundToZero: rounded = trunc(toRound); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(toRound); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(toRound); rounded_int = static_cast(rounded); break; } } void Simulator::round64_according_to_fcsr(double toRound, double& rounded, int64_t& rounded_int, double fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or. // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward +infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RN (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (FCSR_ & 3) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } void Simulator::round64_according_to_fcsr(float toRound, float& rounded, int64_t& rounded_int, float fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or. // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward +infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RN (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (FCSR_ & 3) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.f; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } // 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. void Simulator::TraceRegWr(int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int32_t fmt_int32; float fmt_float; } v; v.fmt_int32 = value; switch (t) { case WORD: SNPrintF(trace_buf_, "%08" PRIx32 " (%" PRIu64 ") int32:%" PRId32 " uint32:%" PRIu32, value, icount_, value, value); break; case FLOAT: SNPrintF(trace_buf_, "%08" PRIx32 " (%" PRIu64 ") flt:%e", v.fmt_int32, icount_, v.fmt_float); break; default: UNREACHABLE(); } } } void Simulator::TraceRegWr(int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int64_t fmt_int64; double fmt_double; } v; v.fmt_int64 = value; switch (t) { case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " (%" PRIu64 ") int64:%" PRId64 " uint64:%" PRIu64, value, icount_, value, value); break; case DOUBLE: SNPrintF(trace_buf_, "%016" PRIx64 " (%" PRIu64 ") dbl:%e", v.fmt_int64, icount_, v.fmt_double); break; default: UNREACHABLE(); } } } template void Simulator::TraceMSARegWr(T* value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { uint8_t b[16]; uint16_t h[8]; uint32_t w[4]; uint64_t d[2]; float f[4]; double df[2]; } v; memcpy(v.b, value, kSimd128Size); switch (t) { case BYTE: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); break; case HALF: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); break; case WORD: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") int32[0..3]:%" PRId32 " %" PRId32 " %" PRId32 " %" PRId32, v.d[0], v.d[1], icount_, v.w[0], v.w[1], v.w[2], v.w[3]); break; case DWORD: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); break; case FLOAT: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") flt[0..3]:%e %e %e %e", v.d[0], v.d[1], icount_, v.f[0], v.f[1], v.f[2], v.f[3]); break; case DOUBLE: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") dbl[0..1]:%e %e", v.d[0], v.d[1], icount_, v.df[0], v.df[1]); break; default: UNREACHABLE(); } } } template void Simulator::TraceMSARegWr(T* value) { if (::v8::internal::FLAG_trace_sim) { union { uint8_t b[kMSALanesByte]; uint16_t h[kMSALanesHalf]; uint32_t w[kMSALanesWord]; uint64_t d[kMSALanesDword]; float f[kMSALanesWord]; double df[kMSALanesDword]; } v; memcpy(v.b, value, kMSALanesByte); if (std::is_same::value) { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") int32[0..3]:%" PRId32 " %" PRId32 " %" PRId32 " %" PRId32, v.d[0], v.d[1], icount_, v.w[0], v.w[1], v.w[2], v.w[3]); } else if (std::is_same::value) { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") flt[0..3]:%e %e %e %e", v.d[0], v.d[1], icount_, v.f[0], v.f[1], v.f[2], v.f[3]); } else if (std::is_same::value) { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") dbl[0..1]:%e %e", v.d[0], v.d[1], icount_, v.df[0], v.df[1]); } else { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); } } } // TODO(plind): consider making icount_ printing a flag option. void Simulator::TraceMemRd(int32_t addr, int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int32_t fmt_int32; float fmt_float; } v; v.fmt_int32 = value; switch (t) { case WORD: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") int32:%" PRId32 " uint32:%" PRIu32, value, addr, icount_, value, value); break; case FLOAT: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") flt:%e", v.fmt_int32, addr, icount_, v.fmt_float); break; default: UNREACHABLE(); } } } void Simulator::TraceMemWr(int32_t addr, int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case BYTE: SNPrintF(trace_buf_, " %02" PRIx8 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case HALF: SNPrintF(trace_buf_, " %04" PRIx16 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case WORD: SNPrintF(trace_buf_, "%08" PRIx32 " --> [%08" PRIx32 "] (%" PRIu64 ")", value, addr, icount_); break; default: UNREACHABLE(); } } } template void Simulator::TraceMemRd(int32_t addr, T value) { if (::v8::internal::FLAG_trace_sim) { switch (sizeof(T)) { case 1: SNPrintF(trace_buf_, "%08" PRIx8 " <-- [%08" PRIx32 "] (%" PRIu64 ") int8:%" PRId8 " uint8:%" PRIu8, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; case 2: SNPrintF(trace_buf_, "%08" PRIx16 " <-- [%08" PRIx32 "] (%" PRIu64 ") int16:%" PRId16 " uint16:%" PRIu16, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; case 4: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") int32:%" PRId32 " uint32:%" PRIu32, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; case 8: SNPrintF(trace_buf_, "%08" PRIx64 " <-- [%08" PRIx32 "] (%" PRIu64 ") int64:%" PRId64 " uint64:%" PRIu64, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; default: UNREACHABLE(); } } } template void Simulator::TraceMemWr(int32_t addr, T value) { if (::v8::internal::FLAG_trace_sim) { switch (sizeof(T)) { case 1: SNPrintF(trace_buf_, " %02" PRIx8 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case 2: SNPrintF(trace_buf_, " %04" PRIx16 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case 4: SNPrintF(trace_buf_, "%08" PRIx32 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case 8: SNPrintF(trace_buf_, "%16" PRIx64 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; default: UNREACHABLE(); } } } void Simulator::TraceMemRd(int32_t addr, int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int64_t fmt_int64; int32_t fmt_int32[2]; float fmt_float[2]; double fmt_double; } v; v.fmt_int64 = value; switch (t) { case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " <-- [%08" PRIx32 "] (%" PRIu64 ") int64:%" PRId64 " uint64:%" PRIu64, v.fmt_int64, addr, icount_, v.fmt_int64, v.fmt_int64); break; case DOUBLE: SNPrintF(trace_buf_, "%016" PRIx64 " <-- [%08" PRIx32 "] (%" PRIu64 ") dbl:%e", v.fmt_int64, addr, icount_, v.fmt_double); break; case FLOAT_DOUBLE: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") flt:%e dbl:%e", v.fmt_int32[1], addr, icount_, v.fmt_float[1], v.fmt_double); break; default: UNREACHABLE(); } } } void Simulator::TraceMemWr(int32_t addr, int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " --> [%08" PRIx32 "] (%" PRIu64 ")", value, addr, icount_); break; default: UNREACHABLE(); } } } int Simulator::ReadW(int32_t addr, Instruction* instr, TraceType t) { if (addr >=0 && addr < 0x400) { // This has to be a nullptr-dereference, drop into debugger. PrintF("Memory read from bad address: 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { intptr_t* ptr = reinterpret_cast(addr); switch (t) { case WORD: TraceMemRd(addr, static_cast(*ptr), t); break; case FLOAT: // This TraceType is allowed but tracing for this value will be omitted. break; default: UNREACHABLE(); } return *ptr; } PrintF("Unaligned read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); return 0; } void Simulator::WriteW(int32_t addr, int value, Instruction* instr) { if (addr >= 0 && addr < 0x400) { // This has to be a nullptr-dereference, drop into debugger. PrintF("Memory write to bad address: 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { intptr_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, WORD); *ptr = value; return; } PrintF("Unaligned write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } double Simulator::ReadD(int32_t addr, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { double* ptr = reinterpret_cast(addr); return *ptr; } PrintF("Unaligned (double) read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } void Simulator::WriteD(int32_t addr, double value, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { double* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned (double) write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); } uint16_t Simulator::ReadHU(int32_t addr, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { uint16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned unsigned halfword read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } int16_t Simulator::ReadH(int32_t addr, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { int16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned signed halfword read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } void Simulator::WriteH(int32_t addr, uint16_t value, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { uint16_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, HALF); *ptr = value; return; } PrintF("Unaligned unsigned halfword write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); } void Simulator::WriteH(int32_t addr, int16_t value, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { int16_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, HALF); *ptr = value; return; } PrintF("Unaligned halfword write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); } uint32_t Simulator::ReadBU(int32_t addr) { uint8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr & 0xFF; } int32_t Simulator::ReadB(int32_t addr) { int8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } void Simulator::WriteB(int32_t addr, uint8_t value) { uint8_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, BYTE); *ptr = value; } void Simulator::WriteB(int32_t addr, int8_t value) { int8_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, BYTE); *ptr = value; } template T Simulator::ReadMem(int32_t addr, Instruction* instr) { int alignment_mask = (1 << sizeof(T)) - 1; if ((addr & alignment_mask) == 0 || IsMipsArchVariant(kMips32r6)) { T* ptr = reinterpret_cast(addr); TraceMemRd(addr, *ptr); return *ptr; } PrintF("Unaligned read of type sizeof(%d) at 0x%08x, pc=0x%08" V8PRIxPTR "\n", sizeof(T), addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } template void Simulator::WriteMem(int32_t addr, T value, Instruction* instr) { int alignment_mask = (1 << sizeof(T)) - 1; if ((addr & alignment_mask) == 0 || IsMipsArchVariant(kMips32r6)) { T* ptr = reinterpret_cast(addr); *ptr = value; TraceMemWr(addr, value); return; } PrintF("Unaligned write of type sizeof(%d) at 0x%08x, pc=0x%08" V8PRIxPTR "\n", sizeof(T), addr, reinterpret_cast(instr)); base::OS::Abort(); } // Returns the limit of the stack area to enable checking for stack overflows. uintptr_t Simulator::StackLimit(uintptr_t c_limit) const { // The simulator uses a separate JS stack. If we have exhausted the C stack, // we also drop down the JS limit to reflect the exhaustion on the JS stack. if (GetCurrentStackPosition() < c_limit) { return reinterpret_cast(get_sp()); } // Otherwise the limit is the JS stack. Leave a safety margin of 1024 bytes // to prevent overrunning the stack when pushing values. return reinterpret_cast(stack_) + 1024; } // Unsupported instructions use Format to print an error and stop execution. void Simulator::Format(Instruction* instr, const char* format) { PrintF("Simulator found unsupported instruction:\n 0x%08" PRIxPTR ": %s\n", reinterpret_cast(instr), format); UNIMPLEMENTED_MIPS(); } // Calls into the V8 runtime are based on this very simple interface. // Note: To be able to return two values from some calls the code in runtime.cc // uses the ObjectPair which is essentially two 32-bit values stuffed into a // 64-bit value. With the code below we assume that all runtime calls return // 64 bits of result. If they don't, the v1 result register contains a bogus // value, which is fine because it is caller-saved. typedef int64_t (*SimulatorRuntimeCall)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6, int32_t arg7, int32_t arg8); // These prototypes handle the four types of FP calls. typedef int64_t (*SimulatorRuntimeCompareCall)(double darg0, double darg1); typedef double (*SimulatorRuntimeFPFPCall)(double darg0, double darg1); typedef double (*SimulatorRuntimeFPCall)(double darg0); typedef double (*SimulatorRuntimeFPIntCall)(double darg0, int32_t arg0); // This signature supports direct call in to API function native callback // (refer to InvocationCallback in v8.h). typedef void (*SimulatorRuntimeDirectApiCall)(int32_t arg0); typedef void (*SimulatorRuntimeProfilingApiCall)(int32_t arg0, void* arg1); // This signature supports direct call to accessor getter callback. typedef void (*SimulatorRuntimeDirectGetterCall)(int32_t arg0, int32_t arg1); typedef void (*SimulatorRuntimeProfilingGetterCall)( int32_t arg0, int32_t arg1, void* arg2); // Software interrupt instructions are used by the simulator to call into the // C-based V8 runtime. They are also used for debugging with simulator. void Simulator::SoftwareInterrupt() { // There are several instructions that could get us here, // the break_ instruction, or several variants of traps. All // Are "SPECIAL" class opcode, and are distinuished by function. int32_t func = instr_.FunctionFieldRaw(); uint32_t code = (func == BREAK) ? instr_.Bits(25, 6) : -1; // We first check if we met a call_rt_redirected. if (instr_.InstructionBits() == rtCallRedirInstr) { Redirection* redirection = Redirection::FromInstruction(instr_.instr()); int32_t arg0 = get_register(a0); int32_t arg1 = get_register(a1); int32_t arg2 = get_register(a2); int32_t arg3 = get_register(a3); int32_t* stack_pointer = reinterpret_cast(get_register(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]; int32_t arg6 = stack_pointer[6]; int32_t arg7 = stack_pointer[7]; int32_t arg8 = stack_pointer[8]; STATIC_ASSERT(kMaxCParameters == 9); bool fp_call = (redirection->type() == ExternalReference::BUILTIN_FP_FP_CALL) || (redirection->type() == ExternalReference::BUILTIN_COMPARE_CALL) || (redirection->type() == ExternalReference::BUILTIN_FP_CALL) || (redirection->type() == ExternalReference::BUILTIN_FP_INT_CALL); if (!IsMipsSoftFloatABI) { // With the hard floating point calling convention, double // arguments are passed in FPU registers. Fetch the arguments // from there and call the builtin using soft floating point // convention. switch (redirection->type()) { case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_COMPARE_CALL: if (IsFp64Mode()) { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_hi_word(f12); arg2 = get_fpu_register_word(f14); arg3 = get_fpu_register_hi_word(f14); } else { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_word(f13); arg2 = get_fpu_register_word(f14); arg3 = get_fpu_register_word(f15); } break; case ExternalReference::BUILTIN_FP_CALL: if (IsFp64Mode()) { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_hi_word(f12); } else { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_word(f13); } break; case ExternalReference::BUILTIN_FP_INT_CALL: if (IsFp64Mode()) { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_hi_word(f12); } else { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_word(f13); } arg2 = get_register(a2); break; default: break; } } // This is dodgy but it works because the C entry stubs are never moved. // See comment in codegen-arm.cc and bug 1242173. int32_t saved_ra = get_register(ra); intptr_t external = reinterpret_cast(redirection->external_function()); // Based on CpuFeatures::IsSupported(FPU), Mips will use either hardware // FPU, or gcc soft-float routines. Hardware FPU is simulated in this // simulator. Soft-float has additional abstraction of ExternalReference, // to support serialization. if (fp_call) { double dval0, dval1; // one or two double parameters int32_t ival; // zero or one integer parameters int64_t iresult = 0; // integer return value double dresult = 0; // double return value GetFpArgs(&dval0, &dval1, &ival); SimulatorRuntimeCall generic_target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { switch (redirection->type()) { case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_COMPARE_CALL: PrintF("Call to host function at %p with args %f, %f", reinterpret_cast(FUNCTION_ADDR(generic_target)), dval0, dval1); break; case ExternalReference::BUILTIN_FP_CALL: PrintF("Call to host function at %p with arg %f", reinterpret_cast(FUNCTION_ADDR(generic_target)), dval0); break; case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Call to host function at %p with args %f, %d", reinterpret_cast(FUNCTION_ADDR(generic_target)), dval0, ival); break; default: UNREACHABLE(); break; } } switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: { SimulatorRuntimeCompareCall target = reinterpret_cast(external); iresult = target(dval0, dval1); set_register(v0, static_cast(iresult)); set_register(v1, static_cast(iresult >> 32)); break; } case ExternalReference::BUILTIN_FP_FP_CALL: { SimulatorRuntimeFPFPCall target = reinterpret_cast(external); dresult = target(dval0, dval1); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_CALL: { SimulatorRuntimeFPCall target = reinterpret_cast(external); dresult = target(dval0); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_INT_CALL: { SimulatorRuntimeFPIntCall target = reinterpret_cast(external); dresult = target(dval0, ival); SetFpResult(dresult); break; } default: UNREACHABLE(); break; } if (::v8::internal::FLAG_trace_sim) { switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: PrintF("Returned %08x\n", static_cast(iresult)); break; case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_FP_CALL: case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Returned %f\n", dresult); break; default: UNREACHABLE(); break; } } } else if (redirection->type() == ExternalReference::DIRECT_API_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x\n", reinterpret_cast(external), arg0); } SimulatorRuntimeDirectApiCall target = reinterpret_cast(external); target(arg0); } else if ( redirection->type() == ExternalReference::PROFILING_API_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x %08x\n", reinterpret_cast(external), arg0, arg1); } SimulatorRuntimeProfilingApiCall target = reinterpret_cast(external); target(arg0, Redirection::ReverseRedirection(arg1)); } else if ( redirection->type() == ExternalReference::DIRECT_GETTER_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x %08x\n", reinterpret_cast(external), arg0, arg1); } SimulatorRuntimeDirectGetterCall target = reinterpret_cast(external); target(arg0, arg1); } else if ( redirection->type() == ExternalReference::PROFILING_GETTER_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x %08x %08x\n", reinterpret_cast(external), arg0, arg1, arg2); } SimulatorRuntimeProfilingGetterCall target = reinterpret_cast(external); target(arg0, arg1, Redirection::ReverseRedirection(arg2)); } else { DCHECK(redirection->type() == ExternalReference::BUILTIN_CALL || redirection->type() == ExternalReference::BUILTIN_CALL_PAIR); SimulatorRuntimeCall target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { PrintF( "Call to host function at %p " "args %08x, %08x, %08x, %08x, %08x, %08x, %08x, %08x, %08x\n", reinterpret_cast(FUNCTION_ADDR(target)), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); set_register(v0, static_cast(result)); set_register(v1, static_cast(result >> 32)); } if (::v8::internal::FLAG_trace_sim) { PrintF("Returned %08x : %08x\n", get_register(v1), get_register(v0)); } set_register(ra, saved_ra); set_pc(get_register(ra)); } else if (func == BREAK && code <= kMaxStopCode) { if (IsWatchpoint(code)) { PrintWatchpoint(code); } else { IncreaseStopCounter(code); HandleStop(code, instr_.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: %" PRIu64 ") ----------" "----------------------------------", code, break_count_, icount_); dbg.PrintAllRegs(); // Print registers and continue running. } void Simulator::HandleStop(uint32_t code, Instruction* instr) { // Stop if it is enabled, otherwise go on jumping over the stop // and the message address. if (IsEnabledStop(code)) { MipsDebugger dbg(this); dbg.Stop(instr); } } bool Simulator::IsStopInstruction(Instruction* instr) { int32_t func = instr->FunctionFieldRaw(); uint32_t code = static_cast(instr->Bits(25, 6)); return (func == BREAK) && code > kMaxWatchpointCode && code <= kMaxStopCode; } bool Simulator::IsEnabledStop(uint32_t code) { DCHECK_LE(code, kMaxStopCode); DCHECK_GT(code, kMaxWatchpointCode); return !(watched_stops_[code].count & kStopDisabledBit); } void Simulator::EnableStop(uint32_t code) { if (!IsEnabledStop(code)) { watched_stops_[code].count &= ~kStopDisabledBit; } } void Simulator::DisableStop(uint32_t code) { if (IsEnabledStop(code)) { watched_stops_[code].count |= kStopDisabledBit; } } void Simulator::IncreaseStopCounter(uint32_t code) { DCHECK_LE(code, kMaxStopCode); if ((watched_stops_[code].count & ~(1 << 31)) == 0x7FFFFFFF) { PrintF("Stop counter for code %i has overflowed.\n" "Enabling this code and reseting the counter to 0.\n", code); watched_stops_[code].count = 0; EnableStop(code); } else { watched_stops_[code].count++; } } // Print a stop status. void Simulator::PrintStopInfo(uint32_t code) { if (code <= kMaxWatchpointCode) { PrintF("That is a watchpoint, not a stop.\n"); return; } else if (code > kMaxStopCode) { PrintF("Code too large, only %u stops can be used\n", kMaxStopCode + 1); return; } const char* state = IsEnabledStop(code) ? "Enabled" : "Disabled"; int32_t count = watched_stops_[code].count & ~kStopDisabledBit; // Don't print the state of unused breakpoints. if (count != 0) { if (watched_stops_[code].desc) { PrintF("stop %i - 0x%x: \t%s, \tcounter = %i, \t%s\n", code, code, state, count, watched_stops_[code].desc); } else { PrintF("stop %i - 0x%x: \t%s, \tcounter = %i\n", code, code, state, count); } } } void Simulator::SignalException(Exception e) { FATAL("Error: Exception %i raised.", static_cast(e)); } // Min/Max template functions for Double and Single arguments. template static T FPAbs(T a); template <> double FPAbs(double a) { return fabs(a); } template <> float FPAbs(float a) { return fabsf(a); } template static bool FPUProcessNaNsAndZeros(T a, T b, MaxMinKind kind, T& result) { if (std::isnan(a) && std::isnan(b)) { result = a; } else if (std::isnan(a)) { result = b; } else if (std::isnan(b)) { result = a; } else if (b == a) { // Handle -0.0 == 0.0 case. // std::signbit() returns int 0 or 1 so subtracting MaxMinKind::kMax // negates the result. result = std::signbit(b) - static_cast(kind) ? b : a; } else { return false; } return true; } template static T FPUMin(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { return result; } else { return b < a ? b : a; } } template static T FPUMax(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMax, result)) { return result; } else { return b > a ? b : a; } } template static T FPUMinA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) < FPAbs(b)) { result = a; } else if (FPAbs(b) < FPAbs(a)) { result = b; } else { result = a < b ? a : b; } } return result; } template static T FPUMaxA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) > FPAbs(b)) { result = a; } else if (FPAbs(b) > FPAbs(a)) { result = b; } else { result = a > b ? a : b; } } return result; } enum class KeepSign : bool { no = false, yes }; template ::value, int>::type = 0> T FPUCanonalizeNaNArg(T result, T arg, KeepSign keepSign = KeepSign::no) { DCHECK(std::isnan(arg)); T qNaN = std::numeric_limits::quiet_NaN(); if (keepSign == KeepSign::yes) { return std::copysign(qNaN, result); } return qNaN; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return result; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first, Args... args) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return FPUCanonalizeNaNArgs(result, keepSign, args...); } template T FPUCanonalizeOperation(Func f, T first, Args... args) { return FPUCanonalizeOperation(f, KeepSign::no, first, args...); } template T FPUCanonalizeOperation(Func f, KeepSign keepSign, T first, Args... args) { T result = f(first, args...); if (std::isnan(result)) { result = FPUCanonalizeNaNArgs(result, keepSign, first, args...); } return result; } // Handle execution based on instruction types. void Simulator::DecodeTypeRegisterDRsType() { double ft, fs, fd; uint32_t cc, fcsr_cc; int64_t i64; fs = get_fpu_register_double(fs_reg()); ft = (instr_.FunctionFieldRaw() != MOVF) ? get_fpu_register_double(ft_reg()) : 0.0; fd = get_fpu_register_double(fd_reg()); int64_t ft_int = bit_cast(ft); int64_t fd_int = bit_cast(fd); cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(IsMipsArchVariant(kMips32r6)); double result, temp, temp_result; double upper = std::ceil(fs); double lower = std::floor(fs); switch (get_fcsr_rounding_mode()) { case kRoundToNearest: if (upper - fs < fs - lower) { result = upper; } else if (upper - fs > fs - lower) { result = lower; } else { temp_result = upper / 2; double reminder = modf(temp_result, &temp); if (reminder == 0) { result = upper; } else { result = lower; } } break; case kRoundToZero: result = (fs > 0 ? lower : upper); break; case kRoundToPlusInf: result = upper; break; case kRoundToMinusInf: result = lower; break; } SetFPUDoubleResult(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), (ft_int & 0x1) == 0 ? fs : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), (ft_int & 0x1) != 0 ? fs : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { SetFPUDoubleResult(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); int32_t rt_reg = instr_.RtValue(); int32_t rt = get_register(rt_reg); if (rt != 0) { SetFPUDoubleResult(fd_reg(), fs); } break; } case MOVF: { // Same function field for MOVT.D and MOVF.D uint32_t ft_cc = (ft_reg() >> 2) & 0x7; ft_cc = get_fcsr_condition_bit(ft_cc); if (instr_.Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) SetFPUDoubleResult(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) SetFPUDoubleResult(fd_reg(), fs); } break; } case MIN: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMin(ft, fs)); break; case MAX: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMax(ft, fs)); break; case MINA: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMinA(ft, fs)); break; case MAXA: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMaxA(ft, fs)); break; case ADD_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_D: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), std::fma(fs, ft, fd)); break; case MSUBF_D: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), std::fma(-fs, ft, fd)); break; case MUL_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation([](double fs) { return FPAbs(fs); }, fs)); break; case MOV_D: SetFPUDoubleResult(fd_reg(), fs); break; case NEG_D: SetFPUDoubleResult(fd_reg(), FPUCanonalizeOperation([](double src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation([](double fs) { return std::sqrt(fs); }, fs)); break; case RSQRT_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double fs) { return 1.0 / std::sqrt(fs); }, fs)); break; case RECIP_D: SetFPUDoubleResult(fd_reg(), FPUCanonalizeOperation( [](double fs) { return 1.0 / fs; }, fs)); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case CVT_W_D: { // Convert double to word. double rounded; int32_t result; round_according_to_fcsr(fs, rounded, result, fs); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case ROUND_W_D: // Round double to word (round half to even). { double rounded = std::floor(fs + 0.5); int32_t result = static_cast(rounded); if ((result & 1) != 0 && result - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. result--; } SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case TRUNC_W_D: // Truncate double to word (round towards 0). { double rounded = trunc(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case FLOOR_W_D: // Round double to word towards negative infinity. { double rounded = std::floor(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case CEIL_W_D: // Round double to word towards positive infinity. { double rounded = std::ceil(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case CVT_S_D: // Convert double to float (single). SetFPUFloatResult(fd_reg(), static_cast(fs)); break; case CVT_L_D: { // Mips32r2: Truncate double to 64-bit long-word. if (IsFp64Mode()) { int64_t result; double rounded; round64_according_to_fcsr(fs, rounded, result, fs); SetFPUResult(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; break; } case TRUNC_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = trunc(fs); i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case ROUND_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = std::floor(fs + 0.5); int64_t result = static_cast(rounded); if ((result & 1) != 0 && result - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. result--; } int64_t i64 = static_cast(result); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case FLOOR_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = std::floor(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CEIL_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = std::ceil(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CLASS_D: { // Mips32r6 instruction // Convert double input to uint64_t for easier bit manipulation uint64_t classed = bit_cast(fs); // Extracting sign, exponent and mantissa from the input double uint32_t sign = (classed >> 63) & 1; uint32_t exponent = (classed >> 52) & 0x00000000000007FF; uint64_t mantissa = classed & 0x000FFFFFFFFFFFFF; uint64_t result; double dResult; // Setting flags if input double is negative infinity, // positive infinity, negative zero or positive zero bool negInf = (classed == 0xFFF0000000000000); bool posInf = (classed == 0x7FF0000000000000); bool negZero = (classed == 0x8000000000000000); bool posZero = (classed == 0x0000000000000000); bool signalingNan; bool quietNan; bool negSubnorm; bool posSubnorm; bool negNorm; bool posNorm; // Setting flags if double is NaN signalingNan = false; quietNan = false; if (!negInf && !posInf && exponent == 0x7FF) { quietNan = ((mantissa & 0x0008000000000000) != 0) && ((mantissa & (0x0008000000000000 - 1)) == 0); signalingNan = !quietNan; } // Setting flags if double is subnormal number posSubnorm = false; negSubnorm = false; if ((exponent == 0) && (mantissa != 0)) { DCHECK(sign == 0 || sign == 1); posSubnorm = (sign == 0); negSubnorm = (sign == 1); } // Setting flags if double is normal number posNorm = false; negNorm = false; if (!posSubnorm && !negSubnorm && !posInf && !negInf && !signalingNan && !quietNan && !negZero && !posZero) { DCHECK(sign == 0 || sign == 1); posNorm = (sign == 0); negNorm = (sign == 1); } // Calculating result according to description of CLASS.D instruction result = (posZero << 9) | (posSubnorm << 8) | (posNorm << 7) | (posInf << 6) | (negZero << 5) | (negSubnorm << 4) | (negNorm << 3) | (negInf << 2) | (quietNan << 1) | signalingNan; DCHECK_NE(result, 0); dResult = bit_cast(result); SetFPUDoubleResult(fd_reg(), dResult); break; } case C_F_D: { set_fcsr_bit(fcsr_cc, false); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; } default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterWRsType() { float fs = get_fpu_register_float(fs_reg()); float ft = get_fpu_register_float(ft_reg()); int32_t alu_out = 0x12345678; switch (instr_.FunctionFieldRaw()) { case CVT_S_W: // Convert word to float (single). alu_out = get_fpu_register_signed_word(fs_reg()); SetFPUFloatResult(fd_reg(), static_cast(alu_out)); break; case CVT_D_W: // Convert word to double. alu_out = get_fpu_register_signed_word(fs_reg()); SetFPUDoubleResult(fd_reg(), static_cast(alu_out)); break; case CMP_AF: SetFPUWordResult(fd_reg(), 0); break; case CMP_UN: if (std::isnan(fs) || std::isnan(ft)) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_EQ: if (fs == ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_UEQ: if ((fs == ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_LT: if (fs < ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_ULT: if ((fs < ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_LE: if (fs <= ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_ULE: if ((fs <= ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_OR: if (!std::isnan(fs) && !std::isnan(ft)) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_UNE: if ((fs != ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_NE: if (fs != ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterSRsType() { float fs, ft, fd; fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); fd = get_fpu_register_float(fd_reg()); int32_t ft_int = bit_cast(ft); int32_t fd_int = bit_cast(fd); uint32_t cc, fcsr_cc; cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(IsMipsArchVariant(kMips32r6)); float result, temp_result; double temp; float upper = std::ceil(fs); float lower = std::floor(fs); switch (get_fcsr_rounding_mode()) { case kRoundToNearest: if (upper - fs < fs - lower) { result = upper; } else if (upper - fs > fs - lower) { result = lower; } else { temp_result = upper / 2; float reminder = modf(temp_result, &temp); if (reminder == 0) { result = upper; } else { result = lower; } } break; case kRoundToZero: result = (fs > 0 ? lower : upper); break; case kRoundToPlusInf: result = upper; break; case kRoundToMinusInf: result = lower; break; } SetFPUFloatResult(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case ADD_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_S: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult(fd_reg(), std::fma(fs, ft, fd)); break; case MSUBF_S: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult(fd_reg(), std::fma(-fs, ft, fd)); break; case MUL_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_S: SetFPUFloatResult(fd_reg(), FPUCanonalizeOperation( [](float fs) { return FPAbs(fs); }, fs)); break; case MOV_S: SetFPUFloatResult(fd_reg(), fs); break; case NEG_S: SetFPUFloatResult(fd_reg(), FPUCanonalizeOperation([](float src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float src) { return std::sqrt(src); }, fs)); break; case RSQRT_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation( [](float src) { return 1.0 / std::sqrt(src); }, fs)); break; case RECIP_S: SetFPUFloatResult(fd_reg(), FPUCanonalizeOperation( [](float src) { return 1.0 / src; }, fs)); break; case C_F_D: set_fcsr_bit(fcsr_cc, false); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case CVT_D_S: SetFPUDoubleResult(fd_reg(), static_cast(fs)); break; case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case CLASS_S: { // Mips32r6 instruction // Convert float input to uint32_t for easier bit manipulation float fs = get_fpu_register_float(fs_reg()); uint32_t classed = bit_cast(fs); // Extracting sign, exponent and mantissa from the input float uint32_t sign = (classed >> 31) & 1; uint32_t exponent = (classed >> 23) & 0x000000FF; uint32_t mantissa = classed & 0x007FFFFF; uint32_t result; float fResult; // Setting flags if input float is negative infinity, // positive infinity, negative zero or positive zero bool negInf = (classed == 0xFF800000); bool posInf = (classed == 0x7F800000); bool negZero = (classed == 0x80000000); bool posZero = (classed == 0x00000000); bool signalingNan; bool quietNan; bool negSubnorm; bool posSubnorm; bool negNorm; bool posNorm; // Setting flags if float is NaN signalingNan = false; quietNan = false; if (!negInf && !posInf && (exponent == 0xFF)) { quietNan = ((mantissa & 0x00200000) == 0) && ((mantissa & (0x00200000 - 1)) == 0); signalingNan = !quietNan; } // Setting flags if float is subnormal number posSubnorm = false; negSubnorm = false; if ((exponent == 0) && (mantissa != 0)) { DCHECK(sign == 0 || sign == 1); posSubnorm = (sign == 0); negSubnorm = (sign == 1); } // Setting flags if float is normal number posNorm = false; negNorm = false; if (!posSubnorm && !negSubnorm && !posInf && !negInf && !signalingNan && !quietNan && !negZero && !posZero) { DCHECK(sign == 0 || sign == 1); posNorm = (sign == 0); negNorm = (sign == 1); } // Calculating result according to description of CLASS.S instruction result = (posZero << 9) | (posSubnorm << 8) | (posNorm << 7) | (posInf << 6) | (negZero << 5) | (negSubnorm << 4) | (negNorm << 3) | (negInf << 2) | (quietNan << 1) | signalingNan; DCHECK_NE(result, 0); fResult = bit_cast(result); SetFPUFloatResult(fd_reg(), fResult); break; } case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult( fd_reg(), (ft_int & 0x1) == 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult( fd_reg(), (ft_int & 0x1) != 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { SetFPUFloatResult(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() != 0) { SetFPUFloatResult(fd_reg(), fs); } break; } case MOVF: { // Same function field for MOVT.D and MOVF.D uint32_t ft_cc = (ft_reg() >> 2) & 0x7; ft_cc = get_fcsr_condition_bit(ft_cc); if (instr_.Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) SetFPUFloatResult(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) SetFPUFloatResult(fd_reg(), fs); } break; } case TRUNC_W_S: { // Truncate single to word (round towards 0). float rounded = trunc(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case TRUNC_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float rounded = trunc(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case FLOOR_W_S: // Round double to word towards negative infinity. { float rounded = std::floor(fs); int32_t result = static_cast
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 } bool Simulator::ICacheMatch(void* one, void* two) { DCHECK_EQ(reinterpret_cast(one) & CachePage::kPageMask, 0); DCHECK_EQ(reinterpret_cast(two) & CachePage::kPageMask, 0); return one == two; } static uint32_t ICacheHash(void* key) { return static_cast(reinterpret_cast(key)) >> 2; } static bool AllOnOnePage(uintptr_t start, int size) { intptr_t start_page = (start & ~CachePage::kPageMask); intptr_t end_page = ((start + size) & ~CachePage::kPageMask); return start_page == end_page; } void Simulator::set_last_debugger_input(char* input) { DeleteArray(last_debugger_input_); last_debugger_input_ = input; } void Simulator::SetRedirectInstruction(Instruction* instruction) { instruction->SetInstructionBits(rtCallRedirInstr); } void Simulator::FlushICache(base::CustomMatcherHashMap* i_cache, void* start_addr, size_t size) { intptr_t start = reinterpret_cast(start_addr); int intra_line = (start & CachePage::kLineMask); start -= intra_line; size += intra_line; size = ((size - 1) | CachePage::kLineMask) + 1; int offset = (start & CachePage::kPageMask); while (!AllOnOnePage(start, size - 1)) { int bytes_to_flush = CachePage::kPageSize - offset; FlushOnePage(i_cache, start, bytes_to_flush); start += bytes_to_flush; size -= bytes_to_flush; DCHECK_EQ(0, start & CachePage::kPageMask); offset = 0; } if (size != 0) { FlushOnePage(i_cache, start, size); } } CachePage* Simulator::GetCachePage(base::CustomMatcherHashMap* i_cache, void* page) { base::CustomMatcherHashMap::Entry* entry = i_cache->LookupOrInsert(page, ICacheHash(page)); if (entry->value == nullptr) { CachePage* new_page = new CachePage(); entry->value = new_page; } return reinterpret_cast(entry->value); } // Flush from start up to and not including start + size. void Simulator::FlushOnePage(base::CustomMatcherHashMap* i_cache, intptr_t start, int size) { DCHECK_LE(size, CachePage::kPageSize); DCHECK(AllOnOnePage(start, size - 1)); DCHECK_EQ(start & CachePage::kLineMask, 0); DCHECK_EQ(size & CachePage::kLineMask, 0); void* page = reinterpret_cast(start & (~CachePage::kPageMask)); int offset = (start & CachePage::kPageMask); CachePage* cache_page = GetCachePage(i_cache, page); char* valid_bytemap = cache_page->ValidityByte(offset); memset(valid_bytemap, CachePage::LINE_INVALID, size >> CachePage::kLineShift); } void Simulator::CheckICache(base::CustomMatcherHashMap* i_cache, Instruction* instr) { intptr_t address = reinterpret_cast(instr); void* page = reinterpret_cast(address & (~CachePage::kPageMask)); void* line = reinterpret_cast(address & (~CachePage::kLineMask)); int offset = (address & CachePage::kPageMask); CachePage* cache_page = GetCachePage(i_cache, page); char* cache_valid_byte = cache_page->ValidityByte(offset); bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID); char* cached_line = cache_page->CachedData(offset & ~CachePage::kLineMask); if (cache_hit) { // Check that the data in memory matches the contents of the I-cache. CHECK_EQ(0, memcmp(reinterpret_cast(instr), cache_page->CachedData(offset), kInstrSize)); } else { // Cache miss. Load memory into the cache. memcpy(cached_line, line, CachePage::kLineLength); *cache_valid_byte = CachePage::LINE_VALID; } } Simulator::Simulator(Isolate* isolate) : isolate_(isolate) { // Set up simulator support first. Some of this information is needed to // setup the architecture state. stack_ = reinterpret_cast(malloc(stack_size_)); pc_modified_ = false; icount_ = 0; break_count_ = 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 < kNumSimuRegisters; i++) { registers_[i] = 0; } for (int i = 0; i < kNumFPURegisters; i++) { FPUregisters_[2 * i] = 0; FPUregisters_[2 * i + 1] = 0; // upper part for MSA ASE } if (IsMipsArchVariant(kMips32r6)) { FCSR_ = kFCSRNaN2008FlagMask; MSACSR_ = 0; } else { DCHECK(IsMipsArchVariant(kMips32r1) || IsMipsArchVariant(kMips32r2)); FCSR_ = 0; } // The sp is initialized to point to the bottom (high address) of the // allocated stack area. To be safe in potential stack underflows we leave // some buffer below. registers_[sp] = reinterpret_cast(stack_) + stack_size_ - 64; // The ra and pc are initialized to a known bad value that will cause an // access violation if the simulator ever tries to execute it. registers_[pc] = bad_ra; registers_[ra] = bad_ra; last_debugger_input_ = nullptr; } Simulator::~Simulator() { free(stack_); } // Get the active Simulator for the current thread. Simulator* Simulator::current(Isolate* isolate) { v8::internal::Isolate::PerIsolateThreadData* isolate_data = isolate->FindOrAllocatePerThreadDataForThisThread(); DCHECK_NOT_NULL(isolate_data); Simulator* sim = isolate_data->simulator(); if (sim == nullptr) { // TODO(146): delete the simulator object when a thread/isolate goes away. sim = new Simulator(isolate); isolate_data->set_simulator(sim); } return sim; } // Sets the register in the architecture state. It will also deal with updating // Simulator internal state for special registers such as PC. void Simulator::set_register(int reg, int32_t value) { DCHECK((reg >= 0) && (reg < kNumSimuRegisters)); if (reg == pc) { pc_modified_ = true; } // Zero register always holds 0. registers_[reg] = (reg == 0) ? 0 : value; } void Simulator::set_dw_register(int reg, const int* dbl) { DCHECK((reg >= 0) && (reg < kNumSimuRegisters)); registers_[reg] = dbl[0]; registers_[reg + 1] = dbl[1]; } void Simulator::set_fpu_register(int fpureg, int64_t value) { DCHECK(IsFp64Mode()); DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); FPUregisters_[fpureg * 2] = value; } void Simulator::set_fpu_register_word(int fpureg, int32_t value) { // Set ONLY lower 32-bits, leaving upper bits untouched. // TODO(plind): big endian issue. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* pword = reinterpret_cast(&FPUregisters_[fpureg * 2]); *pword = value; } void Simulator::set_fpu_register_hi_word(int fpureg, int32_t value) { // Set ONLY upper 32-bits, leaving lower bits untouched. // TODO(plind): big endian issue. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* phiword = (reinterpret_cast(&FPUregisters_[fpureg * 2])) + 1; *phiword = value; } void Simulator::set_fpu_register_float(int fpureg, float value) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg * 2]) = value; } void Simulator::set_fpu_register_double(int fpureg, double value) { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg * 2]) = value; } else { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); int64_t i64 = bit_cast(value); set_fpu_register_word(fpureg, i64 & 0xFFFFFFFF); set_fpu_register_word(fpureg + 1, i64 >> 32); } } // Get the register from the architecture state. This function does handle // the special case of accessing the PC register. int32_t Simulator::get_register(int reg) const { DCHECK((reg >= 0) && (reg < kNumSimuRegisters)); if (reg == 0) return 0; else return registers_[reg] + ((reg == pc) ? Instruction::kPCReadOffset : 0); } double Simulator::get_double_from_register_pair(int reg) { // TODO(plind): bad ABI stuff, refactor or remove. DCHECK((reg >= 0) && (reg < 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. char buffer[2 * sizeof(registers_[0])]; memcpy(buffer, ®isters_[reg], 2 * sizeof(registers_[0])); memcpy(&dm_val, buffer, 2 * sizeof(registers_[0])); return(dm_val); } int64_t Simulator::get_fpu_register(int fpureg) const { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return FPUregisters_[fpureg * 2]; } else { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); uint64_t i64; i64 = static_cast(get_fpu_register_word(fpureg)); i64 |= static_cast(get_fpu_register_word(fpureg + 1)) << 32; return static_cast(i64); } } int32_t Simulator::get_fpu_register_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg * 2] & 0xFFFFFFFF); } int32_t Simulator::get_fpu_register_signed_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg * 2] & 0xFFFFFFFF); } int32_t Simulator::get_fpu_register_hi_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast((FPUregisters_[fpureg * 2] >> 32) & 0xFFFFFFFF); } float Simulator::get_fpu_register_float(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(const_cast(&FPUregisters_[fpureg * 2])); } double Simulator::get_fpu_register_double(int fpureg) const { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(&FPUregisters_[fpureg * 2]); } else { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); int64_t i64; i64 = static_cast(get_fpu_register_word(fpureg)); i64 |= static_cast(get_fpu_register_word(fpureg + 1)) << 32; return bit_cast(i64); } } template void Simulator::get_msa_register(int wreg, T* value) { DCHECK((wreg >= 0) && (wreg < kNumMSARegisters)); memcpy(value, FPUregisters_ + wreg * 2, kSimd128Size); } template void Simulator::set_msa_register(int wreg, const T* value) { DCHECK((wreg >= 0) && (wreg < kNumMSARegisters)); memcpy(FPUregisters_ + wreg * 2, value, kSimd128Size); } // 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) { if (!IsMipsSoftFloatABI) { *x = get_fpu_register_double(12); *y = get_fpu_register_double(14); *z = get_register(a2); } else { // TODO(plind): bad ABI stuff, refactor or remove. // We use a char buffer to get around the strict-aliasing rules which // otherwise allow the compiler to optimize away the copy. char buffer[sizeof(*x)]; int32_t* reg_buffer = reinterpret_cast(buffer); // Registers a0 and a1 -> x. reg_buffer[0] = get_register(a0); reg_buffer[1] = get_register(a1); memcpy(x, buffer, sizeof(buffer)); // Registers a2 and a3 -> y. reg_buffer[0] = get_register(a2); reg_buffer[1] = get_register(a3); memcpy(y, buffer, sizeof(buffer)); // Register 2 -> z. reg_buffer[0] = get_register(a2); memcpy(z, buffer, sizeof(*z)); } } // The return value is either in v0/v1 or f0. void Simulator::SetFpResult(const double& result) { if (!IsMipsSoftFloatABI) { set_fpu_register_double(0, result); } else { char buffer[2 * sizeof(registers_[0])]; int32_t* reg_buffer = reinterpret_cast(buffer); memcpy(buffer, &result, sizeof(buffer)); // Copy result to v0 and v1. set_register(v0, reg_buffer[0]); set_register(v1, reg_buffer[1]); } } // Helper functions for setting and testing the FCSR register's bits. void Simulator::set_fcsr_bit(uint32_t cc, bool value) { if (value) { FCSR_ |= (1 << cc); } else { FCSR_ &= ~(1 << cc); } } bool Simulator::test_fcsr_bit(uint32_t cc) { return FCSR_ & (1 << cc); } void Simulator::set_fcsr_rounding_mode(FPURoundingMode mode) { FCSR_ |= mode & kFPURoundingModeMask; } void Simulator::set_msacsr_rounding_mode(FPURoundingMode mode) { MSACSR_ |= mode & kFPURoundingModeMask; } unsigned int Simulator::get_fcsr_rounding_mode() { return FCSR_ & kFPURoundingModeMask; } unsigned int Simulator::get_msacsr_rounding_mode() { return MSACSR_ & kFPURoundingModeMask; } void Simulator::set_fpu_register_word_invalid_result(float original, float rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register_word(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result(float original, float rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result64(float original, float rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded >= max_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResult); } else if (rounded < min_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPU64InvalidResult); } } void Simulator::set_fpu_register_word_invalid_result(double original, double rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register_word(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register_word(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register_word(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result(double original, double rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded > max_int32) { set_fpu_register(fd_reg(), kFPUInvalidResult); } else if (rounded < min_int32) { set_fpu_register(fd_reg(), kFPUInvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPUInvalidResult); } } void Simulator::set_fpu_register_invalid_result64(double original, double rounded) { if (FCSR_ & kFCSRNaN2008FlagMask) { // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (std::isnan(original)) { set_fpu_register(fd_reg(), 0); } else if (rounded >= max_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResult); } else if (rounded < min_int64) { set_fpu_register(fd_reg(), kFPU64InvalidResultNegative); } else { UNREACHABLE(); } } else { set_fpu_register(fd_reg(), kFPU64InvalidResult); } } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round_error(double original, double rounded) { bool ret = false; double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded > max_int32 || rounded < min_int32) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round64_error(double original, double rounded) { bool ret = false; // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded >= max_int64 || rounded < min_int64) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round_error(float original, float rounded) { bool ret = false; double max_int32 = std::numeric_limits::max(); double min_int32 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < FLT_MIN && rounded > -FLT_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded > max_int32 || rounded < min_int32) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round64_error(float original, float rounded) { bool ret = false; // 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 = std::numeric_limits::max(); double min_int64 = std::numeric_limits::min(); if (!std::isfinite(original) || !std::isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < FLT_MIN && rounded > -FLT_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded >= max_int64 || rounded < min_int64) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } void Simulator::round_according_to_fcsr(double toRound, double& rounded, int32_t& rounded_int, double fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RD (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (get_fcsr_rounding_mode()) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } void Simulator::round_according_to_fcsr(float toRound, float& rounded, int32_t& rounded_int, float fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RD (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (get_fcsr_rounding_mode()) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.f; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } template void Simulator::round_according_to_msacsr(T_fp toRound, T_fp& rounded, T_int& rounded_int) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RD (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (get_msacsr_rounding_mode()) { case kRoundToNearest: rounded = std::floor(toRound + 0.5); rounded_int = static_cast(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--; rounded -= 1; } break; case kRoundToZero: rounded = trunc(toRound); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(toRound); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(toRound); rounded_int = static_cast(rounded); break; } } void Simulator::round64_according_to_fcsr(double toRound, double& rounded, int64_t& rounded_int, double fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or. // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward +infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RN (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (FCSR_ & 3) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } void Simulator::round64_according_to_fcsr(float toRound, float& rounded, int64_t& rounded_int, float fs) { // 0 RN (round to nearest): Round a result to the nearest // representable value; if the result is exactly halfway between // two representable values, round to zero. Behave like round_w_d. // 1 RZ (round toward zero): Round a result to the closest // representable value whose absolute value is less than or. // equal to the infinitely accurate result. Behave like trunc_w_d. // 2 RP (round up, or toward +infinity): Round a result to the // next representable value up. Behave like ceil_w_d. // 3 RN (round down, or toward −infinity): Round a result to // the next representable value down. Behave like floor_w_d. switch (FCSR_ & 3) { case kRoundToNearest: rounded = std::floor(fs + 0.5); rounded_int = static_cast(rounded); if ((rounded_int & 1) != 0 && rounded_int - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. rounded_int--; rounded -= 1.f; } break; case kRoundToZero: rounded = trunc(fs); rounded_int = static_cast(rounded); break; case kRoundToPlusInf: rounded = std::ceil(fs); rounded_int = static_cast(rounded); break; case kRoundToMinusInf: rounded = std::floor(fs); rounded_int = static_cast(rounded); break; } } // 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. void Simulator::TraceRegWr(int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int32_t fmt_int32; float fmt_float; } v; v.fmt_int32 = value; switch (t) { case WORD: SNPrintF(trace_buf_, "%08" PRIx32 " (%" PRIu64 ") int32:%" PRId32 " uint32:%" PRIu32, value, icount_, value, value); break; case FLOAT: SNPrintF(trace_buf_, "%08" PRIx32 " (%" PRIu64 ") flt:%e", v.fmt_int32, icount_, v.fmt_float); break; default: UNREACHABLE(); } } } void Simulator::TraceRegWr(int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int64_t fmt_int64; double fmt_double; } v; v.fmt_int64 = value; switch (t) { case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " (%" PRIu64 ") int64:%" PRId64 " uint64:%" PRIu64, value, icount_, value, value); break; case DOUBLE: SNPrintF(trace_buf_, "%016" PRIx64 " (%" PRIu64 ") dbl:%e", v.fmt_int64, icount_, v.fmt_double); break; default: UNREACHABLE(); } } } template void Simulator::TraceMSARegWr(T* value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { uint8_t b[16]; uint16_t h[8]; uint32_t w[4]; uint64_t d[2]; float f[4]; double df[2]; } v; memcpy(v.b, value, kSimd128Size); switch (t) { case BYTE: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); break; case HALF: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); break; case WORD: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") int32[0..3]:%" PRId32 " %" PRId32 " %" PRId32 " %" PRId32, v.d[0], v.d[1], icount_, v.w[0], v.w[1], v.w[2], v.w[3]); break; case DWORD: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); break; case FLOAT: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") flt[0..3]:%e %e %e %e", v.d[0], v.d[1], icount_, v.f[0], v.f[1], v.f[2], v.f[3]); break; case DOUBLE: SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") dbl[0..1]:%e %e", v.d[0], v.d[1], icount_, v.df[0], v.df[1]); break; default: UNREACHABLE(); } } } template void Simulator::TraceMSARegWr(T* value) { if (::v8::internal::FLAG_trace_sim) { union { uint8_t b[kMSALanesByte]; uint16_t h[kMSALanesHalf]; uint32_t w[kMSALanesWord]; uint64_t d[kMSALanesDword]; float f[kMSALanesWord]; double df[kMSALanesDword]; } v; memcpy(v.b, value, kMSALanesByte); if (std::is_same::value) { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") int32[0..3]:%" PRId32 " %" PRId32 " %" PRId32 " %" PRId32, v.d[0], v.d[1], icount_, v.w[0], v.w[1], v.w[2], v.w[3]); } else if (std::is_same::value) { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") flt[0..3]:%e %e %e %e", v.d[0], v.d[1], icount_, v.f[0], v.f[1], v.f[2], v.f[3]); } else if (std::is_same::value) { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ") dbl[0..1]:%e %e", v.d[0], v.d[1], icount_, v.df[0], v.df[1]); } else { SNPrintF(trace_buf_, "LO: %016" PRIx64 " HI: %016" PRIx64 " (%" PRIu64 ")", v.d[0], v.d[1], icount_); } } } // TODO(plind): consider making icount_ printing a flag option. void Simulator::TraceMemRd(int32_t addr, int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int32_t fmt_int32; float fmt_float; } v; v.fmt_int32 = value; switch (t) { case WORD: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") int32:%" PRId32 " uint32:%" PRIu32, value, addr, icount_, value, value); break; case FLOAT: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") flt:%e", v.fmt_int32, addr, icount_, v.fmt_float); break; default: UNREACHABLE(); } } } void Simulator::TraceMemWr(int32_t addr, int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case BYTE: SNPrintF(trace_buf_, " %02" PRIx8 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case HALF: SNPrintF(trace_buf_, " %04" PRIx16 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case WORD: SNPrintF(trace_buf_, "%08" PRIx32 " --> [%08" PRIx32 "] (%" PRIu64 ")", value, addr, icount_); break; default: UNREACHABLE(); } } } template void Simulator::TraceMemRd(int32_t addr, T value) { if (::v8::internal::FLAG_trace_sim) { switch (sizeof(T)) { case 1: SNPrintF(trace_buf_, "%08" PRIx8 " <-- [%08" PRIx32 "] (%" PRIu64 ") int8:%" PRId8 " uint8:%" PRIu8, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; case 2: SNPrintF(trace_buf_, "%08" PRIx16 " <-- [%08" PRIx32 "] (%" PRIu64 ") int16:%" PRId16 " uint16:%" PRIu16, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; case 4: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") int32:%" PRId32 " uint32:%" PRIu32, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; case 8: SNPrintF(trace_buf_, "%08" PRIx64 " <-- [%08" PRIx32 "] (%" PRIu64 ") int64:%" PRId64 " uint64:%" PRIu64, static_cast(value), addr, icount_, static_cast(value), static_cast(value)); break; default: UNREACHABLE(); } } } template void Simulator::TraceMemWr(int32_t addr, T value) { if (::v8::internal::FLAG_trace_sim) { switch (sizeof(T)) { case 1: SNPrintF(trace_buf_, " %02" PRIx8 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case 2: SNPrintF(trace_buf_, " %04" PRIx16 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case 4: SNPrintF(trace_buf_, "%08" PRIx32 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; case 8: SNPrintF(trace_buf_, "%16" PRIx64 " --> [%08" PRIx32 "] (%" PRIu64 ")", static_cast(value), addr, icount_); break; default: UNREACHABLE(); } } } void Simulator::TraceMemRd(int32_t addr, int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int64_t fmt_int64; int32_t fmt_int32[2]; float fmt_float[2]; double fmt_double; } v; v.fmt_int64 = value; switch (t) { case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " <-- [%08" PRIx32 "] (%" PRIu64 ") int64:%" PRId64 " uint64:%" PRIu64, v.fmt_int64, addr, icount_, v.fmt_int64, v.fmt_int64); break; case DOUBLE: SNPrintF(trace_buf_, "%016" PRIx64 " <-- [%08" PRIx32 "] (%" PRIu64 ") dbl:%e", v.fmt_int64, addr, icount_, v.fmt_double); break; case FLOAT_DOUBLE: SNPrintF(trace_buf_, "%08" PRIx32 " <-- [%08" PRIx32 "] (%" PRIu64 ") flt:%e dbl:%e", v.fmt_int32[1], addr, icount_, v.fmt_float[1], v.fmt_double); break; default: UNREACHABLE(); } } } void Simulator::TraceMemWr(int32_t addr, int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " --> [%08" PRIx32 "] (%" PRIu64 ")", value, addr, icount_); break; default: UNREACHABLE(); } } } int Simulator::ReadW(int32_t addr, Instruction* instr, TraceType t) { if (addr >=0 && addr < 0x400) { // This has to be a nullptr-dereference, drop into debugger. PrintF("Memory read from bad address: 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { intptr_t* ptr = reinterpret_cast(addr); switch (t) { case WORD: TraceMemRd(addr, static_cast(*ptr), t); break; case FLOAT: // This TraceType is allowed but tracing for this value will be omitted. break; default: UNREACHABLE(); } return *ptr; } PrintF("Unaligned read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); return 0; } void Simulator::WriteW(int32_t addr, int value, Instruction* instr) { if (addr >= 0 && addr < 0x400) { // This has to be a nullptr-dereference, drop into debugger. PrintF("Memory write to bad address: 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { intptr_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, WORD); *ptr = value; return; } PrintF("Unaligned write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } double Simulator::ReadD(int32_t addr, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { double* ptr = reinterpret_cast(addr); return *ptr; } PrintF("Unaligned (double) read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } void Simulator::WriteD(int32_t addr, double value, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || IsMipsArchVariant(kMips32r6)) { double* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned (double) write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); } uint16_t Simulator::ReadHU(int32_t addr, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { uint16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned unsigned halfword read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } int16_t Simulator::ReadH(int32_t addr, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { int16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned signed halfword read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } void Simulator::WriteH(int32_t addr, uint16_t value, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { uint16_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, HALF); *ptr = value; return; } PrintF("Unaligned unsigned halfword write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); } void Simulator::WriteH(int32_t addr, int16_t value, Instruction* instr) { if ((addr & 1) == 0 || IsMipsArchVariant(kMips32r6)) { int16_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, HALF); *ptr = value; return; } PrintF("Unaligned halfword write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); } uint32_t Simulator::ReadBU(int32_t addr) { uint8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr & 0xFF; } int32_t Simulator::ReadB(int32_t addr) { int8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } void Simulator::WriteB(int32_t addr, uint8_t value) { uint8_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, BYTE); *ptr = value; } void Simulator::WriteB(int32_t addr, int8_t value) { int8_t* ptr = reinterpret_cast(addr); TraceMemWr(addr, value, BYTE); *ptr = value; } template T Simulator::ReadMem(int32_t addr, Instruction* instr) { int alignment_mask = (1 << sizeof(T)) - 1; if ((addr & alignment_mask) == 0 || IsMipsArchVariant(kMips32r6)) { T* ptr = reinterpret_cast(addr); TraceMemRd(addr, *ptr); return *ptr; } PrintF("Unaligned read of type sizeof(%d) at 0x%08x, pc=0x%08" V8PRIxPTR "\n", sizeof(T), addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } template void Simulator::WriteMem(int32_t addr, T value, Instruction* instr) { int alignment_mask = (1 << sizeof(T)) - 1; if ((addr & alignment_mask) == 0 || IsMipsArchVariant(kMips32r6)) { T* ptr = reinterpret_cast(addr); *ptr = value; TraceMemWr(addr, value); return; } PrintF("Unaligned write of type sizeof(%d) at 0x%08x, pc=0x%08" V8PRIxPTR "\n", sizeof(T), addr, reinterpret_cast(instr)); base::OS::Abort(); } // Returns the limit of the stack area to enable checking for stack overflows. uintptr_t Simulator::StackLimit(uintptr_t c_limit) const { // The simulator uses a separate JS stack. If we have exhausted the C stack, // we also drop down the JS limit to reflect the exhaustion on the JS stack. if (GetCurrentStackPosition() < c_limit) { return reinterpret_cast(get_sp()); } // Otherwise the limit is the JS stack. Leave a safety margin of 1024 bytes // to prevent overrunning the stack when pushing values. return reinterpret_cast(stack_) + 1024; } // Unsupported instructions use Format to print an error and stop execution. void Simulator::Format(Instruction* instr, const char* format) { PrintF("Simulator found unsupported instruction:\n 0x%08" PRIxPTR ": %s\n", reinterpret_cast(instr), format); UNIMPLEMENTED_MIPS(); } // Calls into the V8 runtime are based on this very simple interface. // Note: To be able to return two values from some calls the code in runtime.cc // uses the ObjectPair which is essentially two 32-bit values stuffed into a // 64-bit value. With the code below we assume that all runtime calls return // 64 bits of result. If they don't, the v1 result register contains a bogus // value, which is fine because it is caller-saved. typedef int64_t (*SimulatorRuntimeCall)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6, int32_t arg7, int32_t arg8); // These prototypes handle the four types of FP calls. typedef int64_t (*SimulatorRuntimeCompareCall)(double darg0, double darg1); typedef double (*SimulatorRuntimeFPFPCall)(double darg0, double darg1); typedef double (*SimulatorRuntimeFPCall)(double darg0); typedef double (*SimulatorRuntimeFPIntCall)(double darg0, int32_t arg0); // This signature supports direct call in to API function native callback // (refer to InvocationCallback in v8.h). typedef void (*SimulatorRuntimeDirectApiCall)(int32_t arg0); typedef void (*SimulatorRuntimeProfilingApiCall)(int32_t arg0, void* arg1); // This signature supports direct call to accessor getter callback. typedef void (*SimulatorRuntimeDirectGetterCall)(int32_t arg0, int32_t arg1); typedef void (*SimulatorRuntimeProfilingGetterCall)( int32_t arg0, int32_t arg1, void* arg2); // Software interrupt instructions are used by the simulator to call into the // C-based V8 runtime. They are also used for debugging with simulator. void Simulator::SoftwareInterrupt() { // There are several instructions that could get us here, // the break_ instruction, or several variants of traps. All // Are "SPECIAL" class opcode, and are distinuished by function. int32_t func = instr_.FunctionFieldRaw(); uint32_t code = (func == BREAK) ? instr_.Bits(25, 6) : -1; // We first check if we met a call_rt_redirected. if (instr_.InstructionBits() == rtCallRedirInstr) { Redirection* redirection = Redirection::FromInstruction(instr_.instr()); int32_t arg0 = get_register(a0); int32_t arg1 = get_register(a1); int32_t arg2 = get_register(a2); int32_t arg3 = get_register(a3); int32_t* stack_pointer = reinterpret_cast(get_register(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]; int32_t arg6 = stack_pointer[6]; int32_t arg7 = stack_pointer[7]; int32_t arg8 = stack_pointer[8]; STATIC_ASSERT(kMaxCParameters == 9); bool fp_call = (redirection->type() == ExternalReference::BUILTIN_FP_FP_CALL) || (redirection->type() == ExternalReference::BUILTIN_COMPARE_CALL) || (redirection->type() == ExternalReference::BUILTIN_FP_CALL) || (redirection->type() == ExternalReference::BUILTIN_FP_INT_CALL); if (!IsMipsSoftFloatABI) { // With the hard floating point calling convention, double // arguments are passed in FPU registers. Fetch the arguments // from there and call the builtin using soft floating point // convention. switch (redirection->type()) { case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_COMPARE_CALL: if (IsFp64Mode()) { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_hi_word(f12); arg2 = get_fpu_register_word(f14); arg3 = get_fpu_register_hi_word(f14); } else { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_word(f13); arg2 = get_fpu_register_word(f14); arg3 = get_fpu_register_word(f15); } break; case ExternalReference::BUILTIN_FP_CALL: if (IsFp64Mode()) { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_hi_word(f12); } else { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_word(f13); } break; case ExternalReference::BUILTIN_FP_INT_CALL: if (IsFp64Mode()) { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_hi_word(f12); } else { arg0 = get_fpu_register_word(f12); arg1 = get_fpu_register_word(f13); } arg2 = get_register(a2); break; default: break; } } // This is dodgy but it works because the C entry stubs are never moved. // See comment in codegen-arm.cc and bug 1242173. int32_t saved_ra = get_register(ra); intptr_t external = reinterpret_cast(redirection->external_function()); // Based on CpuFeatures::IsSupported(FPU), Mips will use either hardware // FPU, or gcc soft-float routines. Hardware FPU is simulated in this // simulator. Soft-float has additional abstraction of ExternalReference, // to support serialization. if (fp_call) { double dval0, dval1; // one or two double parameters int32_t ival; // zero or one integer parameters int64_t iresult = 0; // integer return value double dresult = 0; // double return value GetFpArgs(&dval0, &dval1, &ival); SimulatorRuntimeCall generic_target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { switch (redirection->type()) { case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_COMPARE_CALL: PrintF("Call to host function at %p with args %f, %f", reinterpret_cast(FUNCTION_ADDR(generic_target)), dval0, dval1); break; case ExternalReference::BUILTIN_FP_CALL: PrintF("Call to host function at %p with arg %f", reinterpret_cast(FUNCTION_ADDR(generic_target)), dval0); break; case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Call to host function at %p with args %f, %d", reinterpret_cast(FUNCTION_ADDR(generic_target)), dval0, ival); break; default: UNREACHABLE(); break; } } switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: { SimulatorRuntimeCompareCall target = reinterpret_cast(external); iresult = target(dval0, dval1); set_register(v0, static_cast(iresult)); set_register(v1, static_cast(iresult >> 32)); break; } case ExternalReference::BUILTIN_FP_FP_CALL: { SimulatorRuntimeFPFPCall target = reinterpret_cast(external); dresult = target(dval0, dval1); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_CALL: { SimulatorRuntimeFPCall target = reinterpret_cast(external); dresult = target(dval0); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_INT_CALL: { SimulatorRuntimeFPIntCall target = reinterpret_cast(external); dresult = target(dval0, ival); SetFpResult(dresult); break; } default: UNREACHABLE(); break; } if (::v8::internal::FLAG_trace_sim) { switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: PrintF("Returned %08x\n", static_cast(iresult)); break; case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_FP_CALL: case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Returned %f\n", dresult); break; default: UNREACHABLE(); break; } } } else if (redirection->type() == ExternalReference::DIRECT_API_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x\n", reinterpret_cast(external), arg0); } SimulatorRuntimeDirectApiCall target = reinterpret_cast(external); target(arg0); } else if ( redirection->type() == ExternalReference::PROFILING_API_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x %08x\n", reinterpret_cast(external), arg0, arg1); } SimulatorRuntimeProfilingApiCall target = reinterpret_cast(external); target(arg0, Redirection::ReverseRedirection(arg1)); } else if ( redirection->type() == ExternalReference::DIRECT_GETTER_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x %08x\n", reinterpret_cast(external), arg0, arg1); } SimulatorRuntimeDirectGetterCall target = reinterpret_cast(external); target(arg0, arg1); } else if ( redirection->type() == ExternalReference::PROFILING_GETTER_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x %08x %08x\n", reinterpret_cast(external), arg0, arg1, arg2); } SimulatorRuntimeProfilingGetterCall target = reinterpret_cast(external); target(arg0, arg1, Redirection::ReverseRedirection(arg2)); } else { DCHECK(redirection->type() == ExternalReference::BUILTIN_CALL || redirection->type() == ExternalReference::BUILTIN_CALL_PAIR); SimulatorRuntimeCall target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { PrintF( "Call to host function at %p " "args %08x, %08x, %08x, %08x, %08x, %08x, %08x, %08x, %08x\n", reinterpret_cast(FUNCTION_ADDR(target)), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); set_register(v0, static_cast(result)); set_register(v1, static_cast(result >> 32)); } if (::v8::internal::FLAG_trace_sim) { PrintF("Returned %08x : %08x\n", get_register(v1), get_register(v0)); } set_register(ra, saved_ra); set_pc(get_register(ra)); } else if (func == BREAK && code <= kMaxStopCode) { if (IsWatchpoint(code)) { PrintWatchpoint(code); } else { IncreaseStopCounter(code); HandleStop(code, instr_.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: %" PRIu64 ") ----------" "----------------------------------", code, break_count_, icount_); dbg.PrintAllRegs(); // Print registers and continue running. } void Simulator::HandleStop(uint32_t code, Instruction* instr) { // Stop if it is enabled, otherwise go on jumping over the stop // and the message address. if (IsEnabledStop(code)) { MipsDebugger dbg(this); dbg.Stop(instr); } } bool Simulator::IsStopInstruction(Instruction* instr) { int32_t func = instr->FunctionFieldRaw(); uint32_t code = static_cast(instr->Bits(25, 6)); return (func == BREAK) && code > kMaxWatchpointCode && code <= kMaxStopCode; } bool Simulator::IsEnabledStop(uint32_t code) { DCHECK_LE(code, kMaxStopCode); DCHECK_GT(code, kMaxWatchpointCode); return !(watched_stops_[code].count & kStopDisabledBit); } void Simulator::EnableStop(uint32_t code) { if (!IsEnabledStop(code)) { watched_stops_[code].count &= ~kStopDisabledBit; } } void Simulator::DisableStop(uint32_t code) { if (IsEnabledStop(code)) { watched_stops_[code].count |= kStopDisabledBit; } } void Simulator::IncreaseStopCounter(uint32_t code) { DCHECK_LE(code, kMaxStopCode); if ((watched_stops_[code].count & ~(1 << 31)) == 0x7FFFFFFF) { PrintF("Stop counter for code %i has overflowed.\n" "Enabling this code and reseting the counter to 0.\n", code); watched_stops_[code].count = 0; EnableStop(code); } else { watched_stops_[code].count++; } } // Print a stop status. void Simulator::PrintStopInfo(uint32_t code) { if (code <= kMaxWatchpointCode) { PrintF("That is a watchpoint, not a stop.\n"); return; } else if (code > kMaxStopCode) { PrintF("Code too large, only %u stops can be used\n", kMaxStopCode + 1); return; } const char* state = IsEnabledStop(code) ? "Enabled" : "Disabled"; int32_t count = watched_stops_[code].count & ~kStopDisabledBit; // Don't print the state of unused breakpoints. if (count != 0) { if (watched_stops_[code].desc) { PrintF("stop %i - 0x%x: \t%s, \tcounter = %i, \t%s\n", code, code, state, count, watched_stops_[code].desc); } else { PrintF("stop %i - 0x%x: \t%s, \tcounter = %i\n", code, code, state, count); } } } void Simulator::SignalException(Exception e) { FATAL("Error: Exception %i raised.", static_cast(e)); } // Min/Max template functions for Double and Single arguments. template static T FPAbs(T a); template <> double FPAbs(double a) { return fabs(a); } template <> float FPAbs(float a) { return fabsf(a); } template static bool FPUProcessNaNsAndZeros(T a, T b, MaxMinKind kind, T& result) { if (std::isnan(a) && std::isnan(b)) { result = a; } else if (std::isnan(a)) { result = b; } else if (std::isnan(b)) { result = a; } else if (b == a) { // Handle -0.0 == 0.0 case. // std::signbit() returns int 0 or 1 so subtracting MaxMinKind::kMax // negates the result. result = std::signbit(b) - static_cast(kind) ? b : a; } else { return false; } return true; } template static T FPUMin(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { return result; } else { return b < a ? b : a; } } template static T FPUMax(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMax, result)) { return result; } else { return b > a ? b : a; } } template static T FPUMinA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) < FPAbs(b)) { result = a; } else if (FPAbs(b) < FPAbs(a)) { result = b; } else { result = a < b ? a : b; } } return result; } template static T FPUMaxA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) > FPAbs(b)) { result = a; } else if (FPAbs(b) > FPAbs(a)) { result = b; } else { result = a > b ? a : b; } } return result; } enum class KeepSign : bool { no = false, yes }; template ::value, int>::type = 0> T FPUCanonalizeNaNArg(T result, T arg, KeepSign keepSign = KeepSign::no) { DCHECK(std::isnan(arg)); T qNaN = std::numeric_limits::quiet_NaN(); if (keepSign == KeepSign::yes) { return std::copysign(qNaN, result); } return qNaN; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return result; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first, Args... args) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return FPUCanonalizeNaNArgs(result, keepSign, args...); } template T FPUCanonalizeOperation(Func f, T first, Args... args) { return FPUCanonalizeOperation(f, KeepSign::no, first, args...); } template T FPUCanonalizeOperation(Func f, KeepSign keepSign, T first, Args... args) { T result = f(first, args...); if (std::isnan(result)) { result = FPUCanonalizeNaNArgs(result, keepSign, first, args...); } return result; } // Handle execution based on instruction types. void Simulator::DecodeTypeRegisterDRsType() { double ft, fs, fd; uint32_t cc, fcsr_cc; int64_t i64; fs = get_fpu_register_double(fs_reg()); ft = (instr_.FunctionFieldRaw() != MOVF) ? get_fpu_register_double(ft_reg()) : 0.0; fd = get_fpu_register_double(fd_reg()); int64_t ft_int = bit_cast(ft); int64_t fd_int = bit_cast(fd); cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(IsMipsArchVariant(kMips32r6)); double result, temp, temp_result; double upper = std::ceil(fs); double lower = std::floor(fs); switch (get_fcsr_rounding_mode()) { case kRoundToNearest: if (upper - fs < fs - lower) { result = upper; } else if (upper - fs > fs - lower) { result = lower; } else { temp_result = upper / 2; double reminder = modf(temp_result, &temp); if (reminder == 0) { result = upper; } else { result = lower; } } break; case kRoundToZero: result = (fs > 0 ? lower : upper); break; case kRoundToPlusInf: result = upper; break; case kRoundToMinusInf: result = lower; break; } SetFPUDoubleResult(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), (ft_int & 0x1) == 0 ? fs : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), (ft_int & 0x1) != 0 ? fs : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { SetFPUDoubleResult(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); int32_t rt_reg = instr_.RtValue(); int32_t rt = get_register(rt_reg); if (rt != 0) { SetFPUDoubleResult(fd_reg(), fs); } break; } case MOVF: { // Same function field for MOVT.D and MOVF.D uint32_t ft_cc = (ft_reg() >> 2) & 0x7; ft_cc = get_fcsr_condition_bit(ft_cc); if (instr_.Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) SetFPUDoubleResult(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) SetFPUDoubleResult(fd_reg(), fs); } break; } case MIN: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMin(ft, fs)); break; case MAX: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMax(ft, fs)); break; case MINA: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMinA(ft, fs)); break; case MAXA: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), FPUMaxA(ft, fs)); break; case ADD_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_D: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), std::fma(fs, ft, fd)); break; case MSUBF_D: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUDoubleResult(fd_reg(), std::fma(-fs, ft, fd)); break; case MUL_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation([](double fs) { return FPAbs(fs); }, fs)); break; case MOV_D: SetFPUDoubleResult(fd_reg(), fs); break; case NEG_D: SetFPUDoubleResult(fd_reg(), FPUCanonalizeOperation([](double src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation([](double fs) { return std::sqrt(fs); }, fs)); break; case RSQRT_D: SetFPUDoubleResult( fd_reg(), FPUCanonalizeOperation( [](double fs) { return 1.0 / std::sqrt(fs); }, fs)); break; case RECIP_D: SetFPUDoubleResult(fd_reg(), FPUCanonalizeOperation( [](double fs) { return 1.0 / fs; }, fs)); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case CVT_W_D: { // Convert double to word. double rounded; int32_t result; round_according_to_fcsr(fs, rounded, result, fs); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case ROUND_W_D: // Round double to word (round half to even). { double rounded = std::floor(fs + 0.5); int32_t result = static_cast(rounded); if ((result & 1) != 0 && result - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. result--; } SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case TRUNC_W_D: // Truncate double to word (round towards 0). { double rounded = trunc(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case FLOOR_W_D: // Round double to word towards negative infinity. { double rounded = std::floor(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case CEIL_W_D: // Round double to word towards positive infinity. { double rounded = std::ceil(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case CVT_S_D: // Convert double to float (single). SetFPUFloatResult(fd_reg(), static_cast(fs)); break; case CVT_L_D: { // Mips32r2: Truncate double to 64-bit long-word. if (IsFp64Mode()) { int64_t result; double rounded; round64_according_to_fcsr(fs, rounded, result, fs); SetFPUResult(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; break; } case TRUNC_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = trunc(fs); i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case ROUND_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = std::floor(fs + 0.5); int64_t result = static_cast(rounded); if ((result & 1) != 0 && result - fs == 0.5) { // If the number is halfway between two integers, // round to the even one. result--; } int64_t i64 = static_cast(result); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case FLOOR_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = std::floor(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CEIL_L_D: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double rounded = std::ceil(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CLASS_D: { // Mips32r6 instruction // Convert double input to uint64_t for easier bit manipulation uint64_t classed = bit_cast(fs); // Extracting sign, exponent and mantissa from the input double uint32_t sign = (classed >> 63) & 1; uint32_t exponent = (classed >> 52) & 0x00000000000007FF; uint64_t mantissa = classed & 0x000FFFFFFFFFFFFF; uint64_t result; double dResult; // Setting flags if input double is negative infinity, // positive infinity, negative zero or positive zero bool negInf = (classed == 0xFFF0000000000000); bool posInf = (classed == 0x7FF0000000000000); bool negZero = (classed == 0x8000000000000000); bool posZero = (classed == 0x0000000000000000); bool signalingNan; bool quietNan; bool negSubnorm; bool posSubnorm; bool negNorm; bool posNorm; // Setting flags if double is NaN signalingNan = false; quietNan = false; if (!negInf && !posInf && exponent == 0x7FF) { quietNan = ((mantissa & 0x0008000000000000) != 0) && ((mantissa & (0x0008000000000000 - 1)) == 0); signalingNan = !quietNan; } // Setting flags if double is subnormal number posSubnorm = false; negSubnorm = false; if ((exponent == 0) && (mantissa != 0)) { DCHECK(sign == 0 || sign == 1); posSubnorm = (sign == 0); negSubnorm = (sign == 1); } // Setting flags if double is normal number posNorm = false; negNorm = false; if (!posSubnorm && !negSubnorm && !posInf && !negInf && !signalingNan && !quietNan && !negZero && !posZero) { DCHECK(sign == 0 || sign == 1); posNorm = (sign == 0); negNorm = (sign == 1); } // Calculating result according to description of CLASS.D instruction result = (posZero << 9) | (posSubnorm << 8) | (posNorm << 7) | (posInf << 6) | (negZero << 5) | (negSubnorm << 4) | (negNorm << 3) | (negInf << 2) | (quietNan << 1) | signalingNan; DCHECK_NE(result, 0); dResult = bit_cast(result); SetFPUDoubleResult(fd_reg(), dResult); break; } case C_F_D: { set_fcsr_bit(fcsr_cc, false); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; } default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterWRsType() { float fs = get_fpu_register_float(fs_reg()); float ft = get_fpu_register_float(ft_reg()); int32_t alu_out = 0x12345678; switch (instr_.FunctionFieldRaw()) { case CVT_S_W: // Convert word to float (single). alu_out = get_fpu_register_signed_word(fs_reg()); SetFPUFloatResult(fd_reg(), static_cast(alu_out)); break; case CVT_D_W: // Convert word to double. alu_out = get_fpu_register_signed_word(fs_reg()); SetFPUDoubleResult(fd_reg(), static_cast(alu_out)); break; case CMP_AF: SetFPUWordResult(fd_reg(), 0); break; case CMP_UN: if (std::isnan(fs) || std::isnan(ft)) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_EQ: if (fs == ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_UEQ: if ((fs == ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_LT: if (fs < ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_ULT: if ((fs < ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_LE: if (fs <= ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_ULE: if ((fs <= ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_OR: if (!std::isnan(fs) && !std::isnan(ft)) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_UNE: if ((fs != ft) || (std::isnan(fs) || std::isnan(ft))) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; case CMP_NE: if (fs != ft) { SetFPUWordResult(fd_reg(), -1); } else { SetFPUWordResult(fd_reg(), 0); } break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterSRsType() { float fs, ft, fd; fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); fd = get_fpu_register_float(fd_reg()); int32_t ft_int = bit_cast(ft); int32_t fd_int = bit_cast(fd); uint32_t cc, fcsr_cc; cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(IsMipsArchVariant(kMips32r6)); float result, temp_result; double temp; float upper = std::ceil(fs); float lower = std::floor(fs); switch (get_fcsr_rounding_mode()) { case kRoundToNearest: if (upper - fs < fs - lower) { result = upper; } else if (upper - fs > fs - lower) { result = lower; } else { temp_result = upper / 2; float reminder = modf(temp_result, &temp); if (reminder == 0) { result = upper; } else { result = lower; } } break; case kRoundToZero: result = (fs > 0 ? lower : upper); break; case kRoundToPlusInf: result = upper; break; case kRoundToMinusInf: result = lower; break; } SetFPUFloatResult(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case ADD_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_S: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult(fd_reg(), std::fma(fs, ft, fd)); break; case MSUBF_S: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult(fd_reg(), std::fma(-fs, ft, fd)); break; case MUL_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_S: SetFPUFloatResult(fd_reg(), FPUCanonalizeOperation( [](float fs) { return FPAbs(fs); }, fs)); break; case MOV_S: SetFPUFloatResult(fd_reg(), fs); break; case NEG_S: SetFPUFloatResult(fd_reg(), FPUCanonalizeOperation([](float src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation([](float src) { return std::sqrt(src); }, fs)); break; case RSQRT_S: SetFPUFloatResult( fd_reg(), FPUCanonalizeOperation( [](float src) { return 1.0 / std::sqrt(src); }, fs)); break; case RECIP_S: SetFPUFloatResult(fd_reg(), FPUCanonalizeOperation( [](float src) { return 1.0 / src; }, fs)); break; case C_F_D: set_fcsr_bit(fcsr_cc, false); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); TraceRegWr(test_fcsr_bit(fcsr_cc)); break; case CVT_D_S: SetFPUDoubleResult(fd_reg(), static_cast(fs)); break; case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case CLASS_S: { // Mips32r6 instruction // Convert float input to uint32_t for easier bit manipulation float fs = get_fpu_register_float(fs_reg()); uint32_t classed = bit_cast(fs); // Extracting sign, exponent and mantissa from the input float uint32_t sign = (classed >> 31) & 1; uint32_t exponent = (classed >> 23) & 0x000000FF; uint32_t mantissa = classed & 0x007FFFFF; uint32_t result; float fResult; // Setting flags if input float is negative infinity, // positive infinity, negative zero or positive zero bool negInf = (classed == 0xFF800000); bool posInf = (classed == 0x7F800000); bool negZero = (classed == 0x80000000); bool posZero = (classed == 0x00000000); bool signalingNan; bool quietNan; bool negSubnorm; bool posSubnorm; bool negNorm; bool posNorm; // Setting flags if float is NaN signalingNan = false; quietNan = false; if (!negInf && !posInf && (exponent == 0xFF)) { quietNan = ((mantissa & 0x00200000) == 0) && ((mantissa & (0x00200000 - 1)) == 0); signalingNan = !quietNan; } // Setting flags if float is subnormal number posSubnorm = false; negSubnorm = false; if ((exponent == 0) && (mantissa != 0)) { DCHECK(sign == 0 || sign == 1); posSubnorm = (sign == 0); negSubnorm = (sign == 1); } // Setting flags if float is normal number posNorm = false; negNorm = false; if (!posSubnorm && !negSubnorm && !posInf && !negInf && !signalingNan && !quietNan && !negZero && !posZero) { DCHECK(sign == 0 || sign == 1); posNorm = (sign == 0); negNorm = (sign == 1); } // Calculating result according to description of CLASS.S instruction result = (posZero << 9) | (posSubnorm << 8) | (posNorm << 7) | (posInf << 6) | (negZero << 5) | (negSubnorm << 4) | (negNorm << 3) | (negInf << 2) | (quietNan << 1) | signalingNan; DCHECK_NE(result, 0); fResult = bit_cast(result); SetFPUFloatResult(fd_reg(), fResult); break; } case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult( fd_reg(), (ft_int & 0x1) == 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); SetFPUFloatResult( fd_reg(), (ft_int & 0x1) != 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { SetFPUFloatResult(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() != 0) { SetFPUFloatResult(fd_reg(), fs); } break; } case MOVF: { // Same function field for MOVT.D and MOVF.D uint32_t ft_cc = (ft_reg() >> 2) & 0x7; ft_cc = get_fcsr_condition_bit(ft_cc); if (instr_.Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) SetFPUFloatResult(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) SetFPUFloatResult(fd_reg(), fs); } break; } case TRUNC_W_S: { // Truncate single to word (round towards 0). float rounded = trunc(fs); int32_t result = static_cast(rounded); SetFPUWordResult(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case TRUNC_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float rounded = trunc(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { SetFPUResult(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case FLOOR_W_S: // Round double to word towards negative infinity. { float rounded = std::floor(fs); int32_t result = static_cast