: print infos about number \n"); PrintF(" or all stop(s).\n"); PrintF(" stop enable/disable all/ : enables / disables\n"); PrintF(" all or number stop(s)\n"); PrintF(" stop unstop\n"); PrintF(" ignore the stop instruction at the current location\n"); PrintF(" from now on\n"); } else { PrintF("Unknown command: %s\n", cmd); } } } // Add all the breakpoints back to stop execution and enter the debugger // shell when hit. RedoBreakpoints(); #undef COMMAND_SIZE #undef ARG_SIZE #undef STR #undef XSTR } static bool ICacheMatch(void* one, void* two) { DCHECK((reinterpret_cast(one) & CachePage::kPageMask) == 0); DCHECK((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::FlushICache(v8::internal::HashMap* 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(v8::internal::HashMap* i_cache, void* page) { v8::internal::HashMap::Entry* entry = i_cache->LookupOrInsert(page, ICacheHash(page)); if (entry->value == NULL) { 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(v8::internal::HashMap* i_cache, intptr_t start, int size) { DCHECK(size <= CachePage::kPageSize); DCHECK(AllOnOnePage(start, size - 1)); DCHECK((start & CachePage::kLineMask) == 0); DCHECK((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(v8::internal::HashMap* 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), Instruction::kInstrSize)); } else { // Cache miss. Load memory into the cache. memcpy(cached_line, line, CachePage::kLineLength); *cache_valid_byte = CachePage::LINE_VALID; } } void Simulator::Initialize(Isolate* isolate) { if (isolate->simulator_initialized()) return; isolate->set_simulator_initialized(true); ::v8::internal::ExternalReference::set_redirector(isolate, &RedirectExternalReference); } Simulator::Simulator(Isolate* isolate) : isolate_(isolate) { i_cache_ = isolate_->simulator_i_cache(); if (i_cache_ == NULL) { i_cache_ = new v8::internal::HashMap(&ICacheMatch); isolate_->set_simulator_i_cache(i_cache_); } Initialize(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_ = NULL; 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_[i] = 0; } if (IsMipsArchVariant(kMips32r6)) { FCSR_ = kFCSRNaN2008FlagMask; } 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; InitializeCoverage(); last_debugger_input_ = NULL; } Simulator::~Simulator() { free(stack_); } // When the generated code calls an external reference we need to catch that in // the simulator. The external reference will be a function compiled for the // host architecture. We need to call that function instead of trying to // execute it with the simulator. We do that by redirecting the external // reference to a swi (software-interrupt) instruction that is handled by // the simulator. We write the original destination of the jump just at a known // offset from the swi instruction so the simulator knows what to call. class Redirection { public: Redirection(Isolate* isolate, void* external_function, ExternalReference::Type type) : external_function_(external_function), swi_instruction_(rtCallRedirInstr), type_(type), next_(NULL) { next_ = isolate->simulator_redirection(); Simulator::current(isolate)-> FlushICache(isolate->simulator_i_cache(), reinterpret_cast(&swi_instruction_), Instruction::kInstrSize); isolate->set_simulator_redirection(this); } void* address_of_swi_instruction() { return reinterpret_cast(&swi_instruction_); } void* external_function() { return external_function_; } ExternalReference::Type type() { return type_; } static Redirection* Get(Isolate* isolate, void* external_function, ExternalReference::Type type) { Redirection* current = isolate->simulator_redirection(); for (; current != NULL; current = current->next_) { if (current->external_function_ == external_function) return current; } return new Redirection(isolate, external_function, type); } static Redirection* FromSwiInstruction(Instruction* swi_instruction) { char* addr_of_swi = reinterpret_cast(swi_instruction); char* addr_of_redirection = addr_of_swi - offsetof(Redirection, swi_instruction_); return reinterpret_cast(addr_of_redirection); } static void* ReverseRedirection(int32_t reg) { Redirection* redirection = FromSwiInstruction( reinterpret_cast(reinterpret_cast(reg))); return redirection->external_function(); } static void DeleteChain(Redirection* redirection) { while (redirection != nullptr) { Redirection* next = redirection->next_; delete redirection; redirection = next; } } private: void* external_function_; uint32_t swi_instruction_; ExternalReference::Type type_; Redirection* next_; }; // static void Simulator::TearDown(HashMap* i_cache, Redirection* first) { Redirection::DeleteChain(first); if (i_cache != nullptr) { for (HashMap::Entry* entry = i_cache->Start(); entry != nullptr; entry = i_cache->Next(entry)) { delete static_cast(entry->value); } delete i_cache; } } void* Simulator::RedirectExternalReference(Isolate* isolate, void* external_function, ExternalReference::Type type) { Redirection* redirection = Redirection::Get(isolate, external_function, type); return redirection->address_of_swi_instruction(); } // Get the active Simulator for the current thread. Simulator* Simulator::current(Isolate* isolate) { v8::internal::Isolate::PerIsolateThreadData* isolate_data = isolate->FindOrAllocatePerThreadDataForThisThread(); DCHECK(isolate_data != NULL); DCHECK(isolate_data != NULL); Simulator* sim = isolate_data->simulator(); if (sim == NULL) { // 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] = 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]); *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])) + 1; *phiword = value; } void Simulator::set_fpu_register_float(int fpureg, float value) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg]) = value; } void Simulator::set_fpu_register_double(int fpureg, double value) { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg]) = 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 { DCHECK(IsFp64Mode()); DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return FPUregisters_[fpureg]; } int32_t Simulator::get_fpu_register_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg] & 0xffffffff); } int32_t Simulator::get_fpu_register_signed_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg] & 0xffffffff); } int32_t Simulator::get_fpu_register_hi_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast((FPUregisters_[fpureg] >> 32) & 0xffffffff); } float Simulator::get_fpu_register_float(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(const_cast(&FPUregisters_[fpureg])); } double Simulator::get_fpu_register_double(int fpureg) const { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(&FPUregisters_[fpureg]); } 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); } } // 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; } unsigned int Simulator::get_fcsr_rounding_mode() { return FCSR_ & 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--; } 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--; } 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(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--; } 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--; } 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) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%08x", value); } } // TODO(plind): consider making icount_ printing a flag option. void Simulator::TraceMemRd(int32_t addr, int32_t value) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%08x <-- [%08x] (%" PRIu64 ")", value, addr, icount_); } } void Simulator::TraceMemWr(int32_t addr, int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case BYTE: SNPrintF(trace_buf_, " %02x --> [%08x]", static_cast(value), addr); break; case HALF: SNPrintF(trace_buf_, " %04x --> [%08x]", static_cast(value), addr); break; case WORD: SNPrintF(trace_buf_, "%08x --> [%08x]", value, addr); break; } } } int Simulator::ReadW(int32_t addr, Instruction* instr) { if (addr >=0 && addr < 0x400) { // This has to be a NULL-dereference, drop into debugger. PrintF("Memory read from bad address: 0x%08x, pc=0x%08x\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0) { intptr_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); 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 NULL-dereference, drop into debugger. PrintF("Memory write to bad address: 0x%08x, pc=0x%08x\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0) { 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) { 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) { 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) { 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) { 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) { 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) { 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; } // 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%08x: %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); // 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(Instruction* instr) { // 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::FromSwiInstruction(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]; 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", FUNCTION_ADDR(generic_target), dval0, dval1); break; case ExternalReference::BUILTIN_FP_CALL: PrintF("Call to host function at %p with arg %f", FUNCTION_ADDR(generic_target), dval0); break; case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Call to host function at %p with args %f, %d", 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 { 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\n", FUNCTION_ADDR(target), arg0, arg1, arg2, arg3, arg4, arg5); } int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5); 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); } } 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); } else { set_pc(get_pc() + 2 * Instruction::kInstrSize); } } 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(code <= kMaxStopCode); DCHECK(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(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) { V8_Fatal(__FILE__, __LINE__, "Error: Exception %i raised.", static_cast(e)); } void Simulator::DecodeTypeRegisterDRsType() { double ft, fs, fd; uint32_t cc, fcsr_cc; int64_t i64; fs = get_fpu_register_double(fs_reg()); ft = (get_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 = get_instr()->FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (get_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; } set_fpu_register_double(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_double(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_double(fd_reg(), (ft_int & 0x1) == 0 ? fs : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_double(fd_reg(), (ft_int & 0x1) != 0 ? fs : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { set_fpu_register_double(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); int32_t rt_reg = get_instr()->RtValue(); int32_t rt = get_register(rt_reg); if (rt != 0) { set_fpu_register_double(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 (get_instr()->Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) set_fpu_register_double(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) set_fpu_register_double(fd_reg(), fs); } break; } case MIN: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { set_fpu_register_double(fd_reg(), (fs >= ft) ? ft : fs); } break; case MINA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { double result; if (fabs(fs) > fabs(ft)) { result = ft; } else if (fabs(fs) < fabs(ft)) { result = fs; } else { result = (fs < ft ? fs : ft); } set_fpu_register_double(fd_reg(), result); } break; case MAXA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { double result; if (fabs(fs) < fabs(ft)) { result = ft; } else if (fabs(fs) > fabs(ft)) { result = fs; } else { result = (fs > ft ? fs : ft); } set_fpu_register_double(fd_reg(), result); } break; case MAX: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { set_fpu_register_double(fd_reg(), (fs <= ft) ? ft : fs); } break; break; case ADD_D: set_fpu_register_double(fd_reg(), fs + ft); break; case SUB_D: set_fpu_register_double(fd_reg(), fs - ft); break; case MUL_D: set_fpu_register_double(fd_reg(), fs * ft); break; case DIV_D: set_fpu_register_double(fd_reg(), fs / ft); break; case ABS_D: set_fpu_register_double(fd_reg(), fabs(fs)); break; case MOV_D: set_fpu_register_double(fd_reg(), fs); break; case NEG_D: set_fpu_register_double(fd_reg(), -fs); break; case SQRT_D: lazily_initialize_fast_sqrt(isolate_); set_fpu_register_double(fd_reg(), fast_sqrt(fs, isolate_)); break; case RSQRT_D: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); lazily_initialize_fast_sqrt(isolate_); double result = 1.0 / fast_sqrt(fs, isolate_); set_fpu_register_double(fd_reg(), result); break; } case RECIP_D: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double result = 1.0 / fs; set_fpu_register_double(fd_reg(), result); break; } case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); break; case CVT_W_D: { // Convert double to word. double rounded; int32_t result; round_according_to_fcsr(fs, rounded, result, fs); set_fpu_register_word(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--; } set_fpu_register_word(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); set_fpu_register_word(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); set_fpu_register_word(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); set_fpu_register_word(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). set_fpu_register_float(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); set_fpu_register(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()) { set_fpu_register(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()) { set_fpu_register(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()) { set_fpu_register(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()) { set_fpu_register(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(result != 0); dResult = bit_cast(result); set_fpu_register_double(fd_reg(), dResult); break; } case C_F_D: { set_fcsr_bit(fcsr_cc, false); 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 (get_instr()->FunctionFieldRaw()) { case CVT_S_W: // Convert word to float (single). alu_out = get_fpu_register_signed_word(fs_reg()); set_fpu_register_float(fd_reg(), static_cast(alu_out)); break; case CVT_D_W: // Convert word to double. alu_out = get_fpu_register_signed_word(fs_reg()); set_fpu_register_double(fd_reg(), static_cast(alu_out)); break; case CMP_AF: set_fpu_register_word(fd_reg(), 0); break; case CMP_UN: if (std::isnan(fs) || std::isnan(ft)) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_EQ: if (fs == ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_UEQ: if ((fs == ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_LT: if (fs < ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_ULT: if ((fs < ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_LE: if (fs <= ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_ULE: if ((fs <= ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_OR: if (!std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_UNE: if ((fs != ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_NE: if (fs != ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(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 = get_instr()->FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (get_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; } set_fpu_register_float(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case ADD_S: set_fpu_register_float(fd_reg(), fs + ft); break; case SUB_S: set_fpu_register_float(fd_reg(), fs - ft); break; case MUL_S: set_fpu_register_float(fd_reg(), fs * ft); break; case DIV_S: set_fpu_register_float(fd_reg(), fs / ft); break; case ABS_S: set_fpu_register_float(fd_reg(), fabs(fs)); break; case MOV_S: set_fpu_register_float(fd_reg(), fs); break; case NEG_S: set_fpu_register_float(fd_reg(), -fs); break; case SQRT_S: lazily_initialize_fast_sqrt(isolate_); set_fpu_register_float(fd_reg(), fast_sqrt(fs, isolate_)); break; case RSQRT_S: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); lazily_initialize_fast_sqrt(isolate_); float result = 1.0 / fast_sqrt(fs, isolate_); set_fpu_register_float(fd_reg(), result); break; } case RECIP_S: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float result = 1.0 / fs; set_fpu_register_float(fd_reg(), result); break; } case C_F_D: set_fcsr_bit(fcsr_cc, false); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); break; case CVT_D_S: set_fpu_register_double(fd_reg(), static_cast(fs)); break; case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_float(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(result != 0); fResult = bit_cast(result); set_fpu_register_float(fd_reg(), fResult); break; } case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_float(fd_reg(), (ft_int & 0x1) == 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_float(fd_reg(), (ft_int & 0x1) != 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { set_fpu_register_float(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() != 0) { set_fpu_register_float(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 (get_instr()->Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) set_fpu_register_float(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) set_fpu_register_float(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); set_fpu_register_word(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()) { set_fpu_register(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(rounded); set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case FLOOR_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float rounded = std::floor(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case ROUND_W_S: { float 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--; } set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } break; } case ROUND_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float 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()) { set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CEIL_W_S: // Round double to word towards positive infinity. { float rounded = std::ceil(fs); int32_t result = static_cast(rounded); set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case CEIL_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float rounded = std::ceil(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case MIN: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { set_fpu_register_float(fd_reg(), (fs >= ft) ? ft : fs); } break; case MAX: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { set_fpu_register_float(fd_reg(), (fs <= ft) ? ft : fs); } break; case MINA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { float result; if (fabs(fs) > fabs(ft)) { result = ft; } else if (fabs(fs) < fabs(ft)) { result = fs; } else { result = (fs < ft ? fs : ft); } set_fpu_register_float(fd_reg(), result); } break; case MAXA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { float result; if (fabs(fs) < fabs(ft)) { result = ft; } else if (fabs(fs) > fabs(ft)) { result = fs; } else { result = (fs > ft ? fs : ft); } set_fpu_register_float(fd_reg(), result); } break; case CVT_L_S: { if (IsFp64Mode()) { int64_t result; float rounded; round64_according_to_fcsr(fs, rounded, result, fs); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CVT_W_S: { float rounded; int32_t result; round_according_to_fcsr(fs, rounded, result, fs); set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } break; } default: // CVT_W_S CVT_L_S ROUND_W_S ROUND_L_S FLOOR_W_S FLOOR_L_S // CEIL_W_S CEIL_L_S CVT_PS_S are unimplemented. UNREACHABLE(); } } void Simulator::DecodeTypeRegisterLRsType() { double fs = get_fpu_register_double(fs_reg()); double ft = get_fpu_register_double(ft_reg()); switch (get_instr()->FunctionFieldRaw()) { case CVT_D_L: // Mips32r2 instruction. // Watch the signs here, we want 2 32-bit vals // to make a sign-64. int64_t i64; if (IsFp64Mode()) { i64 = get_fpu_register(fs_reg()); } else { i64 = static_cast(get_fpu_register_word(fs_reg())); i64 |= static_cast(get_fpu_register_word(fs_reg() + 1)) << 32; } set_fpu_register_double(fd_reg(), static_cast(i64)); break; case CVT_S_L: if (IsFp64Mode()) { i64 = get_fpu_register(fs_reg()); } else { i64 = static_cast(get_fpu_register_word(fs_reg())); i64 |= static_cast(get_fpu_register_word(fs_reg() + 1)) << 32; } set_fpu_register_float(fd_reg(), static_cast(i64)); break; case CMP_AF: // Mips64r6 CMP.D instructions. set_fpu_register(fd_reg(), 0); break; case CMP_UN: if (std::isnan(fs) || std::isnan(ft)) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_EQ: if (fs == ft) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_UEQ: if ((fs == ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_LT: if (fs < ft) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_ULT: if ((fs < ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_LE: if (fs <= ft) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_ULE: if ((fs <= ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_OR: if (!std::isnan(fs) && !std::isnan(ft)) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_UNE: if ((fs != ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_NE: if (fs != ft && (!std::isnan(fs) && !std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterCOP1() { switch (get_instr()->RsFieldRaw()) { case CFC1: // At the moment only FCSR is supported. DCHECK(fs_reg() == kFCSRRegister); set_register(rt_reg(), FCSR_); break; case MFC1: set_register(rt_reg(), get_fpu_register_word(fs_reg())); break; case MFHC1: set_register(rt_reg(), get_fpu_register_hi_word(fs_reg())); break; case CTC1: { // At the moment only FCSR is supported. DCHECK(fs_reg() == kFCSRRegister); int32_t reg = registers_[rt_reg()]; if (IsMipsArchVariant(kMips32r6)) { FCSR_ = reg | kFCSRNaN2008FlagMask; } else { DCHECK(IsMipsArchVariant(kMips32r1) || IsMipsArchVariant(kMips32r2)); FCSR_ = reg & ~kFCSRNaN2008FlagMask; } break; } case MTC1: // Hardware writes upper 32-bits to zero on mtc1. set_fpu_register_hi_word(fs_reg(), 0); set_fpu_register_word(fs_reg(), registers_[rt_reg()]); break; case MTHC1: set_fpu_register_hi_word(fs_reg(), registers_[rt_reg()]); break; case S: { DecodeTypeRegisterSRsType(); break; } case D: DecodeTypeRegisterDRsType(); break; case W: DecodeTypeRegisterWRsType(); break; case L: DecodeTypeRegisterLRsType(); break; case PS: // Not implemented. UNREACHABLE(); default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterCOP1X() { switch (get_instr()->FunctionFieldRaw()) { case MADD_D: double fr, ft, fs; fr = get_fpu_register_double(fr_reg()); fs = get_fpu_register_double(fs_reg()); ft = get_fpu_register_double(ft_reg()); set_fpu_register_double(fd_reg(), fs * ft + fr); break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterSPECIAL() { int64_t alu_out = 0x12345678; int64_t i64hilo = 0; uint64_t u64hilo = 0; bool do_interrupt = false; switch (get_instr()->FunctionFieldRaw()) { case SELEQZ_S: DCHECK(IsMipsArchVariant(kMips32r6)); set_register(rd_reg(), rt() == 0 ? rs() : 0); break; case SELNEZ_S: DCHECK(IsMipsArchVariant(kMips32r6)); set_register(rd_reg(), rt() != 0 ? rs() : 0); break; case JR: { int32_t next_pc = rs(); int32_t current_pc = get_pc(); Instruction* branch_delay_instr = reinterpret_cast(current_pc + Instruction::kInstrSize); BranchDelayInstructionDecode(branch_delay_instr); set_pc(next_pc); pc_modified_ = true; break; } case JALR: { int32_t next_pc = rs(); int32_t return_addr_reg = rd_reg(); int32_t current_pc = get_pc(); Instruction* branch_delay_instr = reinterpret_cast(current_pc + Instruction::kInstrSize); BranchDelayInstructionDecode(branch_delay_instr); set_register(return_addr_reg, current_pc + 2 * Instruction::kInstrSize); set_pc(next_pc); pc_modified_ = true; break; } case SLL: alu_out = rt() << sa(); SetResult(rd_reg(), static_cast(alu_out)); break; case SRL: if (rs_reg() == 0) { // Regular logical right shift of a word by a fixed number of // bits instruction. RS field is always equal to 0. alu_out = rt_u() >> sa(); } else { // Logical right-rotate of a word by a fixed number of bits. This // is special case of SRL instruction, added in MIPS32 Release 2. // RS field is equal to 00001. alu_out = base::bits::RotateRight32(rt_u(), sa()); } SetResult(rd_reg(), static_cast(alu_out)); break; case SRA: alu_out = rt() >> sa(); SetResult(rd_reg(), static_cast(alu_out)); break; case SLLV: alu_out = rt() << rs(); SetResult(rd_reg(), static_cast(alu_out)); break; case SRLV: if (sa() == 0) { // Regular logical right-shift of a word by a variable number of // bits instruction. SA field is always equal to 0. alu_out = rt_u() >> rs(); } else { // Logical right-rotate of a word by a variable number of bits. // This is special case od SRLV instruction, added in MIPS32 // Release 2. SA field is equal to 00001. alu_out = base::bits::RotateRight32(rt_u(), rs_u()); } SetResult(rd_reg(), static_cast(alu_out)); break; case SRAV: SetResult(rd_reg(), rt() >> rs()); break; case LSA: { DCHECK(IsMipsArchVariant(kMips32r6)); int8_t sa = lsa_sa() + 1; int32_t _rt = rt(); int32_t _rs = rs(); int32_t res = _rs << sa; res += _rt; DCHECK_EQ(res, (rs() << (lsa_sa() + 1)) + rt()); SetResult(rd_reg(), (rs() << (lsa_sa() + 1)) + rt()); break; } case MFHI: // MFHI == CLZ on R6. if (!IsMipsArchVariant(kMips32r6)) { DCHECK(sa() == 0); alu_out = get_register(HI); } else { // MIPS spec: If no bits were set in GPR rs, the result written to // GPR rd is 32. DCHECK(sa() == 1); alu_out = base::bits::CountLeadingZeros32(rs_u()); } SetResult(rd_reg(), static_cast(alu_out)); break; case MFLO: alu_out = get_register(LO); SetResult(rd_reg(), static_cast(alu_out)); break; // Instructions using HI and LO registers. case MULT: i64hilo = static_cast(rs()) * static_cast(rt()); if (!IsMipsArchVariant(kMips32r6)) { set_register(LO, static_cast(i64hilo & 0xffffffff)); set_register(HI, static_cast(i64hilo >> 32)); } else { switch (sa()) { case MUL_OP: set_register(rd_reg(), static_cast(i64hilo & 0xffffffff)); break; case MUH_OP: set_register(rd_reg(), static_cast(i64hilo >> 32)); break; default: UNIMPLEMENTED_MIPS(); break; } } break; case MULTU: u64hilo = static_cast(rs_u()) * static_cast(rt_u()); if (!IsMipsArchVariant(kMips32r6)) { set_register(LO, static_cast(u64hilo & 0xffffffff)); set_register(HI, static_cast(u64hilo >> 32)); } else { switch (sa()) { case MUL_OP: set_register(rd_reg(), static_cast(u64hilo & 0xffffffff)); break; case MUH_OP: set_register(rd_reg(), static_cast(u64hilo >> 32)); break; default: UNIMPLEMENTED_MIPS(); break; } } break; case DIV: if (IsMipsArchVariant(kMips32r6)) { switch (get_instr()->SaValue()) { case DIV_OP: if (rs() == INT_MIN && rt() == -1) { set_register(rd_reg(), INT_MIN); } else if (rt() != 0) { set_register(rd_reg(), rs() / rt()); } break; case MOD_OP: if (rs() == INT_MIN && rt() == -1) { set_register(rd_reg(), 0); } else if (rt() != 0) { set_register(rd_reg(), rs() % rt()); } break; default: UNIMPLEMENTED_MIPS(); break; } } else { // Divide by zero and overflow was not checked in the // configuration step - div and divu do not raise exceptions. On // division by 0 the result will be UNPREDICTABLE. On overflow // (INT_MIN/-1), return INT_MIN which is what the hardware does. if (rs() == INT_MIN && rt() == -1) { set_register(LO, INT_MIN); set_register(HI, 0); } else if (rt() != 0) { set_register(LO, rs() / rt()); set_register(HI, rs() % rt()); } } break; case DIVU: if (IsMipsArchVariant(kMips32r6)) { switch (get_instr()->SaValue()) { case DIV_OP: if (rt_u() != 0) { set_register(rd_reg(), rs_u() / rt_u()); } break; case MOD_OP: if (rt_u() != 0) { set_register(rd_reg(), rs_u() % rt_u()); } break; default: UNIMPLEMENTED_MIPS(); break; } } else { if (rt_u() != 0) { set_register(LO, rs_u() / rt_u()); set_register(HI, rs_u() % rt_u()); } } break; case ADD: if (HaveSameSign(rs(), rt())) { if (rs() > 0) { if (rs() <= (Registers::kMaxValue - rt())) { SignalException(kIntegerOverflow); } } else if (rs() < 0) { if (rs() >= (Registers::kMinValue - rt())) { SignalException(kIntegerUnderflow); } } } SetResult(rd_reg(), rs() + rt()); break; case ADDU: SetResult(rd_reg(), rs() + rt()); break; case SUB: if (!HaveSameSign(rs(), rt())) { if (rs() > 0) { if (rs() <= (Registers::kMaxValue + rt())) { SignalException(kIntegerOverflow); } } else if (rs() < 0) { if (rs() >= (Registers::kMinValue + rt())) { SignalException(kIntegerUnderflow); } } } SetResult(rd_reg(), rs() - rt()); break; case SUBU: SetResult(rd_reg(), rs() - rt()); break; case AND: SetResult(rd_reg(), rs() & rt()); break; case OR: SetResult(rd_reg(), rs() | rt()); break; case XOR: SetResult(rd_reg(), rs() ^ rt()); break; case NOR: SetResult(rd_reg(), ~(rs() | rt())); break; case SLT: SetResult(rd_reg(), rs() < rt() ? 1 : 0); break; case SLTU: SetResult(rd_reg(), rs_u() < rt_u() ? 1 : 0); break; // Break and trap instructions. case BREAK: do_interrupt = true; break; case TGE: do_interrupt = rs() >= rt(); break; case TGEU: do_interrupt = rs_u() >= rt_u(); break; case TLT: do_interrupt = rs() < rt(); break; case TLTU: do_interrupt = rs_u() < rt_u(); break; case TEQ: do_interrupt = rs() == rt(); break; case TNE: do_interrupt = rs() != rt(); break; // Conditional moves. case MOVN: if (rt()) { set_register(rd_reg(), rs()); TraceRegWr(rs()); } break; case MOVCI: { uint32_t cc = get_instr()->FBccValue(); uint32_t fcsr_cc = get_fcsr_condition_bit(cc); if (get_instr()->Bit(16)) { // Read Tf bit. if (test_fcsr_bit(fcsr_cc)) set_register(rd_reg(), rs()); } else { if (!test_fcsr_bit(fcsr_cc)) set_register(rd_reg(), rs()); } break; } case MOVZ: if (!rt()) { set_register(rd_reg(), rs()); TraceRegWr(rs()); } break; default: UNREACHABLE(); } if (do_interrupt) { SoftwareInterrupt(get_instr()); } } void Simulator::DecodeTypeRegisterSPECIAL2() { int32_t alu_out; switch (get_instr()->FunctionFieldRaw()) { case MUL: // Only the lower 32 bits are kept. alu_out = rs_u() * rt_u(); // HI and LO are UNPREDICTABLE after the operation. set_register(LO, Unpredictable); set_register(HI, Unpredictable); break; case CLZ: // MIPS32 spec: If no bits were set in GPR rs, the result written to // GPR rd is 32. alu_out = base::bits::CountLeadingZeros32(rs_u()); break; default: alu_out = 0x12345678; UNREACHABLE(); } SetResult(rd_reg(), alu_out); } void Simulator::DecodeTypeRegisterSPECIAL3() { int32_t alu_out; switch (get_instr()->FunctionFieldRaw()) { case INS: { // Mips32r2 instruction. // Interpret rd field as 5-bit msb of insert. uint16_t msb = rd_reg(); // Interpret sa field as 5-bit lsb of insert. uint16_t lsb = sa(); uint16_t size = msb - lsb + 1; uint32_t mask = (1 << size) - 1; alu_out = (rt_u() & ~(mask << lsb)) | ((rs_u() & mask) << lsb); // Ins instr leaves result in Rt, rather than Rd. SetResult(rt_reg(), alu_out); break; } case EXT: { // Mips32r2 instruction. // Interpret rd field as 5-bit msb of extract. uint16_t msb = rd_reg(); // Interpret sa field as 5-bit lsb of extract. uint16_t lsb = sa(); uint16_t size = msb + 1; uint32_t mask = (1 << size) - 1; alu_out = (rs_u() & (mask << lsb)) >> lsb; SetResult(rt_reg(), alu_out); break; } case BSHFL: { int sa = get_instr()->SaFieldRaw() >> kSaShift; switch (sa) { case BITSWAP: { uint32_t input = static_cast(rt()); uint32_t output = 0; uint8_t i_byte, o_byte; // Reverse the bit in byte for each individual byte for (int i = 0; i < 4; i++) { output = output >> 8; i_byte = input & 0xff; // Fast way to reverse bits in byte // Devised by Sean Anderson, July 13, 2001 o_byte = static_cast(((i_byte * 0x0802LU & 0x22110LU) | (i_byte * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16); output = output | (static_cast(o_byte << 24)); input = input >> 8; } alu_out = static_cast(output); break; } case SEB: case SEH: case WSBH: alu_out = 0x12345678; UNREACHABLE(); break; default: { const uint8_t bp = get_instr()->Bp2Value(); sa >>= kBp2Bits; switch (sa) { case ALIGN: { if (bp == 0) { alu_out = static_cast(rt()); } else { uint32_t rt_hi = rt() << (8 * bp); uint32_t rs_lo = rs() >> (8 * (4 - bp)); alu_out = static_cast(rt_hi | rs_lo); } break; } default: alu_out = 0x12345678; UNREACHABLE(); break; } } } SetResult(rd_reg(), alu_out); break; } default: UNREACHABLE(); } } void Simulator::DecodeTypeRegister(Instruction* instr) { const Opcode op = instr->OpcodeFieldRaw(); // Set up the variables if needed before executing the instruction. // ConfigureTypeRegister(instr); set_instr(instr); // ---------- Execution. switch (op) { case COP1: DecodeTypeRegisterCOP1(); break; case COP1X: DecodeTypeRegisterCOP1X(); break; case SPECIAL: DecodeTypeRegisterSPECIAL(); break; case SPECIAL2: DecodeTypeRegisterSPECIAL2(); break; case SPECIAL3: DecodeTypeRegisterSPECIAL3(); break; default: UNREACHABLE(); } } // Type 2: instructions using a 16, 21 or 26 bits immediate. (e.g. beq, beqc). void Simulator::DecodeTypeImmediate(Instruction* instr) { // Instruction fields. Opcode op = instr->OpcodeFieldRaw(); int32_t rs_reg = instr->RsValue(); int32_t rs = get_register(instr->RsValue()); uint32_t rs_u = static_cast(rs); int32_t rt_reg = instr->RtValue(); // Destination register. int32_t rt = get_register(rt_reg); int16_t imm16 = instr->Imm16Value(); int32_t ft_reg = instr->FtValue(); // Destination register. // Zero extended immediate. uint32_t oe_imm16 = 0xffff & imm16; // Sign extended immediate. int32_t se_imm16 = imm16; // Next pc. int32_t next_pc = bad_ra; // Used for conditional branch instructions. bool execute_branch_delay_instruction = false; // Used for arithmetic instructions. int32_t alu_out = 0; // Used for memory instructions. int32_t addr = 0x0; // Branch instructions common part. auto BranchAndLinkHelper = [this, instr, &next_pc, &execute_branch_delay_instruction]( bool do_branch) { execute_branch_delay_instruction = true; int32_t current_pc = get_pc(); if (do_branch) { int16_t imm16 = instr->Imm16Value(); next_pc = current_pc + (imm16 << 2) + Instruction::kInstrSize; set_register(31, current_pc + 2 * Instruction::kInstrSize); } else { next_pc = current_pc + 2 * Instruction::kInstrSize; } }; auto BranchHelper = [this, instr, &next_pc, &execute_branch_delay_instruction](bool do_branch) { execute_branch_delay_instruction = true; int32_t current_pc = get_pc(); if (do_branch) { int16_t imm16 = instr->Imm16Value(); next_pc = current_pc + (imm16 << 2) + Instruction::kInstrSize; } else { next_pc = current_pc + 2 * Instruction::kInstrSize; } }; auto BranchAndLinkCompactHelper = [this, instr, &next_pc](bool do_branch, int bits) { int32_t current_pc = get_pc(); CheckForbiddenSlot(current_pc); if (do_branch) { int32_t imm = instr->ImmValue(bits); imm <<= 32 - bits; imm >>= 32 - bits; next_pc = current_pc + (imm << 2) + Instruction::kInstrSize; set_register(31, current_pc + Instruction::kInstrSize); } }; auto BranchCompactHelper = [&next_pc, this, instr](bool do_branch, int bits) { int32_t current_pc = get_pc(); CheckForbiddenSlot(current_pc); if (do_branch) { int32_t imm = instr->ImmValue(bits); imm <<= 32 - bits; imm >>= 32 - bits; next_pc = get_pc() + (imm << 2) + Instruction::kInstrSize; } }; switch (op) { // ------------- COP1. Coprocessor instructions. case COP1: switch (instr->RsFieldRaw()) { case BC1: { // Branch on coprocessor condition. // Floating point. uint32_t cc = instr->FBccValue(); uint32_t fcsr_cc = get_fcsr_condition_bit(cc); uint32_t cc_value = test_fcsr_bit(fcsr_cc); bool do_branch = (instr->FBtrueValue()) ? cc_value : !cc_value; BranchHelper(do_branch); break; } case BC1EQZ: BranchHelper(!(get_fpu_register(ft_reg) & 0x1)); break; case BC1NEZ: BranchHelper(get_fpu_register(ft_reg) & 0x1); break; default: UNREACHABLE(); } break; // ------------- REGIMM class. case REGIMM: switch (instr->RtFieldRaw()) { case BLTZ: BranchHelper(rs < 0); break; case BGEZ: BranchHelper(rs >= 0); break; case BLTZAL: BranchAndLinkHelper(rs < 0); break; case BGEZAL: BranchAndLinkHelper(rs >= 0); break; default: UNREACHABLE(); } break; // case REGIMM. // ------------- Branch instructions. // When comparing to zero, the encoding of rt field is always 0, so we don't // need to replace rt with zero. case BEQ: BranchHelper(rs == rt); break; case BNE: BranchHelper(rs != rt); break; case POP06: // BLEZALC, BGEZALC, BGEUC, BLEZ (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BLEZALC BranchAndLinkCompactHelper(rt <= 0, 16); } else { if (rs_reg == rt_reg) { // BGEZALC BranchAndLinkCompactHelper(rt >= 0, 16); } else { // BGEUC BranchCompactHelper( static_cast(rs) >= static_cast(rt), 16); } } } else { // BLEZ BranchHelper(rs <= 0); } } else { // BLEZ BranchHelper(rs <= 0); } break; case POP07: // BGTZALC, BLTZALC, BLTUC, BGTZ (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BGTZALC BranchAndLinkCompactHelper(rt > 0, 16); } else { if (rt_reg == rs_reg) { // BLTZALC BranchAndLinkCompactHelper(rt < 0, 16); } else { // BLTUC BranchCompactHelper( static_cast(rs) < static_cast(rt), 16); } } } else { // BGTZ BranchHelper(rs > 0); } } else { // BGTZ BranchHelper(rs > 0); } break; case POP26: // BLEZC, BGEZC, BGEC/BLEC / BLEZL (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BLEZC BranchCompactHelper(rt <= 0, 16); } else { if (rs_reg == rt_reg) { // BGEZC BranchCompactHelper(rt >= 0, 16); } else { // BGEC/BLEC BranchCompactHelper(rs >= rt, 16); } } } } else { // BLEZL BranchAndLinkHelper(rs <= 0); } break; case POP27: // BGTZC, BLTZC, BLTC/BGTC / BGTZL (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BGTZC BranchCompactHelper(rt > 0, 16); } else { if (rs_reg == rt_reg) { // BLTZC BranchCompactHelper(rt < 0, 16); } else { // BLTC/BGTC BranchCompactHelper(rs < rt, 16); } } } } else { // BGTZL BranchAndLinkHelper(rs > 0); } break; case POP66: // BEQZC, JIC if (rs_reg != 0) { // BEQZC BranchCompactHelper(rs == 0, 21); } else { // JIC next_pc = rt + imm16; } break; case POP76: // BNEZC, JIALC if (rs_reg != 0) { // BNEZC BranchCompactHelper(rs != 0, 21); } else { // JIALC set_register(31, get_pc() + Instruction::kInstrSize); next_pc = rt + imm16; } break; case BC: BranchCompactHelper(true, 26); break; case BALC: BranchAndLinkCompactHelper(true, 26); break; case POP10: // BOVC, BEQZALC, BEQC / ADDI (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rs_reg >= rt_reg) { // BOVC if (HaveSameSign(rs, rt)) { if (rs > 0) { BranchCompactHelper(rs > Registers::kMaxValue - rt, 16); } else if (rs < 0) { BranchCompactHelper(rs < Registers::kMinValue - rt, 16); } } } else { if (rs_reg == 0) { // BEQZALC BranchAndLinkCompactHelper(rt == 0, 16); } else { // BEQC BranchCompactHelper(rt == rs, 16); } } } else { // ADDI if (HaveSameSign(rs, se_imm16)) { if (rs > 0) { if (rs <= Registers::kMaxValue - se_imm16) { SignalException(kIntegerOverflow); } } else if (rs < 0) { if (rs >= Registers::kMinValue - se_imm16) { SignalException(kIntegerUnderflow); } } } SetResult(rt_reg, rs + se_imm16); } break; case POP30: // BNVC, BNEZALC, BNEC / DADDI (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rs_reg >= rt_reg) { // BNVC if (!HaveSameSign(rs, rt) || rs == 0 || rt == 0) { BranchCompactHelper(true, 16); } else { if (rs > 0) { BranchCompactHelper(rs <= Registers::kMaxValue - rt, 16); } else if (rs < 0) { BranchCompactHelper(rs >= Registers::kMinValue - rt, 16); } } } else { if (rs_reg == 0) { // BNEZALC BranchAndLinkCompactHelper(rt != 0, 16); } else { // BNEC BranchCompactHelper(rt != rs, 16); } } } break; // ------------- Arithmetic instructions. case ADDIU: SetResult(rt_reg, rs + se_imm16); break; case SLTI: SetResult(rt_reg, rs < se_imm16 ? 1 : 0); break; case SLTIU: SetResult(rt_reg, rs_u < static_cast(se_imm16) ? 1 : 0); break; case ANDI: SetResult(rt_reg, rs & oe_imm16); break; case ORI: SetResult(rt_reg, rs | oe_imm16); break; case XORI: SetResult(rt_reg, rs ^ oe_imm16); break; case LUI: if (rs_reg != 0) { // AUI DCHECK(IsMipsArchVariant(kMips32r6)); SetResult(rt_reg, rs + (se_imm16 << 16)); } else { // LUI SetResult(rt_reg, oe_imm16 << 16); } break; // ------------- Memory instructions. case LB: set_register(rt_reg, ReadB(rs + se_imm16)); break; case LH: set_register(rt_reg, ReadH(rs + se_imm16, instr)); break; case LWL: { // al_offset is offset of the effective address within an aligned word. uint8_t al_offset = (rs + se_imm16) & kPointerAlignmentMask; uint8_t byte_shift = kPointerAlignmentMask - al_offset; uint32_t mask = (1 << byte_shift * 8) - 1; addr = rs + se_imm16 - al_offset; alu_out = ReadW(addr, instr); alu_out <<= byte_shift * 8; alu_out |= rt & mask; set_register(rt_reg, alu_out); break; } case LW: set_register(rt_reg, ReadW(rs + se_imm16, instr)); break; case LBU: set_register(rt_reg, ReadBU(rs + se_imm16)); break; case LHU: set_register(rt_reg, ReadHU(rs + se_imm16, instr)); break; case LWR: { // al_offset is offset of the effective address within an aligned word. uint8_t al_offset = (rs + se_imm16) & kPointerAlignmentMask; uint8_t byte_shift = kPointerAlignmentMask - al_offset; uint32_t mask = al_offset ? (~0 << (byte_shift + 1) * 8) : 0; addr = rs + se_imm16 - al_offset; alu_out = ReadW(addr, instr); alu_out = static_cast (alu_out) >> al_offset * 8; alu_out |= rt & mask; set_register(rt_reg, alu_out); break; } case SB: WriteB(rs + se_imm16, static_cast(rt)); break; case SH: WriteH(rs + se_imm16, 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 } static bool ICacheMatch(void* one, void* two) { DCHECK((reinterpret_cast(one) & CachePage::kPageMask) == 0); DCHECK((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::FlushICache(v8::internal::HashMap* 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(v8::internal::HashMap* i_cache, void* page) { v8::internal::HashMap::Entry* entry = i_cache->LookupOrInsert(page, ICacheHash(page)); if (entry->value == NULL) { 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(v8::internal::HashMap* i_cache, intptr_t start, int size) { DCHECK(size <= CachePage::kPageSize); DCHECK(AllOnOnePage(start, size - 1)); DCHECK((start & CachePage::kLineMask) == 0); DCHECK((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(v8::internal::HashMap* 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), Instruction::kInstrSize)); } else { // Cache miss. Load memory into the cache. memcpy(cached_line, line, CachePage::kLineLength); *cache_valid_byte = CachePage::LINE_VALID; } } void Simulator::Initialize(Isolate* isolate) { if (isolate->simulator_initialized()) return; isolate->set_simulator_initialized(true); ::v8::internal::ExternalReference::set_redirector(isolate, &RedirectExternalReference); } Simulator::Simulator(Isolate* isolate) : isolate_(isolate) { i_cache_ = isolate_->simulator_i_cache(); if (i_cache_ == NULL) { i_cache_ = new v8::internal::HashMap(&ICacheMatch); isolate_->set_simulator_i_cache(i_cache_); } Initialize(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_ = NULL; 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_[i] = 0; } if (IsMipsArchVariant(kMips32r6)) { FCSR_ = kFCSRNaN2008FlagMask; } 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; InitializeCoverage(); last_debugger_input_ = NULL; } Simulator::~Simulator() { free(stack_); } // When the generated code calls an external reference we need to catch that in // the simulator. The external reference will be a function compiled for the // host architecture. We need to call that function instead of trying to // execute it with the simulator. We do that by redirecting the external // reference to a swi (software-interrupt) instruction that is handled by // the simulator. We write the original destination of the jump just at a known // offset from the swi instruction so the simulator knows what to call. class Redirection { public: Redirection(Isolate* isolate, void* external_function, ExternalReference::Type type) : external_function_(external_function), swi_instruction_(rtCallRedirInstr), type_(type), next_(NULL) { next_ = isolate->simulator_redirection(); Simulator::current(isolate)-> FlushICache(isolate->simulator_i_cache(), reinterpret_cast(&swi_instruction_), Instruction::kInstrSize); isolate->set_simulator_redirection(this); } void* address_of_swi_instruction() { return reinterpret_cast(&swi_instruction_); } void* external_function() { return external_function_; } ExternalReference::Type type() { return type_; } static Redirection* Get(Isolate* isolate, void* external_function, ExternalReference::Type type) { Redirection* current = isolate->simulator_redirection(); for (; current != NULL; current = current->next_) { if (current->external_function_ == external_function) return current; } return new Redirection(isolate, external_function, type); } static Redirection* FromSwiInstruction(Instruction* swi_instruction) { char* addr_of_swi = reinterpret_cast(swi_instruction); char* addr_of_redirection = addr_of_swi - offsetof(Redirection, swi_instruction_); return reinterpret_cast(addr_of_redirection); } static void* ReverseRedirection(int32_t reg) { Redirection* redirection = FromSwiInstruction( reinterpret_cast(reinterpret_cast(reg))); return redirection->external_function(); } static void DeleteChain(Redirection* redirection) { while (redirection != nullptr) { Redirection* next = redirection->next_; delete redirection; redirection = next; } } private: void* external_function_; uint32_t swi_instruction_; ExternalReference::Type type_; Redirection* next_; }; // static void Simulator::TearDown(HashMap* i_cache, Redirection* first) { Redirection::DeleteChain(first); if (i_cache != nullptr) { for (HashMap::Entry* entry = i_cache->Start(); entry != nullptr; entry = i_cache->Next(entry)) { delete static_cast(entry->value); } delete i_cache; } } void* Simulator::RedirectExternalReference(Isolate* isolate, void* external_function, ExternalReference::Type type) { Redirection* redirection = Redirection::Get(isolate, external_function, type); return redirection->address_of_swi_instruction(); } // Get the active Simulator for the current thread. Simulator* Simulator::current(Isolate* isolate) { v8::internal::Isolate::PerIsolateThreadData* isolate_data = isolate->FindOrAllocatePerThreadDataForThisThread(); DCHECK(isolate_data != NULL); DCHECK(isolate_data != NULL); Simulator* sim = isolate_data->simulator(); if (sim == NULL) { // 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] = 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]); *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])) + 1; *phiword = value; } void Simulator::set_fpu_register_float(int fpureg, float value) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg]) = value; } void Simulator::set_fpu_register_double(int fpureg, double value) { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg]) = 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 { DCHECK(IsFp64Mode()); DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return FPUregisters_[fpureg]; } int32_t Simulator::get_fpu_register_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg] & 0xffffffff); } int32_t Simulator::get_fpu_register_signed_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg] & 0xffffffff); } int32_t Simulator::get_fpu_register_hi_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast((FPUregisters_[fpureg] >> 32) & 0xffffffff); } float Simulator::get_fpu_register_float(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(const_cast(&FPUregisters_[fpureg])); } double Simulator::get_fpu_register_double(int fpureg) const { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(&FPUregisters_[fpureg]); } 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); } } // 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; } unsigned int Simulator::get_fcsr_rounding_mode() { return FCSR_ & 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--; } 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--; } 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(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--; } 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--; } 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) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%08x", value); } } // TODO(plind): consider making icount_ printing a flag option. void Simulator::TraceMemRd(int32_t addr, int32_t value) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%08x <-- [%08x] (%" PRIu64 ")", value, addr, icount_); } } void Simulator::TraceMemWr(int32_t addr, int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case BYTE: SNPrintF(trace_buf_, " %02x --> [%08x]", static_cast(value), addr); break; case HALF: SNPrintF(trace_buf_, " %04x --> [%08x]", static_cast(value), addr); break; case WORD: SNPrintF(trace_buf_, "%08x --> [%08x]", value, addr); break; } } } int Simulator::ReadW(int32_t addr, Instruction* instr) { if (addr >=0 && addr < 0x400) { // This has to be a NULL-dereference, drop into debugger. PrintF("Memory read from bad address: 0x%08x, pc=0x%08x\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0) { intptr_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); 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 NULL-dereference, drop into debugger. PrintF("Memory write to bad address: 0x%08x, pc=0x%08x\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0) { 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) { 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) { 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) { 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) { 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) { 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) { 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; } // 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%08x: %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); // 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(Instruction* instr) { // 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::FromSwiInstruction(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]; 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", FUNCTION_ADDR(generic_target), dval0, dval1); break; case ExternalReference::BUILTIN_FP_CALL: PrintF("Call to host function at %p with arg %f", FUNCTION_ADDR(generic_target), dval0); break; case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Call to host function at %p with args %f, %d", 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 { 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\n", FUNCTION_ADDR(target), arg0, arg1, arg2, arg3, arg4, arg5); } int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5); 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); } } 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); } else { set_pc(get_pc() + 2 * Instruction::kInstrSize); } } 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(code <= kMaxStopCode); DCHECK(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(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) { V8_Fatal(__FILE__, __LINE__, "Error: Exception %i raised.", static_cast(e)); } void Simulator::DecodeTypeRegisterDRsType() { double ft, fs, fd; uint32_t cc, fcsr_cc; int64_t i64; fs = get_fpu_register_double(fs_reg()); ft = (get_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 = get_instr()->FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (get_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; } set_fpu_register_double(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_double(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_double(fd_reg(), (ft_int & 0x1) == 0 ? fs : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_double(fd_reg(), (ft_int & 0x1) != 0 ? fs : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { set_fpu_register_double(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); int32_t rt_reg = get_instr()->RtValue(); int32_t rt = get_register(rt_reg); if (rt != 0) { set_fpu_register_double(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 (get_instr()->Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) set_fpu_register_double(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) set_fpu_register_double(fd_reg(), fs); } break; } case MIN: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { set_fpu_register_double(fd_reg(), (fs >= ft) ? ft : fs); } break; case MINA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { double result; if (fabs(fs) > fabs(ft)) { result = ft; } else if (fabs(fs) < fabs(ft)) { result = fs; } else { result = (fs < ft ? fs : ft); } set_fpu_register_double(fd_reg(), result); } break; case MAXA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { double result; if (fabs(fs) < fabs(ft)) { result = ft; } else if (fabs(fs) > fabs(ft)) { result = fs; } else { result = (fs > ft ? fs : ft); } set_fpu_register_double(fd_reg(), result); } break; case MAX: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { set_fpu_register_double(fd_reg(), (fs <= ft) ? ft : fs); } break; break; case ADD_D: set_fpu_register_double(fd_reg(), fs + ft); break; case SUB_D: set_fpu_register_double(fd_reg(), fs - ft); break; case MUL_D: set_fpu_register_double(fd_reg(), fs * ft); break; case DIV_D: set_fpu_register_double(fd_reg(), fs / ft); break; case ABS_D: set_fpu_register_double(fd_reg(), fabs(fs)); break; case MOV_D: set_fpu_register_double(fd_reg(), fs); break; case NEG_D: set_fpu_register_double(fd_reg(), -fs); break; case SQRT_D: lazily_initialize_fast_sqrt(isolate_); set_fpu_register_double(fd_reg(), fast_sqrt(fs, isolate_)); break; case RSQRT_D: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); lazily_initialize_fast_sqrt(isolate_); double result = 1.0 / fast_sqrt(fs, isolate_); set_fpu_register_double(fd_reg(), result); break; } case RECIP_D: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double result = 1.0 / fs; set_fpu_register_double(fd_reg(), result); break; } case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); break; case CVT_W_D: { // Convert double to word. double rounded; int32_t result; round_according_to_fcsr(fs, rounded, result, fs); set_fpu_register_word(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--; } set_fpu_register_word(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); set_fpu_register_word(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); set_fpu_register_word(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); set_fpu_register_word(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). set_fpu_register_float(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); set_fpu_register(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()) { set_fpu_register(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()) { set_fpu_register(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()) { set_fpu_register(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()) { set_fpu_register(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(result != 0); dResult = bit_cast(result); set_fpu_register_double(fd_reg(), dResult); break; } case C_F_D: { set_fcsr_bit(fcsr_cc, false); 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 (get_instr()->FunctionFieldRaw()) { case CVT_S_W: // Convert word to float (single). alu_out = get_fpu_register_signed_word(fs_reg()); set_fpu_register_float(fd_reg(), static_cast(alu_out)); break; case CVT_D_W: // Convert word to double. alu_out = get_fpu_register_signed_word(fs_reg()); set_fpu_register_double(fd_reg(), static_cast(alu_out)); break; case CMP_AF: set_fpu_register_word(fd_reg(), 0); break; case CMP_UN: if (std::isnan(fs) || std::isnan(ft)) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_EQ: if (fs == ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_UEQ: if ((fs == ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_LT: if (fs < ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_ULT: if ((fs < ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_LE: if (fs <= ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_ULE: if ((fs <= ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_OR: if (!std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_UNE: if ((fs != ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_NE: if (fs != ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(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 = get_instr()->FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (get_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; } set_fpu_register_float(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case ADD_S: set_fpu_register_float(fd_reg(), fs + ft); break; case SUB_S: set_fpu_register_float(fd_reg(), fs - ft); break; case MUL_S: set_fpu_register_float(fd_reg(), fs * ft); break; case DIV_S: set_fpu_register_float(fd_reg(), fs / ft); break; case ABS_S: set_fpu_register_float(fd_reg(), fabs(fs)); break; case MOV_S: set_fpu_register_float(fd_reg(), fs); break; case NEG_S: set_fpu_register_float(fd_reg(), -fs); break; case SQRT_S: lazily_initialize_fast_sqrt(isolate_); set_fpu_register_float(fd_reg(), fast_sqrt(fs, isolate_)); break; case RSQRT_S: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); lazily_initialize_fast_sqrt(isolate_); float result = 1.0 / fast_sqrt(fs, isolate_); set_fpu_register_float(fd_reg(), result); break; } case RECIP_S: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float result = 1.0 / fs; set_fpu_register_float(fd_reg(), result); break; } case C_F_D: set_fcsr_bit(fcsr_cc, false); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); break; case CVT_D_S: set_fpu_register_double(fd_reg(), static_cast(fs)); break; case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_float(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(result != 0); fResult = bit_cast(result); set_fpu_register_float(fd_reg(), fResult); break; } case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_float(fd_reg(), (ft_int & 0x1) == 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_float(fd_reg(), (ft_int & 0x1) != 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { set_fpu_register_float(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() != 0) { set_fpu_register_float(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 (get_instr()->Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) set_fpu_register_float(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) set_fpu_register_float(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); set_fpu_register_word(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()) { set_fpu_register(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(rounded); set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case FLOOR_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float rounded = std::floor(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case ROUND_W_S: { float 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--; } set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } break; } case ROUND_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float 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()) { set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CEIL_W_S: // Round double to word towards positive infinity. { float rounded = std::ceil(fs); int32_t result = static_cast(rounded); set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case CEIL_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float rounded = std::ceil(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case MIN: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { set_fpu_register_float(fd_reg(), (fs >= ft) ? ft : fs); } break; case MAX: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { set_fpu_register_float(fd_reg(), (fs <= ft) ? ft : fs); } break; case MINA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { float result; if (fabs(fs) > fabs(ft)) { result = ft; } else if (fabs(fs) < fabs(ft)) { result = fs; } else { result = (fs < ft ? fs : ft); } set_fpu_register_float(fd_reg(), result); } break; case MAXA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { float result; if (fabs(fs) < fabs(ft)) { result = ft; } else if (fabs(fs) > fabs(ft)) { result = fs; } else { result = (fs > ft ? fs : ft); } set_fpu_register_float(fd_reg(), result); } break; case CVT_L_S: { if (IsFp64Mode()) { int64_t result; float rounded; round64_according_to_fcsr(fs, rounded, result, fs); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CVT_W_S: { float rounded; int32_t result; round_according_to_fcsr(fs, rounded, result, fs); set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } break; } default: // CVT_W_S CVT_L_S ROUND_W_S ROUND_L_S FLOOR_W_S FLOOR_L_S // CEIL_W_S CEIL_L_S CVT_PS_S are unimplemented. UNREACHABLE(); } } void Simulator::DecodeTypeRegisterLRsType() { double fs = get_fpu_register_double(fs_reg()); double ft = get_fpu_register_double(ft_reg()); switch (get_instr()->FunctionFieldRaw()) { case CVT_D_L: // Mips32r2 instruction. // Watch the signs here, we want 2 32-bit vals // to make a sign-64. int64_t i64; if (IsFp64Mode()) { i64 = get_fpu_register(fs_reg()); } else { i64 = static_cast(get_fpu_register_word(fs_reg())); i64 |= static_cast(get_fpu_register_word(fs_reg() + 1)) << 32; } set_fpu_register_double(fd_reg(), static_cast(i64)); break; case CVT_S_L: if (IsFp64Mode()) { i64 = get_fpu_register(fs_reg()); } else { i64 = static_cast(get_fpu_register_word(fs_reg())); i64 |= static_cast(get_fpu_register_word(fs_reg() + 1)) << 32; } set_fpu_register_float(fd_reg(), static_cast(i64)); break; case CMP_AF: // Mips64r6 CMP.D instructions. set_fpu_register(fd_reg(), 0); break; case CMP_UN: if (std::isnan(fs) || std::isnan(ft)) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_EQ: if (fs == ft) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_UEQ: if ((fs == ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_LT: if (fs < ft) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_ULT: if ((fs < ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_LE: if (fs <= ft) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_ULE: if ((fs <= ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_OR: if (!std::isnan(fs) && !std::isnan(ft)) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_UNE: if ((fs != ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_NE: if (fs != ft && (!std::isnan(fs) && !std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterCOP1() { switch (get_instr()->RsFieldRaw()) { case CFC1: // At the moment only FCSR is supported. DCHECK(fs_reg() == kFCSRRegister); set_register(rt_reg(), FCSR_); break; case MFC1: set_register(rt_reg(), get_fpu_register_word(fs_reg())); break; case MFHC1: set_register(rt_reg(), get_fpu_register_hi_word(fs_reg())); break; case CTC1: { // At the moment only FCSR is supported. DCHECK(fs_reg() == kFCSRRegister); int32_t reg = registers_[rt_reg()]; if (IsMipsArchVariant(kMips32r6)) { FCSR_ = reg | kFCSRNaN2008FlagMask; } else { DCHECK(IsMipsArchVariant(kMips32r1) || IsMipsArchVariant(kMips32r2)); FCSR_ = reg & ~kFCSRNaN2008FlagMask; } break; } case MTC1: // Hardware writes upper 32-bits to zero on mtc1. set_fpu_register_hi_word(fs_reg(), 0); set_fpu_register_word(fs_reg(), registers_[rt_reg()]); break; case MTHC1: set_fpu_register_hi_word(fs_reg(), registers_[rt_reg()]); break; case S: { DecodeTypeRegisterSRsType(); break; } case D: DecodeTypeRegisterDRsType(); break; case W: DecodeTypeRegisterWRsType(); break; case L: DecodeTypeRegisterLRsType(); break; case PS: // Not implemented. UNREACHABLE(); default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterCOP1X() { switch (get_instr()->FunctionFieldRaw()) { case MADD_D: double fr, ft, fs; fr = get_fpu_register_double(fr_reg()); fs = get_fpu_register_double(fs_reg()); ft = get_fpu_register_double(ft_reg()); set_fpu_register_double(fd_reg(), fs * ft + fr); break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterSPECIAL() { int64_t alu_out = 0x12345678; int64_t i64hilo = 0; uint64_t u64hilo = 0; bool do_interrupt = false; switch (get_instr()->FunctionFieldRaw()) { case SELEQZ_S: DCHECK(IsMipsArchVariant(kMips32r6)); set_register(rd_reg(), rt() == 0 ? rs() : 0); break; case SELNEZ_S: DCHECK(IsMipsArchVariant(kMips32r6)); set_register(rd_reg(), rt() != 0 ? rs() : 0); break; case JR: { int32_t next_pc = rs(); int32_t current_pc = get_pc(); Instruction* branch_delay_instr = reinterpret_cast(current_pc + Instruction::kInstrSize); BranchDelayInstructionDecode(branch_delay_instr); set_pc(next_pc); pc_modified_ = true; break; } case JALR: { int32_t next_pc = rs(); int32_t return_addr_reg = rd_reg(); int32_t current_pc = get_pc(); Instruction* branch_delay_instr = reinterpret_cast(current_pc + Instruction::kInstrSize); BranchDelayInstructionDecode(branch_delay_instr); set_register(return_addr_reg, current_pc + 2 * Instruction::kInstrSize); set_pc(next_pc); pc_modified_ = true; break; } case SLL: alu_out = rt() << sa(); SetResult(rd_reg(), static_cast(alu_out)); break; case SRL: if (rs_reg() == 0) { // Regular logical right shift of a word by a fixed number of // bits instruction. RS field is always equal to 0. alu_out = rt_u() >> sa(); } else { // Logical right-rotate of a word by a fixed number of bits. This // is special case of SRL instruction, added in MIPS32 Release 2. // RS field is equal to 00001. alu_out = base::bits::RotateRight32(rt_u(), sa()); } SetResult(rd_reg(), static_cast(alu_out)); break; case SRA: alu_out = rt() >> sa(); SetResult(rd_reg(), static_cast(alu_out)); break; case SLLV: alu_out = rt() << rs(); SetResult(rd_reg(), static_cast(alu_out)); break; case SRLV: if (sa() == 0) { // Regular logical right-shift of a word by a variable number of // bits instruction. SA field is always equal to 0. alu_out = rt_u() >> rs(); } else { // Logical right-rotate of a word by a variable number of bits. // This is special case od SRLV instruction, added in MIPS32 // Release 2. SA field is equal to 00001. alu_out = base::bits::RotateRight32(rt_u(), rs_u()); } SetResult(rd_reg(), static_cast(alu_out)); break; case SRAV: SetResult(rd_reg(), rt() >> rs()); break; case LSA: { DCHECK(IsMipsArchVariant(kMips32r6)); int8_t sa = lsa_sa() + 1; int32_t _rt = rt(); int32_t _rs = rs(); int32_t res = _rs << sa; res += _rt; DCHECK_EQ(res, (rs() << (lsa_sa() + 1)) + rt()); SetResult(rd_reg(), (rs() << (lsa_sa() + 1)) + rt()); break; } case MFHI: // MFHI == CLZ on R6. if (!IsMipsArchVariant(kMips32r6)) { DCHECK(sa() == 0); alu_out = get_register(HI); } else { // MIPS spec: If no bits were set in GPR rs, the result written to // GPR rd is 32. DCHECK(sa() == 1); alu_out = base::bits::CountLeadingZeros32(rs_u()); } SetResult(rd_reg(), static_cast(alu_out)); break; case MFLO: alu_out = get_register(LO); SetResult(rd_reg(), static_cast(alu_out)); break; // Instructions using HI and LO registers. case MULT: i64hilo = static_cast(rs()) * static_cast(rt()); if (!IsMipsArchVariant(kMips32r6)) { set_register(LO, static_cast(i64hilo & 0xffffffff)); set_register(HI, static_cast(i64hilo >> 32)); } else { switch (sa()) { case MUL_OP: set_register(rd_reg(), static_cast(i64hilo & 0xffffffff)); break; case MUH_OP: set_register(rd_reg(), static_cast(i64hilo >> 32)); break; default: UNIMPLEMENTED_MIPS(); break; } } break; case MULTU: u64hilo = static_cast(rs_u()) * static_cast(rt_u()); if (!IsMipsArchVariant(kMips32r6)) { set_register(LO, static_cast(u64hilo & 0xffffffff)); set_register(HI, static_cast(u64hilo >> 32)); } else { switch (sa()) { case MUL_OP: set_register(rd_reg(), static_cast(u64hilo & 0xffffffff)); break; case MUH_OP: set_register(rd_reg(), static_cast(u64hilo >> 32)); break; default: UNIMPLEMENTED_MIPS(); break; } } break; case DIV: if (IsMipsArchVariant(kMips32r6)) { switch (get_instr()->SaValue()) { case DIV_OP: if (rs() == INT_MIN && rt() == -1) { set_register(rd_reg(), INT_MIN); } else if (rt() != 0) { set_register(rd_reg(), rs() / rt()); } break; case MOD_OP: if (rs() == INT_MIN && rt() == -1) { set_register(rd_reg(), 0); } else if (rt() != 0) { set_register(rd_reg(), rs() % rt()); } break; default: UNIMPLEMENTED_MIPS(); break; } } else { // Divide by zero and overflow was not checked in the // configuration step - div and divu do not raise exceptions. On // division by 0 the result will be UNPREDICTABLE. On overflow // (INT_MIN/-1), return INT_MIN which is what the hardware does. if (rs() == INT_MIN && rt() == -1) { set_register(LO, INT_MIN); set_register(HI, 0); } else if (rt() != 0) { set_register(LO, rs() / rt()); set_register(HI, rs() % rt()); } } break; case DIVU: if (IsMipsArchVariant(kMips32r6)) { switch (get_instr()->SaValue()) { case DIV_OP: if (rt_u() != 0) { set_register(rd_reg(), rs_u() / rt_u()); } break; case MOD_OP: if (rt_u() != 0) { set_register(rd_reg(), rs_u() % rt_u()); } break; default: UNIMPLEMENTED_MIPS(); break; } } else { if (rt_u() != 0) { set_register(LO, rs_u() / rt_u()); set_register(HI, rs_u() % rt_u()); } } break; case ADD: if (HaveSameSign(rs(), rt())) { if (rs() > 0) { if (rs() <= (Registers::kMaxValue - rt())) { SignalException(kIntegerOverflow); } } else if (rs() < 0) { if (rs() >= (Registers::kMinValue - rt())) { SignalException(kIntegerUnderflow); } } } SetResult(rd_reg(), rs() + rt()); break; case ADDU: SetResult(rd_reg(), rs() + rt()); break; case SUB: if (!HaveSameSign(rs(), rt())) { if (rs() > 0) { if (rs() <= (Registers::kMaxValue + rt())) { SignalException(kIntegerOverflow); } } else if (rs() < 0) { if (rs() >= (Registers::kMinValue + rt())) { SignalException(kIntegerUnderflow); } } } SetResult(rd_reg(), rs() - rt()); break; case SUBU: SetResult(rd_reg(), rs() - rt()); break; case AND: SetResult(rd_reg(), rs() & rt()); break; case OR: SetResult(rd_reg(), rs() | rt()); break; case XOR: SetResult(rd_reg(), rs() ^ rt()); break; case NOR: SetResult(rd_reg(), ~(rs() | rt())); break; case SLT: SetResult(rd_reg(), rs() < rt() ? 1 : 0); break; case SLTU: SetResult(rd_reg(), rs_u() < rt_u() ? 1 : 0); break; // Break and trap instructions. case BREAK: do_interrupt = true; break; case TGE: do_interrupt = rs() >= rt(); break; case TGEU: do_interrupt = rs_u() >= rt_u(); break; case TLT: do_interrupt = rs() < rt(); break; case TLTU: do_interrupt = rs_u() < rt_u(); break; case TEQ: do_interrupt = rs() == rt(); break; case TNE: do_interrupt = rs() != rt(); break; // Conditional moves. case MOVN: if (rt()) { set_register(rd_reg(), rs()); TraceRegWr(rs()); } break; case MOVCI: { uint32_t cc = get_instr()->FBccValue(); uint32_t fcsr_cc = get_fcsr_condition_bit(cc); if (get_instr()->Bit(16)) { // Read Tf bit. if (test_fcsr_bit(fcsr_cc)) set_register(rd_reg(), rs()); } else { if (!test_fcsr_bit(fcsr_cc)) set_register(rd_reg(), rs()); } break; } case MOVZ: if (!rt()) { set_register(rd_reg(), rs()); TraceRegWr(rs()); } break; default: UNREACHABLE(); } if (do_interrupt) { SoftwareInterrupt(get_instr()); } } void Simulator::DecodeTypeRegisterSPECIAL2() { int32_t alu_out; switch (get_instr()->FunctionFieldRaw()) { case MUL: // Only the lower 32 bits are kept. alu_out = rs_u() * rt_u(); // HI and LO are UNPREDICTABLE after the operation. set_register(LO, Unpredictable); set_register(HI, Unpredictable); break; case CLZ: // MIPS32 spec: If no bits were set in GPR rs, the result written to // GPR rd is 32. alu_out = base::bits::CountLeadingZeros32(rs_u()); break; default: alu_out = 0x12345678; UNREACHABLE(); } SetResult(rd_reg(), alu_out); } void Simulator::DecodeTypeRegisterSPECIAL3() { int32_t alu_out; switch (get_instr()->FunctionFieldRaw()) { case INS: { // Mips32r2 instruction. // Interpret rd field as 5-bit msb of insert. uint16_t msb = rd_reg(); // Interpret sa field as 5-bit lsb of insert. uint16_t lsb = sa(); uint16_t size = msb - lsb + 1; uint32_t mask = (1 << size) - 1; alu_out = (rt_u() & ~(mask << lsb)) | ((rs_u() & mask) << lsb); // Ins instr leaves result in Rt, rather than Rd. SetResult(rt_reg(), alu_out); break; } case EXT: { // Mips32r2 instruction. // Interpret rd field as 5-bit msb of extract. uint16_t msb = rd_reg(); // Interpret sa field as 5-bit lsb of extract. uint16_t lsb = sa(); uint16_t size = msb + 1; uint32_t mask = (1 << size) - 1; alu_out = (rs_u() & (mask << lsb)) >> lsb; SetResult(rt_reg(), alu_out); break; } case BSHFL: { int sa = get_instr()->SaFieldRaw() >> kSaShift; switch (sa) { case BITSWAP: { uint32_t input = static_cast(rt()); uint32_t output = 0; uint8_t i_byte, o_byte; // Reverse the bit in byte for each individual byte for (int i = 0; i < 4; i++) { output = output >> 8; i_byte = input & 0xff; // Fast way to reverse bits in byte // Devised by Sean Anderson, July 13, 2001 o_byte = static_cast(((i_byte * 0x0802LU & 0x22110LU) | (i_byte * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16); output = output | (static_cast(o_byte << 24)); input = input >> 8; } alu_out = static_cast(output); break; } case SEB: case SEH: case WSBH: alu_out = 0x12345678; UNREACHABLE(); break; default: { const uint8_t bp = get_instr()->Bp2Value(); sa >>= kBp2Bits; switch (sa) { case ALIGN: { if (bp == 0) { alu_out = static_cast(rt()); } else { uint32_t rt_hi = rt() << (8 * bp); uint32_t rs_lo = rs() >> (8 * (4 - bp)); alu_out = static_cast(rt_hi | rs_lo); } break; } default: alu_out = 0x12345678; UNREACHABLE(); break; } } } SetResult(rd_reg(), alu_out); break; } default: UNREACHABLE(); } } void Simulator::DecodeTypeRegister(Instruction* instr) { const Opcode op = instr->OpcodeFieldRaw(); // Set up the variables if needed before executing the instruction. // ConfigureTypeRegister(instr); set_instr(instr); // ---------- Execution. switch (op) { case COP1: DecodeTypeRegisterCOP1(); break; case COP1X: DecodeTypeRegisterCOP1X(); break; case SPECIAL: DecodeTypeRegisterSPECIAL(); break; case SPECIAL2: DecodeTypeRegisterSPECIAL2(); break; case SPECIAL3: DecodeTypeRegisterSPECIAL3(); break; default: UNREACHABLE(); } } // Type 2: instructions using a 16, 21 or 26 bits immediate. (e.g. beq, beqc). void Simulator::DecodeTypeImmediate(Instruction* instr) { // Instruction fields. Opcode op = instr->OpcodeFieldRaw(); int32_t rs_reg = instr->RsValue(); int32_t rs = get_register(instr->RsValue()); uint32_t rs_u = static_cast(rs); int32_t rt_reg = instr->RtValue(); // Destination register. int32_t rt = get_register(rt_reg); int16_t imm16 = instr->Imm16Value(); int32_t ft_reg = instr->FtValue(); // Destination register. // Zero extended immediate. uint32_t oe_imm16 = 0xffff & imm16; // Sign extended immediate. int32_t se_imm16 = imm16; // Next pc. int32_t next_pc = bad_ra; // Used for conditional branch instructions. bool execute_branch_delay_instruction = false; // Used for arithmetic instructions. int32_t alu_out = 0; // Used for memory instructions. int32_t addr = 0x0; // Branch instructions common part. auto BranchAndLinkHelper = [this, instr, &next_pc, &execute_branch_delay_instruction]( bool do_branch) { execute_branch_delay_instruction = true; int32_t current_pc = get_pc(); if (do_branch) { int16_t imm16 = instr->Imm16Value(); next_pc = current_pc + (imm16 << 2) + Instruction::kInstrSize; set_register(31, current_pc + 2 * Instruction::kInstrSize); } else { next_pc = current_pc + 2 * Instruction::kInstrSize; } }; auto BranchHelper = [this, instr, &next_pc, &execute_branch_delay_instruction](bool do_branch) { execute_branch_delay_instruction = true; int32_t current_pc = get_pc(); if (do_branch) { int16_t imm16 = instr->Imm16Value(); next_pc = current_pc + (imm16 << 2) + Instruction::kInstrSize; } else { next_pc = current_pc + 2 * Instruction::kInstrSize; } }; auto BranchAndLinkCompactHelper = [this, instr, &next_pc](bool do_branch, int bits) { int32_t current_pc = get_pc(); CheckForbiddenSlot(current_pc); if (do_branch) { int32_t imm = instr->ImmValue(bits); imm <<= 32 - bits; imm >>= 32 - bits; next_pc = current_pc + (imm << 2) + Instruction::kInstrSize; set_register(31, current_pc + Instruction::kInstrSize); } }; auto BranchCompactHelper = [&next_pc, this, instr](bool do_branch, int bits) { int32_t current_pc = get_pc(); CheckForbiddenSlot(current_pc); if (do_branch) { int32_t imm = instr->ImmValue(bits); imm <<= 32 - bits; imm >>= 32 - bits; next_pc = get_pc() + (imm << 2) + Instruction::kInstrSize; } }; switch (op) { // ------------- COP1. Coprocessor instructions. case COP1: switch (instr->RsFieldRaw()) { case BC1: { // Branch on coprocessor condition. // Floating point. uint32_t cc = instr->FBccValue(); uint32_t fcsr_cc = get_fcsr_condition_bit(cc); uint32_t cc_value = test_fcsr_bit(fcsr_cc); bool do_branch = (instr->FBtrueValue()) ? cc_value : !cc_value; BranchHelper(do_branch); break; } case BC1EQZ: BranchHelper(!(get_fpu_register(ft_reg) & 0x1)); break; case BC1NEZ: BranchHelper(get_fpu_register(ft_reg) & 0x1); break; default: UNREACHABLE(); } break; // ------------- REGIMM class. case REGIMM: switch (instr->RtFieldRaw()) { case BLTZ: BranchHelper(rs < 0); break; case BGEZ: BranchHelper(rs >= 0); break; case BLTZAL: BranchAndLinkHelper(rs < 0); break; case BGEZAL: BranchAndLinkHelper(rs >= 0); break; default: UNREACHABLE(); } break; // case REGIMM. // ------------- Branch instructions. // When comparing to zero, the encoding of rt field is always 0, so we don't // need to replace rt with zero. case BEQ: BranchHelper(rs == rt); break; case BNE: BranchHelper(rs != rt); break; case POP06: // BLEZALC, BGEZALC, BGEUC, BLEZ (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BLEZALC BranchAndLinkCompactHelper(rt <= 0, 16); } else { if (rs_reg == rt_reg) { // BGEZALC BranchAndLinkCompactHelper(rt >= 0, 16); } else { // BGEUC BranchCompactHelper( static_cast(rs) >= static_cast(rt), 16); } } } else { // BLEZ BranchHelper(rs <= 0); } } else { // BLEZ BranchHelper(rs <= 0); } break; case POP07: // BGTZALC, BLTZALC, BLTUC, BGTZ (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BGTZALC BranchAndLinkCompactHelper(rt > 0, 16); } else { if (rt_reg == rs_reg) { // BLTZALC BranchAndLinkCompactHelper(rt < 0, 16); } else { // BLTUC BranchCompactHelper( static_cast(rs) < static_cast(rt), 16); } } } else { // BGTZ BranchHelper(rs > 0); } } else { // BGTZ BranchHelper(rs > 0); } break; case POP26: // BLEZC, BGEZC, BGEC/BLEC / BLEZL (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BLEZC BranchCompactHelper(rt <= 0, 16); } else { if (rs_reg == rt_reg) { // BGEZC BranchCompactHelper(rt >= 0, 16); } else { // BGEC/BLEC BranchCompactHelper(rs >= rt, 16); } } } } else { // BLEZL BranchAndLinkHelper(rs <= 0); } break; case POP27: // BGTZC, BLTZC, BLTC/BGTC / BGTZL (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BGTZC BranchCompactHelper(rt > 0, 16); } else { if (rs_reg == rt_reg) { // BLTZC BranchCompactHelper(rt < 0, 16); } else { // BLTC/BGTC BranchCompactHelper(rs < rt, 16); } } } } else { // BGTZL BranchAndLinkHelper(rs > 0); } break; case POP66: // BEQZC, JIC if (rs_reg != 0) { // BEQZC BranchCompactHelper(rs == 0, 21); } else { // JIC next_pc = rt + imm16; } break; case POP76: // BNEZC, JIALC if (rs_reg != 0) { // BNEZC BranchCompactHelper(rs != 0, 21); } else { // JIALC set_register(31, get_pc() + Instruction::kInstrSize); next_pc = rt + imm16; } break; case BC: BranchCompactHelper(true, 26); break; case BALC: BranchAndLinkCompactHelper(true, 26); break; case POP10: // BOVC, BEQZALC, BEQC / ADDI (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rs_reg >= rt_reg) { // BOVC if (HaveSameSign(rs, rt)) { if (rs > 0) { BranchCompactHelper(rs > Registers::kMaxValue - rt, 16); } else if (rs < 0) { BranchCompactHelper(rs < Registers::kMinValue - rt, 16); } } } else { if (rs_reg == 0) { // BEQZALC BranchAndLinkCompactHelper(rt == 0, 16); } else { // BEQC BranchCompactHelper(rt == rs, 16); } } } else { // ADDI if (HaveSameSign(rs, se_imm16)) { if (rs > 0) { if (rs <= Registers::kMaxValue - se_imm16) { SignalException(kIntegerOverflow); } } else if (rs < 0) { if (rs >= Registers::kMinValue - se_imm16) { SignalException(kIntegerUnderflow); } } } SetResult(rt_reg, rs + se_imm16); } break; case POP30: // BNVC, BNEZALC, BNEC / DADDI (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rs_reg >= rt_reg) { // BNVC if (!HaveSameSign(rs, rt) || rs == 0 || rt == 0) { BranchCompactHelper(true, 16); } else { if (rs > 0) { BranchCompactHelper(rs <= Registers::kMaxValue - rt, 16); } else if (rs < 0) { BranchCompactHelper(rs >= Registers::kMinValue - rt, 16); } } } else { if (rs_reg == 0) { // BNEZALC BranchAndLinkCompactHelper(rt != 0, 16); } else { // BNEC BranchCompactHelper(rt != rs, 16); } } } break; // ------------- Arithmetic instructions. case ADDIU: SetResult(rt_reg, rs + se_imm16); break; case SLTI: SetResult(rt_reg, rs < se_imm16 ? 1 : 0); break; case SLTIU: SetResult(rt_reg, rs_u < static_cast(se_imm16) ? 1 : 0); break; case ANDI: SetResult(rt_reg, rs & oe_imm16); break; case ORI: SetResult(rt_reg, rs | oe_imm16); break; case XORI: SetResult(rt_reg, rs ^ oe_imm16); break; case LUI: if (rs_reg != 0) { // AUI DCHECK(IsMipsArchVariant(kMips32r6)); SetResult(rt_reg, rs + (se_imm16 << 16)); } else { // LUI SetResult(rt_reg, oe_imm16 << 16); } break; // ------------- Memory instructions. case LB: set_register(rt_reg, ReadB(rs + se_imm16)); break; case LH: set_register(rt_reg, ReadH(rs + se_imm16, instr)); break; case LWL: { // al_offset is offset of the effective address within an aligned word. uint8_t al_offset = (rs + se_imm16) & kPointerAlignmentMask; uint8_t byte_shift = kPointerAlignmentMask - al_offset; uint32_t mask = (1 << byte_shift * 8) - 1; addr = rs + se_imm16 - al_offset; alu_out = ReadW(addr, instr); alu_out <<= byte_shift * 8; alu_out |= rt & mask; set_register(rt_reg, alu_out); break; } case LW: set_register(rt_reg, ReadW(rs + se_imm16, instr)); break; case LBU: set_register(rt_reg, ReadBU(rs + se_imm16)); break; case LHU: set_register(rt_reg, ReadHU(rs + se_imm16, instr)); break; case LWR: { // al_offset is offset of the effective address within an aligned word. uint8_t al_offset = (rs + se_imm16) & kPointerAlignmentMask; uint8_t byte_shift = kPointerAlignmentMask - al_offset; uint32_t mask = al_offset ? (~0 << (byte_shift + 1) * 8) : 0; addr = rs + se_imm16 - al_offset; alu_out = ReadW(addr, instr); alu_out = static_cast (alu_out) >> al_offset * 8; alu_out |= rt & mask; set_register(rt_reg, alu_out); break; } case SB: WriteB(rs + se_imm16, static_cast(rt)); break; case SH: WriteH(rs + se_imm16, 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 } static bool ICacheMatch(void* one, void* two) { DCHECK((reinterpret_cast(one) & CachePage::kPageMask) == 0); DCHECK((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::FlushICache(v8::internal::HashMap* 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(v8::internal::HashMap* i_cache, void* page) { v8::internal::HashMap::Entry* entry = i_cache->LookupOrInsert(page, ICacheHash(page)); if (entry->value == NULL) { 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(v8::internal::HashMap* i_cache, intptr_t start, int size) { DCHECK(size <= CachePage::kPageSize); DCHECK(AllOnOnePage(start, size - 1)); DCHECK((start & CachePage::kLineMask) == 0); DCHECK((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(v8::internal::HashMap* 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), Instruction::kInstrSize)); } else { // Cache miss. Load memory into the cache. memcpy(cached_line, line, CachePage::kLineLength); *cache_valid_byte = CachePage::LINE_VALID; } } void Simulator::Initialize(Isolate* isolate) { if (isolate->simulator_initialized()) return; isolate->set_simulator_initialized(true); ::v8::internal::ExternalReference::set_redirector(isolate, &RedirectExternalReference); } Simulator::Simulator(Isolate* isolate) : isolate_(isolate) { i_cache_ = isolate_->simulator_i_cache(); if (i_cache_ == NULL) { i_cache_ = new v8::internal::HashMap(&ICacheMatch); isolate_->set_simulator_i_cache(i_cache_); } Initialize(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_ = NULL; 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_[i] = 0; } if (IsMipsArchVariant(kMips32r6)) { FCSR_ = kFCSRNaN2008FlagMask; } 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; InitializeCoverage(); last_debugger_input_ = NULL; } Simulator::~Simulator() { free(stack_); } // When the generated code calls an external reference we need to catch that in // the simulator. The external reference will be a function compiled for the // host architecture. We need to call that function instead of trying to // execute it with the simulator. We do that by redirecting the external // reference to a swi (software-interrupt) instruction that is handled by // the simulator. We write the original destination of the jump just at a known // offset from the swi instruction so the simulator knows what to call. class Redirection { public: Redirection(Isolate* isolate, void* external_function, ExternalReference::Type type) : external_function_(external_function), swi_instruction_(rtCallRedirInstr), type_(type), next_(NULL) { next_ = isolate->simulator_redirection(); Simulator::current(isolate)-> FlushICache(isolate->simulator_i_cache(), reinterpret_cast(&swi_instruction_), Instruction::kInstrSize); isolate->set_simulator_redirection(this); } void* address_of_swi_instruction() { return reinterpret_cast(&swi_instruction_); } void* external_function() { return external_function_; } ExternalReference::Type type() { return type_; } static Redirection* Get(Isolate* isolate, void* external_function, ExternalReference::Type type) { Redirection* current = isolate->simulator_redirection(); for (; current != NULL; current = current->next_) { if (current->external_function_ == external_function) return current; } return new Redirection(isolate, external_function, type); } static Redirection* FromSwiInstruction(Instruction* swi_instruction) { char* addr_of_swi = reinterpret_cast(swi_instruction); char* addr_of_redirection = addr_of_swi - offsetof(Redirection, swi_instruction_); return reinterpret_cast(addr_of_redirection); } static void* ReverseRedirection(int32_t reg) { Redirection* redirection = FromSwiInstruction( reinterpret_cast(reinterpret_cast(reg))); return redirection->external_function(); } static void DeleteChain(Redirection* redirection) { while (redirection != nullptr) { Redirection* next = redirection->next_; delete redirection; redirection = next; } } private: void* external_function_; uint32_t swi_instruction_; ExternalReference::Type type_; Redirection* next_; }; // static void Simulator::TearDown(HashMap* i_cache, Redirection* first) { Redirection::DeleteChain(first); if (i_cache != nullptr) { for (HashMap::Entry* entry = i_cache->Start(); entry != nullptr; entry = i_cache->Next(entry)) { delete static_cast(entry->value); } delete i_cache; } } void* Simulator::RedirectExternalReference(Isolate* isolate, void* external_function, ExternalReference::Type type) { Redirection* redirection = Redirection::Get(isolate, external_function, type); return redirection->address_of_swi_instruction(); } // Get the active Simulator for the current thread. Simulator* Simulator::current(Isolate* isolate) { v8::internal::Isolate::PerIsolateThreadData* isolate_data = isolate->FindOrAllocatePerThreadDataForThisThread(); DCHECK(isolate_data != NULL); DCHECK(isolate_data != NULL); Simulator* sim = isolate_data->simulator(); if (sim == NULL) { // 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] = 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]); *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])) + 1; *phiword = value; } void Simulator::set_fpu_register_float(int fpureg, float value) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg]) = value; } void Simulator::set_fpu_register_double(int fpureg, double value) { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg]) = 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 { DCHECK(IsFp64Mode()); DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return FPUregisters_[fpureg]; } int32_t Simulator::get_fpu_register_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg] & 0xffffffff); } int32_t Simulator::get_fpu_register_signed_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg] & 0xffffffff); } int32_t Simulator::get_fpu_register_hi_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast((FPUregisters_[fpureg] >> 32) & 0xffffffff); } float Simulator::get_fpu_register_float(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(const_cast(&FPUregisters_[fpureg])); } double Simulator::get_fpu_register_double(int fpureg) const { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(&FPUregisters_[fpureg]); } 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); } } // 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; } unsigned int Simulator::get_fcsr_rounding_mode() { return FCSR_ & 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--; } 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--; } 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(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--; } 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--; } 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) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%08x", value); } } // TODO(plind): consider making icount_ printing a flag option. void Simulator::TraceMemRd(int32_t addr, int32_t value) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%08x <-- [%08x] (%" PRIu64 ")", value, addr, icount_); } } void Simulator::TraceMemWr(int32_t addr, int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case BYTE: SNPrintF(trace_buf_, " %02x --> [%08x]", static_cast(value), addr); break; case HALF: SNPrintF(trace_buf_, " %04x --> [%08x]", static_cast(value), addr); break; case WORD: SNPrintF(trace_buf_, "%08x --> [%08x]", value, addr); break; } } } int Simulator::ReadW(int32_t addr, Instruction* instr) { if (addr >=0 && addr < 0x400) { // This has to be a NULL-dereference, drop into debugger. PrintF("Memory read from bad address: 0x%08x, pc=0x%08x\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0) { intptr_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); 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 NULL-dereference, drop into debugger. PrintF("Memory write to bad address: 0x%08x, pc=0x%08x\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0) { 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) { 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) { 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) { 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) { 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) { 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) { 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; } // 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%08x: %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); // 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(Instruction* instr) { // 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::FromSwiInstruction(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]; 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", FUNCTION_ADDR(generic_target), dval0, dval1); break; case ExternalReference::BUILTIN_FP_CALL: PrintF("Call to host function at %p with arg %f", FUNCTION_ADDR(generic_target), dval0); break; case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Call to host function at %p with args %f, %d", 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 { 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\n", FUNCTION_ADDR(target), arg0, arg1, arg2, arg3, arg4, arg5); } int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5); 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); } } 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); } else { set_pc(get_pc() + 2 * Instruction::kInstrSize); } } 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(code <= kMaxStopCode); DCHECK(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(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) { V8_Fatal(__FILE__, __LINE__, "Error: Exception %i raised.", static_cast(e)); } void Simulator::DecodeTypeRegisterDRsType() { double ft, fs, fd; uint32_t cc, fcsr_cc; int64_t i64; fs = get_fpu_register_double(fs_reg()); ft = (get_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 = get_instr()->FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (get_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; } set_fpu_register_double(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_double(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_double(fd_reg(), (ft_int & 0x1) == 0 ? fs : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_double(fd_reg(), (ft_int & 0x1) != 0 ? fs : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { set_fpu_register_double(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); int32_t rt_reg = get_instr()->RtValue(); int32_t rt = get_register(rt_reg); if (rt != 0) { set_fpu_register_double(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 (get_instr()->Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) set_fpu_register_double(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) set_fpu_register_double(fd_reg(), fs); } break; } case MIN: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { set_fpu_register_double(fd_reg(), (fs >= ft) ? ft : fs); } break; case MINA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { double result; if (fabs(fs) > fabs(ft)) { result = ft; } else if (fabs(fs) < fabs(ft)) { result = fs; } else { result = (fs < ft ? fs : ft); } set_fpu_register_double(fd_reg(), result); } break; case MAXA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { double result; if (fabs(fs) < fabs(ft)) { result = ft; } else if (fabs(fs) > fabs(ft)) { result = fs; } else { result = (fs > ft ? fs : ft); } set_fpu_register_double(fd_reg(), result); } break; case MAX: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { set_fpu_register_double(fd_reg(), (fs <= ft) ? ft : fs); } break; break; case ADD_D: set_fpu_register_double(fd_reg(), fs + ft); break; case SUB_D: set_fpu_register_double(fd_reg(), fs - ft); break; case MUL_D: set_fpu_register_double(fd_reg(), fs * ft); break; case DIV_D: set_fpu_register_double(fd_reg(), fs / ft); break; case ABS_D: set_fpu_register_double(fd_reg(), fabs(fs)); break; case MOV_D: set_fpu_register_double(fd_reg(), fs); break; case NEG_D: set_fpu_register_double(fd_reg(), -fs); break; case SQRT_D: lazily_initialize_fast_sqrt(isolate_); set_fpu_register_double(fd_reg(), fast_sqrt(fs, isolate_)); break; case RSQRT_D: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); lazily_initialize_fast_sqrt(isolate_); double result = 1.0 / fast_sqrt(fs, isolate_); set_fpu_register_double(fd_reg(), result); break; } case RECIP_D: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double result = 1.0 / fs; set_fpu_register_double(fd_reg(), result); break; } case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); break; case CVT_W_D: { // Convert double to word. double rounded; int32_t result; round_according_to_fcsr(fs, rounded, result, fs); set_fpu_register_word(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--; } set_fpu_register_word(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); set_fpu_register_word(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); set_fpu_register_word(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); set_fpu_register_word(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). set_fpu_register_float(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); set_fpu_register(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()) { set_fpu_register(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()) { set_fpu_register(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()) { set_fpu_register(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()) { set_fpu_register(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(result != 0); dResult = bit_cast(result); set_fpu_register_double(fd_reg(), dResult); break; } case C_F_D: { set_fcsr_bit(fcsr_cc, false); 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 (get_instr()->FunctionFieldRaw()) { case CVT_S_W: // Convert word to float (single). alu_out = get_fpu_register_signed_word(fs_reg()); set_fpu_register_float(fd_reg(), static_cast(alu_out)); break; case CVT_D_W: // Convert word to double. alu_out = get_fpu_register_signed_word(fs_reg()); set_fpu_register_double(fd_reg(), static_cast(alu_out)); break; case CMP_AF: set_fpu_register_word(fd_reg(), 0); break; case CMP_UN: if (std::isnan(fs) || std::isnan(ft)) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_EQ: if (fs == ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_UEQ: if ((fs == ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_LT: if (fs < ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_ULT: if ((fs < ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_LE: if (fs <= ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_ULE: if ((fs <= ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_OR: if (!std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_UNE: if ((fs != ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_NE: if (fs != ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(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 = get_instr()->FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (get_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; } set_fpu_register_float(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case ADD_S: set_fpu_register_float(fd_reg(), fs + ft); break; case SUB_S: set_fpu_register_float(fd_reg(), fs - ft); break; case MUL_S: set_fpu_register_float(fd_reg(), fs * ft); break; case DIV_S: set_fpu_register_float(fd_reg(), fs / ft); break; case ABS_S: set_fpu_register_float(fd_reg(), fabs(fs)); break; case MOV_S: set_fpu_register_float(fd_reg(), fs); break; case NEG_S: set_fpu_register_float(fd_reg(), -fs); break; case SQRT_S: lazily_initialize_fast_sqrt(isolate_); set_fpu_register_float(fd_reg(), fast_sqrt(fs, isolate_)); break; case RSQRT_S: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); lazily_initialize_fast_sqrt(isolate_); float result = 1.0 / fast_sqrt(fs, isolate_); set_fpu_register_float(fd_reg(), result); break; } case RECIP_S: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float result = 1.0 / fs; set_fpu_register_float(fd_reg(), result); break; } case C_F_D: set_fcsr_bit(fcsr_cc, false); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); break; case CVT_D_S: set_fpu_register_double(fd_reg(), static_cast(fs)); break; case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_float(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(result != 0); fResult = bit_cast(result); set_fpu_register_float(fd_reg(), fResult); break; } case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_float(fd_reg(), (ft_int & 0x1) == 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_float(fd_reg(), (ft_int & 0x1) != 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { set_fpu_register_float(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() != 0) { set_fpu_register_float(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 (get_instr()->Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) set_fpu_register_float(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) set_fpu_register_float(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); set_fpu_register_word(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()) { set_fpu_register(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(rounded); set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case FLOOR_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float rounded = std::floor(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case ROUND_W_S: { float 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--; } set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } break; } case ROUND_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float 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()) { set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CEIL_W_S: // Round double to word towards positive infinity. { float rounded = std::ceil(fs); int32_t result = static_cast(rounded); set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case CEIL_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float rounded = std::ceil(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case MIN: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { set_fpu_register_float(fd_reg(), (fs >= ft) ? ft : fs); } break; case MAX: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { set_fpu_register_float(fd_reg(), (fs <= ft) ? ft : fs); } break; case MINA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { float result; if (fabs(fs) > fabs(ft)) { result = ft; } else if (fabs(fs) < fabs(ft)) { result = fs; } else { result = (fs < ft ? fs : ft); } set_fpu_register_float(fd_reg(), result); } break; case MAXA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { float result; if (fabs(fs) < fabs(ft)) { result = ft; } else if (fabs(fs) > fabs(ft)) { result = fs; } else { result = (fs > ft ? fs : ft); } set_fpu_register_float(fd_reg(), result); } break; case CVT_L_S: { if (IsFp64Mode()) { int64_t result; float rounded; round64_according_to_fcsr(fs, rounded, result, fs); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CVT_W_S: { float rounded; int32_t result; round_according_to_fcsr(fs, rounded, result, fs); set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } break; } default: // CVT_W_S CVT_L_S ROUND_W_S ROUND_L_S FLOOR_W_S FLOOR_L_S // CEIL_W_S CEIL_L_S CVT_PS_S are unimplemented. UNREACHABLE(); } } void Simulator::DecodeTypeRegisterLRsType() { double fs = get_fpu_register_double(fs_reg()); double ft = get_fpu_register_double(ft_reg()); switch (get_instr()->FunctionFieldRaw()) { case CVT_D_L: // Mips32r2 instruction. // Watch the signs here, we want 2 32-bit vals // to make a sign-64. int64_t i64; if (IsFp64Mode()) { i64 = get_fpu_register(fs_reg()); } else { i64 = static_cast(get_fpu_register_word(fs_reg())); i64 |= static_cast(get_fpu_register_word(fs_reg() + 1)) << 32; } set_fpu_register_double(fd_reg(), static_cast(i64)); break; case CVT_S_L: if (IsFp64Mode()) { i64 = get_fpu_register(fs_reg()); } else { i64 = static_cast(get_fpu_register_word(fs_reg())); i64 |= static_cast(get_fpu_register_word(fs_reg() + 1)) << 32; } set_fpu_register_float(fd_reg(), static_cast(i64)); break; case CMP_AF: // Mips64r6 CMP.D instructions. set_fpu_register(fd_reg(), 0); break; case CMP_UN: if (std::isnan(fs) || std::isnan(ft)) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_EQ: if (fs == ft) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_UEQ: if ((fs == ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_LT: if (fs < ft) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_ULT: if ((fs < ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_LE: if (fs <= ft) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_ULE: if ((fs <= ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_OR: if (!std::isnan(fs) && !std::isnan(ft)) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_UNE: if ((fs != ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_NE: if (fs != ft && (!std::isnan(fs) && !std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterCOP1() { switch (get_instr()->RsFieldRaw()) { case CFC1: // At the moment only FCSR is supported. DCHECK(fs_reg() == kFCSRRegister); set_register(rt_reg(), FCSR_); break; case MFC1: set_register(rt_reg(), get_fpu_register_word(fs_reg())); break; case MFHC1: set_register(rt_reg(), get_fpu_register_hi_word(fs_reg())); break; case CTC1: { // At the moment only FCSR is supported. DCHECK(fs_reg() == kFCSRRegister); int32_t reg = registers_[rt_reg()]; if (IsMipsArchVariant(kMips32r6)) { FCSR_ = reg | kFCSRNaN2008FlagMask; } else { DCHECK(IsMipsArchVariant(kMips32r1) || IsMipsArchVariant(kMips32r2)); FCSR_ = reg & ~kFCSRNaN2008FlagMask; } break; } case MTC1: // Hardware writes upper 32-bits to zero on mtc1. set_fpu_register_hi_word(fs_reg(), 0); set_fpu_register_word(fs_reg(), registers_[rt_reg()]); break; case MTHC1: set_fpu_register_hi_word(fs_reg(), registers_[rt_reg()]); break; case S: { DecodeTypeRegisterSRsType(); break; } case D: DecodeTypeRegisterDRsType(); break; case W: DecodeTypeRegisterWRsType(); break; case L: DecodeTypeRegisterLRsType(); break; case PS: // Not implemented. UNREACHABLE(); default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterCOP1X() { switch (get_instr()->FunctionFieldRaw()) { case MADD_D: double fr, ft, fs; fr = get_fpu_register_double(fr_reg()); fs = get_fpu_register_double(fs_reg()); ft = get_fpu_register_double(ft_reg()); set_fpu_register_double(fd_reg(), fs * ft + fr); break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterSPECIAL() { int64_t alu_out = 0x12345678; int64_t i64hilo = 0; uint64_t u64hilo = 0; bool do_interrupt = false; switch (get_instr()->FunctionFieldRaw()) { case SELEQZ_S: DCHECK(IsMipsArchVariant(kMips32r6)); set_register(rd_reg(), rt() == 0 ? rs() : 0); break; case SELNEZ_S: DCHECK(IsMipsArchVariant(kMips32r6)); set_register(rd_reg(), rt() != 0 ? rs() : 0); break; case JR: { int32_t next_pc = rs(); int32_t current_pc = get_pc(); Instruction* branch_delay_instr = reinterpret_cast(current_pc + Instruction::kInstrSize); BranchDelayInstructionDecode(branch_delay_instr); set_pc(next_pc); pc_modified_ = true; break; } case JALR: { int32_t next_pc = rs(); int32_t return_addr_reg = rd_reg(); int32_t current_pc = get_pc(); Instruction* branch_delay_instr = reinterpret_cast(current_pc + Instruction::kInstrSize); BranchDelayInstructionDecode(branch_delay_instr); set_register(return_addr_reg, current_pc + 2 * Instruction::kInstrSize); set_pc(next_pc); pc_modified_ = true; break; } case SLL: alu_out = rt() << sa(); SetResult(rd_reg(), static_cast(alu_out)); break; case SRL: if (rs_reg() == 0) { // Regular logical right shift of a word by a fixed number of // bits instruction. RS field is always equal to 0. alu_out = rt_u() >> sa(); } else { // Logical right-rotate of a word by a fixed number of bits. This // is special case of SRL instruction, added in MIPS32 Release 2. // RS field is equal to 00001. alu_out = base::bits::RotateRight32(rt_u(), sa()); } SetResult(rd_reg(), static_cast(alu_out)); break; case SRA: alu_out = rt() >> sa(); SetResult(rd_reg(), static_cast(alu_out)); break; case SLLV: alu_out = rt() << rs(); SetResult(rd_reg(), static_cast(alu_out)); break; case SRLV: if (sa() == 0) { // Regular logical right-shift of a word by a variable number of // bits instruction. SA field is always equal to 0. alu_out = rt_u() >> rs(); } else { // Logical right-rotate of a word by a variable number of bits. // This is special case od SRLV instruction, added in MIPS32 // Release 2. SA field is equal to 00001. alu_out = base::bits::RotateRight32(rt_u(), rs_u()); } SetResult(rd_reg(), static_cast(alu_out)); break; case SRAV: SetResult(rd_reg(), rt() >> rs()); break; case LSA: { DCHECK(IsMipsArchVariant(kMips32r6)); int8_t sa = lsa_sa() + 1; int32_t _rt = rt(); int32_t _rs = rs(); int32_t res = _rs << sa; res += _rt; DCHECK_EQ(res, (rs() << (lsa_sa() + 1)) + rt()); SetResult(rd_reg(), (rs() << (lsa_sa() + 1)) + rt()); break; } case MFHI: // MFHI == CLZ on R6. if (!IsMipsArchVariant(kMips32r6)) { DCHECK(sa() == 0); alu_out = get_register(HI); } else { // MIPS spec: If no bits were set in GPR rs, the result written to // GPR rd is 32. DCHECK(sa() == 1); alu_out = base::bits::CountLeadingZeros32(rs_u()); } SetResult(rd_reg(), static_cast(alu_out)); break; case MFLO: alu_out = get_register(LO); SetResult(rd_reg(), static_cast(alu_out)); break; // Instructions using HI and LO registers. case MULT: i64hilo = static_cast(rs()) * static_cast(rt()); if (!IsMipsArchVariant(kMips32r6)) { set_register(LO, static_cast(i64hilo & 0xffffffff)); set_register(HI, static_cast(i64hilo >> 32)); } else { switch (sa()) { case MUL_OP: set_register(rd_reg(), static_cast(i64hilo & 0xffffffff)); break; case MUH_OP: set_register(rd_reg(), static_cast(i64hilo >> 32)); break; default: UNIMPLEMENTED_MIPS(); break; } } break; case MULTU: u64hilo = static_cast(rs_u()) * static_cast(rt_u()); if (!IsMipsArchVariant(kMips32r6)) { set_register(LO, static_cast(u64hilo & 0xffffffff)); set_register(HI, static_cast(u64hilo >> 32)); } else { switch (sa()) { case MUL_OP: set_register(rd_reg(), static_cast(u64hilo & 0xffffffff)); break; case MUH_OP: set_register(rd_reg(), static_cast(u64hilo >> 32)); break; default: UNIMPLEMENTED_MIPS(); break; } } break; case DIV: if (IsMipsArchVariant(kMips32r6)) { switch (get_instr()->SaValue()) { case DIV_OP: if (rs() == INT_MIN && rt() == -1) { set_register(rd_reg(), INT_MIN); } else if (rt() != 0) { set_register(rd_reg(), rs() / rt()); } break; case MOD_OP: if (rs() == INT_MIN && rt() == -1) { set_register(rd_reg(), 0); } else if (rt() != 0) { set_register(rd_reg(), rs() % rt()); } break; default: UNIMPLEMENTED_MIPS(); break; } } else { // Divide by zero and overflow was not checked in the // configuration step - div and divu do not raise exceptions. On // division by 0 the result will be UNPREDICTABLE. On overflow // (INT_MIN/-1), return INT_MIN which is what the hardware does. if (rs() == INT_MIN && rt() == -1) { set_register(LO, INT_MIN); set_register(HI, 0); } else if (rt() != 0) { set_register(LO, rs() / rt()); set_register(HI, rs() % rt()); } } break; case DIVU: if (IsMipsArchVariant(kMips32r6)) { switch (get_instr()->SaValue()) { case DIV_OP: if (rt_u() != 0) { set_register(rd_reg(), rs_u() / rt_u()); } break; case MOD_OP: if (rt_u() != 0) { set_register(rd_reg(), rs_u() % rt_u()); } break; default: UNIMPLEMENTED_MIPS(); break; } } else { if (rt_u() != 0) { set_register(LO, rs_u() / rt_u()); set_register(HI, rs_u() % rt_u()); } } break; case ADD: if (HaveSameSign(rs(), rt())) { if (rs() > 0) { if (rs() <= (Registers::kMaxValue - rt())) { SignalException(kIntegerOverflow); } } else if (rs() < 0) { if (rs() >= (Registers::kMinValue - rt())) { SignalException(kIntegerUnderflow); } } } SetResult(rd_reg(), rs() + rt()); break; case ADDU: SetResult(rd_reg(), rs() + rt()); break; case SUB: if (!HaveSameSign(rs(), rt())) { if (rs() > 0) { if (rs() <= (Registers::kMaxValue + rt())) { SignalException(kIntegerOverflow); } } else if (rs() < 0) { if (rs() >= (Registers::kMinValue + rt())) { SignalException(kIntegerUnderflow); } } } SetResult(rd_reg(), rs() - rt()); break; case SUBU: SetResult(rd_reg(), rs() - rt()); break; case AND: SetResult(rd_reg(), rs() & rt()); break; case OR: SetResult(rd_reg(), rs() | rt()); break; case XOR: SetResult(rd_reg(), rs() ^ rt()); break; case NOR: SetResult(rd_reg(), ~(rs() | rt())); break; case SLT: SetResult(rd_reg(), rs() < rt() ? 1 : 0); break; case SLTU: SetResult(rd_reg(), rs_u() < rt_u() ? 1 : 0); break; // Break and trap instructions. case BREAK: do_interrupt = true; break; case TGE: do_interrupt = rs() >= rt(); break; case TGEU: do_interrupt = rs_u() >= rt_u(); break; case TLT: do_interrupt = rs() < rt(); break; case TLTU: do_interrupt = rs_u() < rt_u(); break; case TEQ: do_interrupt = rs() == rt(); break; case TNE: do_interrupt = rs() != rt(); break; // Conditional moves. case MOVN: if (rt()) { set_register(rd_reg(), rs()); TraceRegWr(rs()); } break; case MOVCI: { uint32_t cc = get_instr()->FBccValue(); uint32_t fcsr_cc = get_fcsr_condition_bit(cc); if (get_instr()->Bit(16)) { // Read Tf bit. if (test_fcsr_bit(fcsr_cc)) set_register(rd_reg(), rs()); } else { if (!test_fcsr_bit(fcsr_cc)) set_register(rd_reg(), rs()); } break; } case MOVZ: if (!rt()) { set_register(rd_reg(), rs()); TraceRegWr(rs()); } break; default: UNREACHABLE(); } if (do_interrupt) { SoftwareInterrupt(get_instr()); } } void Simulator::DecodeTypeRegisterSPECIAL2() { int32_t alu_out; switch (get_instr()->FunctionFieldRaw()) { case MUL: // Only the lower 32 bits are kept. alu_out = rs_u() * rt_u(); // HI and LO are UNPREDICTABLE after the operation. set_register(LO, Unpredictable); set_register(HI, Unpredictable); break; case CLZ: // MIPS32 spec: If no bits were set in GPR rs, the result written to // GPR rd is 32. alu_out = base::bits::CountLeadingZeros32(rs_u()); break; default: alu_out = 0x12345678; UNREACHABLE(); } SetResult(rd_reg(), alu_out); } void Simulator::DecodeTypeRegisterSPECIAL3() { int32_t alu_out; switch (get_instr()->FunctionFieldRaw()) { case INS: { // Mips32r2 instruction. // Interpret rd field as 5-bit msb of insert. uint16_t msb = rd_reg(); // Interpret sa field as 5-bit lsb of insert. uint16_t lsb = sa(); uint16_t size = msb - lsb + 1; uint32_t mask = (1 << size) - 1; alu_out = (rt_u() & ~(mask << lsb)) | ((rs_u() & mask) << lsb); // Ins instr leaves result in Rt, rather than Rd. SetResult(rt_reg(), alu_out); break; } case EXT: { // Mips32r2 instruction. // Interpret rd field as 5-bit msb of extract. uint16_t msb = rd_reg(); // Interpret sa field as 5-bit lsb of extract. uint16_t lsb = sa(); uint16_t size = msb + 1; uint32_t mask = (1 << size) - 1; alu_out = (rs_u() & (mask << lsb)) >> lsb; SetResult(rt_reg(), alu_out); break; } case BSHFL: { int sa = get_instr()->SaFieldRaw() >> kSaShift; switch (sa) { case BITSWAP: { uint32_t input = static_cast(rt()); uint32_t output = 0; uint8_t i_byte, o_byte; // Reverse the bit in byte for each individual byte for (int i = 0; i < 4; i++) { output = output >> 8; i_byte = input & 0xff; // Fast way to reverse bits in byte // Devised by Sean Anderson, July 13, 2001 o_byte = static_cast(((i_byte * 0x0802LU & 0x22110LU) | (i_byte * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16); output = output | (static_cast(o_byte << 24)); input = input >> 8; } alu_out = static_cast(output); break; } case SEB: case SEH: case WSBH: alu_out = 0x12345678; UNREACHABLE(); break; default: { const uint8_t bp = get_instr()->Bp2Value(); sa >>= kBp2Bits; switch (sa) { case ALIGN: { if (bp == 0) { alu_out = static_cast(rt()); } else { uint32_t rt_hi = rt() << (8 * bp); uint32_t rs_lo = rs() >> (8 * (4 - bp)); alu_out = static_cast(rt_hi | rs_lo); } break; } default: alu_out = 0x12345678; UNREACHABLE(); break; } } } SetResult(rd_reg(), alu_out); break; } default: UNREACHABLE(); } } void Simulator::DecodeTypeRegister(Instruction* instr) { const Opcode op = instr->OpcodeFieldRaw(); // Set up the variables if needed before executing the instruction. // ConfigureTypeRegister(instr); set_instr(instr); // ---------- Execution. switch (op) { case COP1: DecodeTypeRegisterCOP1(); break; case COP1X: DecodeTypeRegisterCOP1X(); break; case SPECIAL: DecodeTypeRegisterSPECIAL(); break; case SPECIAL2: DecodeTypeRegisterSPECIAL2(); break; case SPECIAL3: DecodeTypeRegisterSPECIAL3(); break; default: UNREACHABLE(); } } // Type 2: instructions using a 16, 21 or 26 bits immediate. (e.g. beq, beqc). void Simulator::DecodeTypeImmediate(Instruction* instr) { // Instruction fields. Opcode op = instr->OpcodeFieldRaw(); int32_t rs_reg = instr->RsValue(); int32_t rs = get_register(instr->RsValue()); uint32_t rs_u = static_cast(rs); int32_t rt_reg = instr->RtValue(); // Destination register. int32_t rt = get_register(rt_reg); int16_t imm16 = instr->Imm16Value(); int32_t ft_reg = instr->FtValue(); // Destination register. // Zero extended immediate. uint32_t oe_imm16 = 0xffff & imm16; // Sign extended immediate. int32_t se_imm16 = imm16; // Next pc. int32_t next_pc = bad_ra; // Used for conditional branch instructions. bool execute_branch_delay_instruction = false; // Used for arithmetic instructions. int32_t alu_out = 0; // Used for memory instructions. int32_t addr = 0x0; // Branch instructions common part. auto BranchAndLinkHelper = [this, instr, &next_pc, &execute_branch_delay_instruction]( bool do_branch) { execute_branch_delay_instruction = true; int32_t current_pc = get_pc(); if (do_branch) { int16_t imm16 = instr->Imm16Value(); next_pc = current_pc + (imm16 << 2) + Instruction::kInstrSize; set_register(31, current_pc + 2 * Instruction::kInstrSize); } else { next_pc = current_pc + 2 * Instruction::kInstrSize; } }; auto BranchHelper = [this, instr, &next_pc, &execute_branch_delay_instruction](bool do_branch) { execute_branch_delay_instruction = true; int32_t current_pc = get_pc(); if (do_branch) { int16_t imm16 = instr->Imm16Value(); next_pc = current_pc + (imm16 << 2) + Instruction::kInstrSize; } else { next_pc = current_pc + 2 * Instruction::kInstrSize; } }; auto BranchAndLinkCompactHelper = [this, instr, &next_pc](bool do_branch, int bits) { int32_t current_pc = get_pc(); CheckForbiddenSlot(current_pc); if (do_branch) { int32_t imm = instr->ImmValue(bits); imm <<= 32 - bits; imm >>= 32 - bits; next_pc = current_pc + (imm << 2) + Instruction::kInstrSize; set_register(31, current_pc + Instruction::kInstrSize); } }; auto BranchCompactHelper = [&next_pc, this, instr](bool do_branch, int bits) { int32_t current_pc = get_pc(); CheckForbiddenSlot(current_pc); if (do_branch) { int32_t imm = instr->ImmValue(bits); imm <<= 32 - bits; imm >>= 32 - bits; next_pc = get_pc() + (imm << 2) + Instruction::kInstrSize; } }; switch (op) { // ------------- COP1. Coprocessor instructions. case COP1: switch (instr->RsFieldRaw()) { case BC1: { // Branch on coprocessor condition. // Floating point. uint32_t cc = instr->FBccValue(); uint32_t fcsr_cc = get_fcsr_condition_bit(cc); uint32_t cc_value = test_fcsr_bit(fcsr_cc); bool do_branch = (instr->FBtrueValue()) ? cc_value : !cc_value; BranchHelper(do_branch); break; } case BC1EQZ: BranchHelper(!(get_fpu_register(ft_reg) & 0x1)); break; case BC1NEZ: BranchHelper(get_fpu_register(ft_reg) & 0x1); break; default: UNREACHABLE(); } break; // ------------- REGIMM class. case REGIMM: switch (instr->RtFieldRaw()) { case BLTZ: BranchHelper(rs < 0); break; case BGEZ: BranchHelper(rs >= 0); break; case BLTZAL: BranchAndLinkHelper(rs < 0); break; case BGEZAL: BranchAndLinkHelper(rs >= 0); break; default: UNREACHABLE(); } break; // case REGIMM. // ------------- Branch instructions. // When comparing to zero, the encoding of rt field is always 0, so we don't // need to replace rt with zero. case BEQ: BranchHelper(rs == rt); break; case BNE: BranchHelper(rs != rt); break; case POP06: // BLEZALC, BGEZALC, BGEUC, BLEZ (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BLEZALC BranchAndLinkCompactHelper(rt <= 0, 16); } else { if (rs_reg == rt_reg) { // BGEZALC BranchAndLinkCompactHelper(rt >= 0, 16); } else { // BGEUC BranchCompactHelper( static_cast(rs) >= static_cast(rt), 16); } } } else { // BLEZ BranchHelper(rs <= 0); } } else { // BLEZ BranchHelper(rs <= 0); } break; case POP07: // BGTZALC, BLTZALC, BLTUC, BGTZ (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BGTZALC BranchAndLinkCompactHelper(rt > 0, 16); } else { if (rt_reg == rs_reg) { // BLTZALC BranchAndLinkCompactHelper(rt < 0, 16); } else { // BLTUC BranchCompactHelper( static_cast(rs) < static_cast(rt), 16); } } } else { // BGTZ BranchHelper(rs > 0); } } else { // BGTZ BranchHelper(rs > 0); } break; case POP26: // BLEZC, BGEZC, BGEC/BLEC / BLEZL (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BLEZC BranchCompactHelper(rt <= 0, 16); } else { if (rs_reg == rt_reg) { // BGEZC BranchCompactHelper(rt >= 0, 16); } else { // BGEC/BLEC BranchCompactHelper(rs >= rt, 16); } } } } else { // BLEZL BranchAndLinkHelper(rs <= 0); } break; case POP27: // BGTZC, BLTZC, BLTC/BGTC / BGTZL (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BGTZC BranchCompactHelper(rt > 0, 16); } else { if (rs_reg == rt_reg) { // BLTZC BranchCompactHelper(rt < 0, 16); } else { // BLTC/BGTC BranchCompactHelper(rs < rt, 16); } } } } else { // BGTZL BranchAndLinkHelper(rs > 0); } break; case POP66: // BEQZC, JIC if (rs_reg != 0) { // BEQZC BranchCompactHelper(rs == 0, 21); } else { // JIC next_pc = rt + imm16; } break; case POP76: // BNEZC, JIALC if (rs_reg != 0) { // BNEZC BranchCompactHelper(rs != 0, 21); } else { // JIALC set_register(31, get_pc() + Instruction::kInstrSize); next_pc = rt + imm16; } break; case BC: BranchCompactHelper(true, 26); break; case BALC: BranchAndLinkCompactHelper(true, 26); break; case POP10: // BOVC, BEQZALC, BEQC / ADDI (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rs_reg >= rt_reg) { // BOVC if (HaveSameSign(rs, rt)) { if (rs > 0) { BranchCompactHelper(rs > Registers::kMaxValue - rt, 16); } else if (rs < 0) { BranchCompactHelper(rs < Registers::kMinValue - rt, 16); } } } else { if (rs_reg == 0) { // BEQZALC BranchAndLinkCompactHelper(rt == 0, 16); } else { // BEQC BranchCompactHelper(rt == rs, 16); } } } else { // ADDI if (HaveSameSign(rs, se_imm16)) { if (rs > 0) { if (rs <= Registers::kMaxValue - se_imm16) { SignalException(kIntegerOverflow); } } else if (rs < 0) { if (rs >= Registers::kMinValue - se_imm16) { SignalException(kIntegerUnderflow); } } } SetResult(rt_reg, rs + se_imm16); } break; case POP30: // BNVC, BNEZALC, BNEC / DADDI (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rs_reg >= rt_reg) { // BNVC if (!HaveSameSign(rs, rt) || rs == 0 || rt == 0) { BranchCompactHelper(true, 16); } else { if (rs > 0) { BranchCompactHelper(rs <= Registers::kMaxValue - rt, 16); } else if (rs < 0) { BranchCompactHelper(rs >= Registers::kMinValue - rt, 16); } } } else { if (rs_reg == 0) { // BNEZALC BranchAndLinkCompactHelper(rt != 0, 16); } else { // BNEC BranchCompactHelper(rt != rs, 16); } } } break; // ------------- Arithmetic instructions. case ADDIU: SetResult(rt_reg, rs + se_imm16); break; case SLTI: SetResult(rt_reg, rs < se_imm16 ? 1 : 0); break; case SLTIU: SetResult(rt_reg, rs_u < static_cast(se_imm16) ? 1 : 0); break; case ANDI: SetResult(rt_reg, rs & oe_imm16); break; case ORI: SetResult(rt_reg, rs | oe_imm16); break; case XORI: SetResult(rt_reg, rs ^ oe_imm16); break; case LUI: if (rs_reg != 0) { // AUI DCHECK(IsMipsArchVariant(kMips32r6)); SetResult(rt_reg, rs + (se_imm16 << 16)); } else { // LUI SetResult(rt_reg, oe_imm16 << 16); } break; // ------------- Memory instructions. case LB: set_register(rt_reg, ReadB(rs + se_imm16)); break; case LH: set_register(rt_reg, ReadH(rs + se_imm16, instr)); break; case LWL: { // al_offset is offset of the effective address within an aligned word. uint8_t al_offset = (rs + se_imm16) & kPointerAlignmentMask; uint8_t byte_shift = kPointerAlignmentMask - al_offset; uint32_t mask = (1 << byte_shift * 8) - 1; addr = rs + se_imm16 - al_offset; alu_out = ReadW(addr, instr); alu_out <<= byte_shift * 8; alu_out |= rt & mask; set_register(rt_reg, alu_out); break; } case LW: set_register(rt_reg, ReadW(rs + se_imm16, instr)); break; case LBU: set_register(rt_reg, ReadBU(rs + se_imm16)); break; case LHU: set_register(rt_reg, ReadHU(rs + se_imm16, instr)); break; case LWR: { // al_offset is offset of the effective address within an aligned word. uint8_t al_offset = (rs + se_imm16) & kPointerAlignmentMask; uint8_t byte_shift = kPointerAlignmentMask - al_offset; uint32_t mask = al_offset ? (~0 << (byte_shift + 1) * 8) : 0; addr = rs + se_imm16 - al_offset; alu_out = ReadW(addr, instr); alu_out = static_cast (alu_out) >> al_offset * 8; alu_out |= rt & mask; set_register(rt_reg, alu_out); break; } case SB: WriteB(rs + se_imm16, static_cast(rt)); break; case SH: WriteH(rs + se_imm16, 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 } static bool ICacheMatch(void* one, void* two) { DCHECK((reinterpret_cast(one) & CachePage::kPageMask) == 0); DCHECK((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::FlushICache(v8::internal::HashMap* 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(v8::internal::HashMap* i_cache, void* page) { v8::internal::HashMap::Entry* entry = i_cache->LookupOrInsert(page, ICacheHash(page)); if (entry->value == NULL) { 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(v8::internal::HashMap* i_cache, intptr_t start, int size) { DCHECK(size <= CachePage::kPageSize); DCHECK(AllOnOnePage(start, size - 1)); DCHECK((start & CachePage::kLineMask) == 0); DCHECK((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(v8::internal::HashMap* 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), Instruction::kInstrSize)); } else { // Cache miss. Load memory into the cache. memcpy(cached_line, line, CachePage::kLineLength); *cache_valid_byte = CachePage::LINE_VALID; } } void Simulator::Initialize(Isolate* isolate) { if (isolate->simulator_initialized()) return; isolate->set_simulator_initialized(true); ::v8::internal::ExternalReference::set_redirector(isolate, &RedirectExternalReference); } Simulator::Simulator(Isolate* isolate) : isolate_(isolate) { i_cache_ = isolate_->simulator_i_cache(); if (i_cache_ == NULL) { i_cache_ = new v8::internal::HashMap(&ICacheMatch); isolate_->set_simulator_i_cache(i_cache_); } Initialize(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_ = NULL; 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_[i] = 0; } if (IsMipsArchVariant(kMips32r6)) { FCSR_ = kFCSRNaN2008FlagMask; } 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; InitializeCoverage(); last_debugger_input_ = NULL; } Simulator::~Simulator() { free(stack_); } // When the generated code calls an external reference we need to catch that in // the simulator. The external reference will be a function compiled for the // host architecture. We need to call that function instead of trying to // execute it with the simulator. We do that by redirecting the external // reference to a swi (software-interrupt) instruction that is handled by // the simulator. We write the original destination of the jump just at a known // offset from the swi instruction so the simulator knows what to call. class Redirection { public: Redirection(Isolate* isolate, void* external_function, ExternalReference::Type type) : external_function_(external_function), swi_instruction_(rtCallRedirInstr), type_(type), next_(NULL) { next_ = isolate->simulator_redirection(); Simulator::current(isolate)-> FlushICache(isolate->simulator_i_cache(), reinterpret_cast(&swi_instruction_), Instruction::kInstrSize); isolate->set_simulator_redirection(this); } void* address_of_swi_instruction() { return reinterpret_cast(&swi_instruction_); } void* external_function() { return external_function_; } ExternalReference::Type type() { return type_; } static Redirection* Get(Isolate* isolate, void* external_function, ExternalReference::Type type) { Redirection* current = isolate->simulator_redirection(); for (; current != NULL; current = current->next_) { if (current->external_function_ == external_function) return current; } return new Redirection(isolate, external_function, type); } static Redirection* FromSwiInstruction(Instruction* swi_instruction) { char* addr_of_swi = reinterpret_cast(swi_instruction); char* addr_of_redirection = addr_of_swi - offsetof(Redirection, swi_instruction_); return reinterpret_cast(addr_of_redirection); } static void* ReverseRedirection(int32_t reg) { Redirection* redirection = FromSwiInstruction( reinterpret_cast(reinterpret_cast(reg))); return redirection->external_function(); } static void DeleteChain(Redirection* redirection) { while (redirection != nullptr) { Redirection* next = redirection->next_; delete redirection; redirection = next; } } private: void* external_function_; uint32_t swi_instruction_; ExternalReference::Type type_; Redirection* next_; }; // static void Simulator::TearDown(HashMap* i_cache, Redirection* first) { Redirection::DeleteChain(first); if (i_cache != nullptr) { for (HashMap::Entry* entry = i_cache->Start(); entry != nullptr; entry = i_cache->Next(entry)) { delete static_cast(entry->value); } delete i_cache; } } void* Simulator::RedirectExternalReference(Isolate* isolate, void* external_function, ExternalReference::Type type) { Redirection* redirection = Redirection::Get(isolate, external_function, type); return redirection->address_of_swi_instruction(); } // Get the active Simulator for the current thread. Simulator* Simulator::current(Isolate* isolate) { v8::internal::Isolate::PerIsolateThreadData* isolate_data = isolate->FindOrAllocatePerThreadDataForThisThread(); DCHECK(isolate_data != NULL); DCHECK(isolate_data != NULL); Simulator* sim = isolate_data->simulator(); if (sim == NULL) { // 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] = 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]); *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])) + 1; *phiword = value; } void Simulator::set_fpu_register_float(int fpureg, float value) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg]) = value; } void Simulator::set_fpu_register_double(int fpureg, double value) { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg]) = 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 { DCHECK(IsFp64Mode()); DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return FPUregisters_[fpureg]; } int32_t Simulator::get_fpu_register_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg] & 0xffffffff); } int32_t Simulator::get_fpu_register_signed_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast(FPUregisters_[fpureg] & 0xffffffff); } int32_t Simulator::get_fpu_register_hi_word(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return static_cast((FPUregisters_[fpureg] >> 32) & 0xffffffff); } float Simulator::get_fpu_register_float(int fpureg) const { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(const_cast(&FPUregisters_[fpureg])); } double Simulator::get_fpu_register_double(int fpureg) const { if (IsFp64Mode()) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(&FPUregisters_[fpureg]); } 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); } } // 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; } unsigned int Simulator::get_fcsr_rounding_mode() { return FCSR_ & 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--; } 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--; } 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(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--; } 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--; } 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) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%08x", value); } } // TODO(plind): consider making icount_ printing a flag option. void Simulator::TraceMemRd(int32_t addr, int32_t value) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%08x <-- [%08x] (%" PRIu64 ")", value, addr, icount_); } } void Simulator::TraceMemWr(int32_t addr, int32_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case BYTE: SNPrintF(trace_buf_, " %02x --> [%08x]", static_cast(value), addr); break; case HALF: SNPrintF(trace_buf_, " %04x --> [%08x]", static_cast(value), addr); break; case WORD: SNPrintF(trace_buf_, "%08x --> [%08x]", value, addr); break; } } } int Simulator::ReadW(int32_t addr, Instruction* instr) { if (addr >=0 && addr < 0x400) { // This has to be a NULL-dereference, drop into debugger. PrintF("Memory read from bad address: 0x%08x, pc=0x%08x\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0) { intptr_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); 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 NULL-dereference, drop into debugger. PrintF("Memory write to bad address: 0x%08x, pc=0x%08x\n", addr, reinterpret_cast(instr)); MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0) { 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) { 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) { 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) { 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) { 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) { 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) { 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; } // 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%08x: %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); // 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(Instruction* instr) { // 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::FromSwiInstruction(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]; 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", FUNCTION_ADDR(generic_target), dval0, dval1); break; case ExternalReference::BUILTIN_FP_CALL: PrintF("Call to host function at %p with arg %f", FUNCTION_ADDR(generic_target), dval0); break; case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Call to host function at %p with args %f, %d", 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 { 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\n", FUNCTION_ADDR(target), arg0, arg1, arg2, arg3, arg4, arg5); } int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5); 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); } } 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); } else { set_pc(get_pc() + 2 * Instruction::kInstrSize); } } 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(code <= kMaxStopCode); DCHECK(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(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) { V8_Fatal(__FILE__, __LINE__, "Error: Exception %i raised.", static_cast(e)); } void Simulator::DecodeTypeRegisterDRsType() { double ft, fs, fd; uint32_t cc, fcsr_cc; int64_t i64; fs = get_fpu_register_double(fs_reg()); ft = (get_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 = get_instr()->FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (get_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; } set_fpu_register_double(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_double(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_double(fd_reg(), (ft_int & 0x1) == 0 ? fs : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_double(fd_reg(), (ft_int & 0x1) != 0 ? fs : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { set_fpu_register_double(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); int32_t rt_reg = get_instr()->RtValue(); int32_t rt = get_register(rt_reg); if (rt != 0) { set_fpu_register_double(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 (get_instr()->Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) set_fpu_register_double(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) set_fpu_register_double(fd_reg(), fs); } break; } case MIN: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { set_fpu_register_double(fd_reg(), (fs >= ft) ? ft : fs); } break; case MINA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { double result; if (fabs(fs) > fabs(ft)) { result = ft; } else if (fabs(fs) < fabs(ft)) { result = fs; } else { result = (fs < ft ? fs : ft); } set_fpu_register_double(fd_reg(), result); } break; case MAXA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { double result; if (fabs(fs) < fabs(ft)) { result = ft; } else if (fabs(fs) > fabs(ft)) { result = fs; } else { result = (fs > ft ? fs : ft); } set_fpu_register_double(fd_reg(), result); } break; case MAX: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_double(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_double(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_double(fd_reg(), fs); } else { set_fpu_register_double(fd_reg(), (fs <= ft) ? ft : fs); } break; break; case ADD_D: set_fpu_register_double(fd_reg(), fs + ft); break; case SUB_D: set_fpu_register_double(fd_reg(), fs - ft); break; case MUL_D: set_fpu_register_double(fd_reg(), fs * ft); break; case DIV_D: set_fpu_register_double(fd_reg(), fs / ft); break; case ABS_D: set_fpu_register_double(fd_reg(), fabs(fs)); break; case MOV_D: set_fpu_register_double(fd_reg(), fs); break; case NEG_D: set_fpu_register_double(fd_reg(), -fs); break; case SQRT_D: lazily_initialize_fast_sqrt(isolate_); set_fpu_register_double(fd_reg(), fast_sqrt(fs, isolate_)); break; case RSQRT_D: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); lazily_initialize_fast_sqrt(isolate_); double result = 1.0 / fast_sqrt(fs, isolate_); set_fpu_register_double(fd_reg(), result); break; } case RECIP_D: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); double result = 1.0 / fs; set_fpu_register_double(fd_reg(), result); break; } case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); break; case CVT_W_D: { // Convert double to word. double rounded; int32_t result; round_according_to_fcsr(fs, rounded, result, fs); set_fpu_register_word(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--; } set_fpu_register_word(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); set_fpu_register_word(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); set_fpu_register_word(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); set_fpu_register_word(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). set_fpu_register_float(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); set_fpu_register(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()) { set_fpu_register(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()) { set_fpu_register(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()) { set_fpu_register(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()) { set_fpu_register(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(result != 0); dResult = bit_cast(result); set_fpu_register_double(fd_reg(), dResult); break; } case C_F_D: { set_fcsr_bit(fcsr_cc, false); 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 (get_instr()->FunctionFieldRaw()) { case CVT_S_W: // Convert word to float (single). alu_out = get_fpu_register_signed_word(fs_reg()); set_fpu_register_float(fd_reg(), static_cast(alu_out)); break; case CVT_D_W: // Convert word to double. alu_out = get_fpu_register_signed_word(fs_reg()); set_fpu_register_double(fd_reg(), static_cast(alu_out)); break; case CMP_AF: set_fpu_register_word(fd_reg(), 0); break; case CMP_UN: if (std::isnan(fs) || std::isnan(ft)) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_EQ: if (fs == ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_UEQ: if ((fs == ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_LT: if (fs < ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_ULT: if ((fs < ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_LE: if (fs <= ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_ULE: if ((fs <= ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_OR: if (!std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_UNE: if ((fs != ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(fd_reg(), 0); } break; case CMP_NE: if (fs != ft) { set_fpu_register_word(fd_reg(), -1); } else { set_fpu_register_word(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 = get_instr()->FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (get_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; } set_fpu_register_float(fd_reg(), result); if (result != fs) { set_fcsr_bit(kFCSRInexactFlagBit, true); } break; } case ADD_S: set_fpu_register_float(fd_reg(), fs + ft); break; case SUB_S: set_fpu_register_float(fd_reg(), fs - ft); break; case MUL_S: set_fpu_register_float(fd_reg(), fs * ft); break; case DIV_S: set_fpu_register_float(fd_reg(), fs / ft); break; case ABS_S: set_fpu_register_float(fd_reg(), fabs(fs)); break; case MOV_S: set_fpu_register_float(fd_reg(), fs); break; case NEG_S: set_fpu_register_float(fd_reg(), -fs); break; case SQRT_S: lazily_initialize_fast_sqrt(isolate_); set_fpu_register_float(fd_reg(), fast_sqrt(fs, isolate_)); break; case RSQRT_S: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); lazily_initialize_fast_sqrt(isolate_); float result = 1.0 / fast_sqrt(fs, isolate_); set_fpu_register_float(fd_reg(), result); break; } case RECIP_S: { DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float result = 1.0 / fs; set_fpu_register_float(fd_reg(), result); break; } case C_F_D: set_fcsr_bit(fcsr_cc, false); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (std::isnan(fs) || std::isnan(ft))); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (std::isnan(fs) || std::isnan(ft))); break; case CVT_D_S: set_fpu_register_double(fd_reg(), static_cast(fs)); break; case SEL: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_float(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(result != 0); fResult = bit_cast(result); set_fpu_register_float(fd_reg(), fResult); break; } case SELEQZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_float(fd_reg(), (ft_int & 0x1) == 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case SELNEZ_C: DCHECK(IsMipsArchVariant(kMips32r6)); set_fpu_register_float(fd_reg(), (ft_int & 0x1) != 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case MOVZ_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() == 0) { set_fpu_register_float(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(IsMipsArchVariant(kMips32r2)); if (rt() != 0) { set_fpu_register_float(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 (get_instr()->Bit(16)) { // Read Tf bit. // MOVT.D if (test_fcsr_bit(ft_cc)) set_fpu_register_float(fd_reg(), fs); } else { // MOVF.D if (!test_fcsr_bit(ft_cc)) set_fpu_register_float(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); set_fpu_register_word(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()) { set_fpu_register(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(rounded); set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case FLOOR_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float rounded = std::floor(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case ROUND_W_S: { float 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--; } set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } break; } case ROUND_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float 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()) { set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CEIL_W_S: // Round double to word towards positive infinity. { float rounded = std::ceil(fs); int32_t result = static_cast(rounded); set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } } break; case CEIL_L_S: { // Mips32r2 instruction. DCHECK(IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)); float rounded = std::ceil(fs); int64_t i64 = static_cast(rounded); if (IsFp64Mode()) { set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case MIN: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { set_fpu_register_float(fd_reg(), (fs >= ft) ? ft : fs); } break; case MAX: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { set_fpu_register_float(fd_reg(), (fs <= ft) ? ft : fs); } break; case MINA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { float result; if (fabs(fs) > fabs(ft)) { result = ft; } else if (fabs(fs) < fabs(ft)) { result = fs; } else { result = (fs < ft ? fs : ft); } set_fpu_register_float(fd_reg(), result); } break; case MAXA: DCHECK(IsMipsArchVariant(kMips32r6)); fs = get_fpu_register_float(fs_reg()); if (std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else if (std::isnan(fs) && !std::isnan(ft)) { set_fpu_register_float(fd_reg(), ft); } else if (!std::isnan(fs) && std::isnan(ft)) { set_fpu_register_float(fd_reg(), fs); } else { float result; if (fabs(fs) < fabs(ft)) { result = ft; } else if (fabs(fs) > fabs(ft)) { result = fs; } else { result = (fs > ft ? fs : ft); } set_fpu_register_float(fd_reg(), result); } break; case CVT_L_S: { if (IsFp64Mode()) { int64_t result; float rounded; round64_according_to_fcsr(fs, rounded, result, fs); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } } else { UNSUPPORTED(); } break; } case CVT_W_S: { float rounded; int32_t result; round_according_to_fcsr(fs, rounded, result, fs); set_fpu_register_word(fd_reg(), result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register_word_invalid_result(fs, rounded); } break; } default: // CVT_W_S CVT_L_S ROUND_W_S ROUND_L_S FLOOR_W_S FLOOR_L_S // CEIL_W_S CEIL_L_S CVT_PS_S are unimplemented. UNREACHABLE(); } } void Simulator::DecodeTypeRegisterLRsType() { double fs = get_fpu_register_double(fs_reg()); double ft = get_fpu_register_double(ft_reg()); switch (get_instr()->FunctionFieldRaw()) { case CVT_D_L: // Mips32r2 instruction. // Watch the signs here, we want 2 32-bit vals // to make a sign-64. int64_t i64; if (IsFp64Mode()) { i64 = get_fpu_register(fs_reg()); } else { i64 = static_cast(get_fpu_register_word(fs_reg())); i64 |= static_cast(get_fpu_register_word(fs_reg() + 1)) << 32; } set_fpu_register_double(fd_reg(), static_cast(i64)); break; case CVT_S_L: if (IsFp64Mode()) { i64 = get_fpu_register(fs_reg()); } else { i64 = static_cast(get_fpu_register_word(fs_reg())); i64 |= static_cast(get_fpu_register_word(fs_reg() + 1)) << 32; } set_fpu_register_float(fd_reg(), static_cast(i64)); break; case CMP_AF: // Mips64r6 CMP.D instructions. set_fpu_register(fd_reg(), 0); break; case CMP_UN: if (std::isnan(fs) || std::isnan(ft)) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_EQ: if (fs == ft) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_UEQ: if ((fs == ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_LT: if (fs < ft) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_ULT: if ((fs < ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_LE: if (fs <= ft) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_ULE: if ((fs <= ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_OR: if (!std::isnan(fs) && !std::isnan(ft)) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_UNE: if ((fs != ft) || (std::isnan(fs) || std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; case CMP_NE: if (fs != ft && (!std::isnan(fs) && !std::isnan(ft))) { set_fpu_register(fd_reg(), -1); } else { set_fpu_register(fd_reg(), 0); } break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterCOP1() { switch (get_instr()->RsFieldRaw()) { case CFC1: // At the moment only FCSR is supported. DCHECK(fs_reg() == kFCSRRegister); set_register(rt_reg(), FCSR_); break; case MFC1: set_register(rt_reg(), get_fpu_register_word(fs_reg())); break; case MFHC1: set_register(rt_reg(), get_fpu_register_hi_word(fs_reg())); break; case CTC1: { // At the moment only FCSR is supported. DCHECK(fs_reg() == kFCSRRegister); int32_t reg = registers_[rt_reg()]; if (IsMipsArchVariant(kMips32r6)) { FCSR_ = reg | kFCSRNaN2008FlagMask; } else { DCHECK(IsMipsArchVariant(kMips32r1) || IsMipsArchVariant(kMips32r2)); FCSR_ = reg & ~kFCSRNaN2008FlagMask; } break; } case MTC1: // Hardware writes upper 32-bits to zero on mtc1. set_fpu_register_hi_word(fs_reg(), 0); set_fpu_register_word(fs_reg(), registers_[rt_reg()]); break; case MTHC1: set_fpu_register_hi_word(fs_reg(), registers_[rt_reg()]); break; case S: { DecodeTypeRegisterSRsType(); break; } case D: DecodeTypeRegisterDRsType(); break; case W: DecodeTypeRegisterWRsType(); break; case L: DecodeTypeRegisterLRsType(); break; case PS: // Not implemented. UNREACHABLE(); default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterCOP1X() { switch (get_instr()->FunctionFieldRaw()) { case MADD_D: double fr, ft, fs; fr = get_fpu_register_double(fr_reg()); fs = get_fpu_register_double(fs_reg()); ft = get_fpu_register_double(ft_reg()); set_fpu_register_double(fd_reg(), fs * ft + fr); break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterSPECIAL() { int64_t alu_out = 0x12345678; int64_t i64hilo = 0; uint64_t u64hilo = 0; bool do_interrupt = false; switch (get_instr()->FunctionFieldRaw()) { case SELEQZ_S: DCHECK(IsMipsArchVariant(kMips32r6)); set_register(rd_reg(), rt() == 0 ? rs() : 0); break; case SELNEZ_S: DCHECK(IsMipsArchVariant(kMips32r6)); set_register(rd_reg(), rt() != 0 ? rs() : 0); break; case JR: { int32_t next_pc = rs(); int32_t current_pc = get_pc(); Instruction* branch_delay_instr = reinterpret_cast(current_pc + Instruction::kInstrSize); BranchDelayInstructionDecode(branch_delay_instr); set_pc(next_pc); pc_modified_ = true; break; } case JALR: { int32_t next_pc = rs(); int32_t return_addr_reg = rd_reg(); int32_t current_pc = get_pc(); Instruction* branch_delay_instr = reinterpret_cast(current_pc + Instruction::kInstrSize); BranchDelayInstructionDecode(branch_delay_instr); set_register(return_addr_reg, current_pc + 2 * Instruction::kInstrSize); set_pc(next_pc); pc_modified_ = true; break; } case SLL: alu_out = rt() << sa(); SetResult(rd_reg(), static_cast(alu_out)); break; case SRL: if (rs_reg() == 0) { // Regular logical right shift of a word by a fixed number of // bits instruction. RS field is always equal to 0. alu_out = rt_u() >> sa(); } else { // Logical right-rotate of a word by a fixed number of bits. This // is special case of SRL instruction, added in MIPS32 Release 2. // RS field is equal to 00001. alu_out = base::bits::RotateRight32(rt_u(), sa()); } SetResult(rd_reg(), static_cast(alu_out)); break; case SRA: alu_out = rt() >> sa(); SetResult(rd_reg(), static_cast(alu_out)); break; case SLLV: alu_out = rt() << rs(); SetResult(rd_reg(), static_cast(alu_out)); break; case SRLV: if (sa() == 0) { // Regular logical right-shift of a word by a variable number of // bits instruction. SA field is always equal to 0. alu_out = rt_u() >> rs(); } else { // Logical right-rotate of a word by a variable number of bits. // This is special case od SRLV instruction, added in MIPS32 // Release 2. SA field is equal to 00001. alu_out = base::bits::RotateRight32(rt_u(), rs_u()); } SetResult(rd_reg(), static_cast(alu_out)); break; case SRAV: SetResult(rd_reg(), rt() >> rs()); break; case LSA: { DCHECK(IsMipsArchVariant(kMips32r6)); int8_t sa = lsa_sa() + 1; int32_t _rt = rt(); int32_t _rs = rs(); int32_t res = _rs << sa; res += _rt; DCHECK_EQ(res, (rs() << (lsa_sa() + 1)) + rt()); SetResult(rd_reg(), (rs() << (lsa_sa() + 1)) + rt()); break; } case MFHI: // MFHI == CLZ on R6. if (!IsMipsArchVariant(kMips32r6)) { DCHECK(sa() == 0); alu_out = get_register(HI); } else { // MIPS spec: If no bits were set in GPR rs, the result written to // GPR rd is 32. DCHECK(sa() == 1); alu_out = base::bits::CountLeadingZeros32(rs_u()); } SetResult(rd_reg(), static_cast(alu_out)); break; case MFLO: alu_out = get_register(LO); SetResult(rd_reg(), static_cast(alu_out)); break; // Instructions using HI and LO registers. case MULT: i64hilo = static_cast(rs()) * static_cast(rt()); if (!IsMipsArchVariant(kMips32r6)) { set_register(LO, static_cast(i64hilo & 0xffffffff)); set_register(HI, static_cast(i64hilo >> 32)); } else { switch (sa()) { case MUL_OP: set_register(rd_reg(), static_cast(i64hilo & 0xffffffff)); break; case MUH_OP: set_register(rd_reg(), static_cast(i64hilo >> 32)); break; default: UNIMPLEMENTED_MIPS(); break; } } break; case MULTU: u64hilo = static_cast(rs_u()) * static_cast(rt_u()); if (!IsMipsArchVariant(kMips32r6)) { set_register(LO, static_cast(u64hilo & 0xffffffff)); set_register(HI, static_cast(u64hilo >> 32)); } else { switch (sa()) { case MUL_OP: set_register(rd_reg(), static_cast(u64hilo & 0xffffffff)); break; case MUH_OP: set_register(rd_reg(), static_cast(u64hilo >> 32)); break; default: UNIMPLEMENTED_MIPS(); break; } } break; case DIV: if (IsMipsArchVariant(kMips32r6)) { switch (get_instr()->SaValue()) { case DIV_OP: if (rs() == INT_MIN && rt() == -1) { set_register(rd_reg(), INT_MIN); } else if (rt() != 0) { set_register(rd_reg(), rs() / rt()); } break; case MOD_OP: if (rs() == INT_MIN && rt() == -1) { set_register(rd_reg(), 0); } else if (rt() != 0) { set_register(rd_reg(), rs() % rt()); } break; default: UNIMPLEMENTED_MIPS(); break; } } else { // Divide by zero and overflow was not checked in the // configuration step - div and divu do not raise exceptions. On // division by 0 the result will be UNPREDICTABLE. On overflow // (INT_MIN/-1), return INT_MIN which is what the hardware does. if (rs() == INT_MIN && rt() == -1) { set_register(LO, INT_MIN); set_register(HI, 0); } else if (rt() != 0) { set_register(LO, rs() / rt()); set_register(HI, rs() % rt()); } } break; case DIVU: if (IsMipsArchVariant(kMips32r6)) { switch (get_instr()->SaValue()) { case DIV_OP: if (rt_u() != 0) { set_register(rd_reg(), rs_u() / rt_u()); } break; case MOD_OP: if (rt_u() != 0) { set_register(rd_reg(), rs_u() % rt_u()); } break; default: UNIMPLEMENTED_MIPS(); break; } } else { if (rt_u() != 0) { set_register(LO, rs_u() / rt_u()); set_register(HI, rs_u() % rt_u()); } } break; case ADD: if (HaveSameSign(rs(), rt())) { if (rs() > 0) { if (rs() <= (Registers::kMaxValue - rt())) { SignalException(kIntegerOverflow); } } else if (rs() < 0) { if (rs() >= (Registers::kMinValue - rt())) { SignalException(kIntegerUnderflow); } } } SetResult(rd_reg(), rs() + rt()); break; case ADDU: SetResult(rd_reg(), rs() + rt()); break; case SUB: if (!HaveSameSign(rs(), rt())) { if (rs() > 0) { if (rs() <= (Registers::kMaxValue + rt())) { SignalException(kIntegerOverflow); } } else if (rs() < 0) { if (rs() >= (Registers::kMinValue + rt())) { SignalException(kIntegerUnderflow); } } } SetResult(rd_reg(), rs() - rt()); break; case SUBU: SetResult(rd_reg(), rs() - rt()); break; case AND: SetResult(rd_reg(), rs() & rt()); break; case OR: SetResult(rd_reg(), rs() | rt()); break; case XOR: SetResult(rd_reg(), rs() ^ rt()); break; case NOR: SetResult(rd_reg(), ~(rs() | rt())); break; case SLT: SetResult(rd_reg(), rs() < rt() ? 1 : 0); break; case SLTU: SetResult(rd_reg(), rs_u() < rt_u() ? 1 : 0); break; // Break and trap instructions. case BREAK: do_interrupt = true; break; case TGE: do_interrupt = rs() >= rt(); break; case TGEU: do_interrupt = rs_u() >= rt_u(); break; case TLT: do_interrupt = rs() < rt(); break; case TLTU: do_interrupt = rs_u() < rt_u(); break; case TEQ: do_interrupt = rs() == rt(); break; case TNE: do_interrupt = rs() != rt(); break; // Conditional moves. case MOVN: if (rt()) { set_register(rd_reg(), rs()); TraceRegWr(rs()); } break; case MOVCI: { uint32_t cc = get_instr()->FBccValue(); uint32_t fcsr_cc = get_fcsr_condition_bit(cc); if (get_instr()->Bit(16)) { // Read Tf bit. if (test_fcsr_bit(fcsr_cc)) set_register(rd_reg(), rs()); } else { if (!test_fcsr_bit(fcsr_cc)) set_register(rd_reg(), rs()); } break; } case MOVZ: if (!rt()) { set_register(rd_reg(), rs()); TraceRegWr(rs()); } break; default: UNREACHABLE(); } if (do_interrupt) { SoftwareInterrupt(get_instr()); } } void Simulator::DecodeTypeRegisterSPECIAL2() { int32_t alu_out; switch (get_instr()->FunctionFieldRaw()) { case MUL: // Only the lower 32 bits are kept. alu_out = rs_u() * rt_u(); // HI and LO are UNPREDICTABLE after the operation. set_register(LO, Unpredictable); set_register(HI, Unpredictable); break; case CLZ: // MIPS32 spec: If no bits were set in GPR rs, the result written to // GPR rd is 32. alu_out = base::bits::CountLeadingZeros32(rs_u()); break; default: alu_out = 0x12345678; UNREACHABLE(); } SetResult(rd_reg(), alu_out); } void Simulator::DecodeTypeRegisterSPECIAL3() { int32_t alu_out; switch (get_instr()->FunctionFieldRaw()) { case INS: { // Mips32r2 instruction. // Interpret rd field as 5-bit msb of insert. uint16_t msb = rd_reg(); // Interpret sa field as 5-bit lsb of insert. uint16_t lsb = sa(); uint16_t size = msb - lsb + 1; uint32_t mask = (1 << size) - 1; alu_out = (rt_u() & ~(mask << lsb)) | ((rs_u() & mask) << lsb); // Ins instr leaves result in Rt, rather than Rd. SetResult(rt_reg(), alu_out); break; } case EXT: { // Mips32r2 instruction. // Interpret rd field as 5-bit msb of extract. uint16_t msb = rd_reg(); // Interpret sa field as 5-bit lsb of extract. uint16_t lsb = sa(); uint16_t size = msb + 1; uint32_t mask = (1 << size) - 1; alu_out = (rs_u() & (mask << lsb)) >> lsb; SetResult(rt_reg(), alu_out); break; } case BSHFL: { int sa = get_instr()->SaFieldRaw() >> kSaShift; switch (sa) { case BITSWAP: { uint32_t input = static_cast(rt()); uint32_t output = 0; uint8_t i_byte, o_byte; // Reverse the bit in byte for each individual byte for (int i = 0; i < 4; i++) { output = output >> 8; i_byte = input & 0xff; // Fast way to reverse bits in byte // Devised by Sean Anderson, July 13, 2001 o_byte = static_cast(((i_byte * 0x0802LU & 0x22110LU) | (i_byte * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16); output = output | (static_cast(o_byte << 24)); input = input >> 8; } alu_out = static_cast(output); break; } case SEB: case SEH: case WSBH: alu_out = 0x12345678; UNREACHABLE(); break; default: { const uint8_t bp = get_instr()->Bp2Value(); sa >>= kBp2Bits; switch (sa) { case ALIGN: { if (bp == 0) { alu_out = static_cast(rt()); } else { uint32_t rt_hi = rt() << (8 * bp); uint32_t rs_lo = rs() >> (8 * (4 - bp)); alu_out = static_cast(rt_hi | rs_lo); } break; } default: alu_out = 0x12345678; UNREACHABLE(); break; } } } SetResult(rd_reg(), alu_out); break; } default: UNREACHABLE(); } } void Simulator::DecodeTypeRegister(Instruction* instr) { const Opcode op = instr->OpcodeFieldRaw(); // Set up the variables if needed before executing the instruction. // ConfigureTypeRegister(instr); set_instr(instr); // ---------- Execution. switch (op) { case COP1: DecodeTypeRegisterCOP1(); break; case COP1X: DecodeTypeRegisterCOP1X(); break; case SPECIAL: DecodeTypeRegisterSPECIAL(); break; case SPECIAL2: DecodeTypeRegisterSPECIAL2(); break; case SPECIAL3: DecodeTypeRegisterSPECIAL3(); break; default: UNREACHABLE(); } } // Type 2: instructions using a 16, 21 or 26 bits immediate. (e.g. beq, beqc). void Simulator::DecodeTypeImmediate(Instruction* instr) { // Instruction fields. Opcode op = instr->OpcodeFieldRaw(); int32_t rs_reg = instr->RsValue(); int32_t rs = get_register(instr->RsValue()); uint32_t rs_u = static_cast(rs); int32_t rt_reg = instr->RtValue(); // Destination register. int32_t rt = get_register(rt_reg); int16_t imm16 = instr->Imm16Value(); int32_t ft_reg = instr->FtValue(); // Destination register. // Zero extended immediate. uint32_t oe_imm16 = 0xffff & imm16; // Sign extended immediate. int32_t se_imm16 = imm16; // Next pc. int32_t next_pc = bad_ra; // Used for conditional branch instructions. bool execute_branch_delay_instruction = false; // Used for arithmetic instructions. int32_t alu_out = 0; // Used for memory instructions. int32_t addr = 0x0; // Branch instructions common part. auto BranchAndLinkHelper = [this, instr, &next_pc, &execute_branch_delay_instruction]( bool do_branch) { execute_branch_delay_instruction = true; int32_t current_pc = get_pc(); if (do_branch) { int16_t imm16 = instr->Imm16Value(); next_pc = current_pc + (imm16 << 2) + Instruction::kInstrSize; set_register(31, current_pc + 2 * Instruction::kInstrSize); } else { next_pc = current_pc + 2 * Instruction::kInstrSize; } }; auto BranchHelper = [this, instr, &next_pc, &execute_branch_delay_instruction](bool do_branch) { execute_branch_delay_instruction = true; int32_t current_pc = get_pc(); if (do_branch) { int16_t imm16 = instr->Imm16Value(); next_pc = current_pc + (imm16 << 2) + Instruction::kInstrSize; } else { next_pc = current_pc + 2 * Instruction::kInstrSize; } }; auto BranchAndLinkCompactHelper = [this, instr, &next_pc](bool do_branch, int bits) { int32_t current_pc = get_pc(); CheckForbiddenSlot(current_pc); if (do_branch) { int32_t imm = instr->ImmValue(bits); imm <<= 32 - bits; imm >>= 32 - bits; next_pc = current_pc + (imm << 2) + Instruction::kInstrSize; set_register(31, current_pc + Instruction::kInstrSize); } }; auto BranchCompactHelper = [&next_pc, this, instr](bool do_branch, int bits) { int32_t current_pc = get_pc(); CheckForbiddenSlot(current_pc); if (do_branch) { int32_t imm = instr->ImmValue(bits); imm <<= 32 - bits; imm >>= 32 - bits; next_pc = get_pc() + (imm << 2) + Instruction::kInstrSize; } }; switch (op) { // ------------- COP1. Coprocessor instructions. case COP1: switch (instr->RsFieldRaw()) { case BC1: { // Branch on coprocessor condition. // Floating point. uint32_t cc = instr->FBccValue(); uint32_t fcsr_cc = get_fcsr_condition_bit(cc); uint32_t cc_value = test_fcsr_bit(fcsr_cc); bool do_branch = (instr->FBtrueValue()) ? cc_value : !cc_value; BranchHelper(do_branch); break; } case BC1EQZ: BranchHelper(!(get_fpu_register(ft_reg) & 0x1)); break; case BC1NEZ: BranchHelper(get_fpu_register(ft_reg) & 0x1); break; default: UNREACHABLE(); } break; // ------------- REGIMM class. case REGIMM: switch (instr->RtFieldRaw()) { case BLTZ: BranchHelper(rs < 0); break; case BGEZ: BranchHelper(rs >= 0); break; case BLTZAL: BranchAndLinkHelper(rs < 0); break; case BGEZAL: BranchAndLinkHelper(rs >= 0); break; default: UNREACHABLE(); } break; // case REGIMM. // ------------- Branch instructions. // When comparing to zero, the encoding of rt field is always 0, so we don't // need to replace rt with zero. case BEQ: BranchHelper(rs == rt); break; case BNE: BranchHelper(rs != rt); break; case POP06: // BLEZALC, BGEZALC, BGEUC, BLEZ (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BLEZALC BranchAndLinkCompactHelper(rt <= 0, 16); } else { if (rs_reg == rt_reg) { // BGEZALC BranchAndLinkCompactHelper(rt >= 0, 16); } else { // BGEUC BranchCompactHelper( static_cast(rs) >= static_cast(rt), 16); } } } else { // BLEZ BranchHelper(rs <= 0); } } else { // BLEZ BranchHelper(rs <= 0); } break; case POP07: // BGTZALC, BLTZALC, BLTUC, BGTZ (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BGTZALC BranchAndLinkCompactHelper(rt > 0, 16); } else { if (rt_reg == rs_reg) { // BLTZALC BranchAndLinkCompactHelper(rt < 0, 16); } else { // BLTUC BranchCompactHelper( static_cast(rs) < static_cast(rt), 16); } } } else { // BGTZ BranchHelper(rs > 0); } } else { // BGTZ BranchHelper(rs > 0); } break; case POP26: // BLEZC, BGEZC, BGEC/BLEC / BLEZL (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BLEZC BranchCompactHelper(rt <= 0, 16); } else { if (rs_reg == rt_reg) { // BGEZC BranchCompactHelper(rt >= 0, 16); } else { // BGEC/BLEC BranchCompactHelper(rs >= rt, 16); } } } } else { // BLEZL BranchAndLinkHelper(rs <= 0); } break; case POP27: // BGTZC, BLTZC, BLTC/BGTC / BGTZL (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rt_reg != 0) { if (rs_reg == 0) { // BGTZC BranchCompactHelper(rt > 0, 16); } else { if (rs_reg == rt_reg) { // BLTZC BranchCompactHelper(rt < 0, 16); } else { // BLTC/BGTC BranchCompactHelper(rs < rt, 16); } } } } else { // BGTZL BranchAndLinkHelper(rs > 0); } break; case POP66: // BEQZC, JIC if (rs_reg != 0) { // BEQZC BranchCompactHelper(rs == 0, 21); } else { // JIC next_pc = rt + imm16; } break; case POP76: // BNEZC, JIALC if (rs_reg != 0) { // BNEZC BranchCompactHelper(rs != 0, 21); } else { // JIALC set_register(31, get_pc() + Instruction::kInstrSize); next_pc = rt + imm16; } break; case BC: BranchCompactHelper(true, 26); break; case BALC: BranchAndLinkCompactHelper(true, 26); break; case POP10: // BOVC, BEQZALC, BEQC / ADDI (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rs_reg >= rt_reg) { // BOVC if (HaveSameSign(rs, rt)) { if (rs > 0) { BranchCompactHelper(rs > Registers::kMaxValue - rt, 16); } else if (rs < 0) { BranchCompactHelper(rs < Registers::kMinValue - rt, 16); } } } else { if (rs_reg == 0) { // BEQZALC BranchAndLinkCompactHelper(rt == 0, 16); } else { // BEQC BranchCompactHelper(rt == rs, 16); } } } else { // ADDI if (HaveSameSign(rs, se_imm16)) { if (rs > 0) { if (rs <= Registers::kMaxValue - se_imm16) { SignalException(kIntegerOverflow); } } else if (rs < 0) { if (rs >= Registers::kMinValue - se_imm16) { SignalException(kIntegerUnderflow); } } } SetResult(rt_reg, rs + se_imm16); } break; case POP30: // BNVC, BNEZALC, BNEC / DADDI (pre-r6) if (IsMipsArchVariant(kMips32r6)) { if (rs_reg >= rt_reg) { // BNVC if (!HaveSameSign(rs, rt) || rs == 0 || rt == 0) { BranchCompactHelper(true, 16); } else { if (rs > 0) { BranchCompactHelper(rs <= Registers::kMaxValue - rt, 16); } else if (rs < 0) { BranchCompactHelper(rs >= Registers::kMinValue - rt, 16); } } } else { if (rs_reg == 0) { // BNEZALC BranchAndLinkCompactHelper(rt != 0, 16); } else { // BNEC BranchCompactHelper(rt != rs, 16); } } } break; // ------------- Arithmetic instructions. case ADDIU: SetResult(rt_reg, rs + se_imm16); break; case SLTI: SetResult(rt_reg, rs < se_imm16 ? 1 : 0); break; case SLTIU: SetResult(rt_reg, rs_u < static_cast(se_imm16) ? 1 : 0); break; case ANDI: SetResult(rt_reg, rs & oe_imm16); break; case ORI: SetResult(rt_reg, rs | oe_imm16); break; case XORI: SetResult(rt_reg, rs ^ oe_imm16); break; case LUI: if (rs_reg != 0) { // AUI DCHECK(IsMipsArchVariant(kMips32r6)); SetResult(rt_reg, rs + (se_imm16 << 16)); } else { // LUI SetResult(rt_reg, oe_imm16 << 16); } break; // ------------- Memory instructions. case LB: set_register(rt_reg, ReadB(rs + se_imm16)); break; case LH: set_register(rt_reg, ReadH(rs + se_imm16, instr)); break; case LWL: { // al_offset is offset of the effective address within an aligned word. uint8_t al_offset = (rs + se_imm16) & kPointerAlignmentMask; uint8_t byte_shift = kPointerAlignmentMask - al_offset; uint32_t mask = (1 << byte_shift * 8) - 1; addr = rs + se_imm16 - al_offset; alu_out = ReadW(addr, instr); alu_out <<= byte_shift * 8; alu_out |= rt & mask; set_register(rt_reg, alu_out); break; } case LW: set_register(rt_reg, ReadW(rs + se_imm16, instr)); break; case LBU: set_register(rt_reg, ReadBU(rs + se_imm16)); break; case LHU: set_register(rt_reg, ReadHU(rs + se_imm16, instr)); break; case LWR: { // al_offset is offset of the effective address within an aligned word. uint8_t al_offset = (rs + se_imm16) & kPointerAlignmentMask; uint8_t byte_shift = kPointerAlignmentMask - al_offset; uint32_t mask = al_offset ? (~0 << (byte_shift + 1) * 8) : 0; addr = rs + se_imm16 - al_offset; alu_out = ReadW(addr, instr); alu_out = static_cast (alu_out) >> al_offset * 8; alu_out |= rt & mask; set_register(rt_reg, alu_out); break; } case SB: WriteB(rs + se_imm16, static_cast(rt)); break; case SH: WriteH(rs + se_imm16, static_cast