: 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, size_t 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(base::CustomMatcherHashMap* i_cache, void* start_addr, size_t size) { int64_t start = reinterpret_cast(start_addr); int64_t 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((int64_t)0, start & CachePage::kPageMask); offset = 0; } if (size != 0) { FlushOnePage(i_cache, start, size); } } CachePage* Simulator::GetCachePage(base::CustomMatcherHashMap* i_cache, void* page) { base::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(base::CustomMatcherHashMap* i_cache, intptr_t start, size_t 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(base::CustomMatcherHashMap* i_cache, Instruction* instr) { int64_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 base::CustomMatcherHashMap(&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_size_ = FLAG_sim_stack_size * KB; 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 (kArchVariant == kMips64r6) { FCSR_ = kFCSRNaN2008FlagMask; } else { FCSR_ = 0; } // The sp is initialized to point to the bottom (high address) of the // allocated stack area. To be safe in potential stack underflows we leave // some buffer below. registers_[sp] = reinterpret_cast(stack_) + stack_size_ - 64; // The ra and pc are initialized to a known bad value that will cause an // access violation if the simulator ever tries to execute it. registers_[pc] = bad_ra; registers_[ra] = bad_ra; last_debugger_input_ = 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(int64_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(base::CustomMatcherHashMap* i_cache, Redirection* first) { Redirection::DeleteChain(first); if (i_cache != nullptr) { for (base::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, int64_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[1]; registers_[reg] = registers_[reg] << 32; registers_[reg] += dbl[0]; } void Simulator::set_fpu_register(int fpureg, int64_t value) { 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. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* pword; if (kArchEndian == kLittle) { pword = reinterpret_cast(&FPUregisters_[fpureg]); } else { pword = reinterpret_cast(&FPUregisters_[fpureg]) + 1; } *pword = value; } void Simulator::set_fpu_register_hi_word(int fpureg, int32_t value) { // Set ONLY upper 32-bits, leaving lower bits untouched. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* phiword; if (kArchEndian == kLittle) { phiword = (reinterpret_cast(&FPUregisters_[fpureg])) + 1; } else { phiword = reinterpret_cast(&FPUregisters_[fpureg]); } *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) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg]) = value; } // Get the register from the architecture state. This function does handle // the special case of accessing the PC register. int64_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[sizeof(registers_[0])]; memcpy(buffer, ®isters_[reg], sizeof(registers_[0])); memcpy(&dm_val, buffer, sizeof(registers_[0])); return(dm_val); } int64_t Simulator::get_fpu_register(int fpureg) const { 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 { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(&FPUregisters_[fpureg]); } // Runtime FP routines take up to two double arguments and zero // or one integer arguments. All are constructed here, // from a0-a3 or f12 and f13 (n64), or f14 (O32). void Simulator::GetFpArgs(double* x, double* y, int32_t* z) { if (!IsMipsSoftFloatABI) { const int fparg2 = 13; *x = get_fpu_register_double(12); *y = get_fpu_register_double(fparg2); *z = static_cast(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])]; int64_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; } // 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; } 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_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; } // For cvt instructions only 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 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(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; } } // for cvt instructions only 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 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(int64_t value) { pc_modified_ = true; registers_[pc] = value; } bool Simulator::has_bad_pc() const { return ((registers_[pc] == bad_ra) || (registers_[pc] == end_sim_pc)); } // Raw access to the PC register without the special adjustment when reading. int64_t Simulator::get_pc() const { return registers_[pc]; } // 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. // TODO(plind): refactor this messy debug code when we do unaligned access. void Simulator::DieOrDebug() { if (1) { // Flag for this was removed. MipsDebugger dbg(this); dbg.Debug(); } else { base::OS::Abort(); } } void Simulator::TraceRegWr(int64_t value) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%016" PRIx64 " ", value); } } // TODO(plind): consider making icount_ printing a flag option. void Simulator::TraceMemRd(int64_t addr, int64_t value) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%016" PRIx64 " <-- [%016" PRIx64 " ] (%" PRId64 " )", value, addr, icount_); } } void Simulator::TraceMemWr(int64_t addr, int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case BYTE: SNPrintF(trace_buf_, " %02x --> [%016" PRIx64 " ]", static_cast(value), addr); break; case HALF: SNPrintF(trace_buf_, " %04x --> [%016" PRIx64 " ]", static_cast(value), addr); break; case WORD: SNPrintF(trace_buf_, " %08x --> [%016" PRIx64 " ]", static_cast(value), addr); break; case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " --> [%016" PRIx64 " ] (%" PRId64 " )", value, addr, icount_); break; } } } // TODO(plind): sign-extend and zero-extend not implmented properly // on all the ReadXX functions, I don't think re-interpret cast does it. int32_t Simulator::ReadW(int64_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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & 0x3) == 0 || kArchVariant == kMips64r6) { int32_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } uint32_t Simulator::ReadWU(int64_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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & 0x3) == 0 || kArchVariant == kMips64r6) { uint32_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } void Simulator::WriteW(int64_t addr, int32_t 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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & 0x3) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, WORD); int* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } int64_t Simulator::Read2W(int64_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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & kPointerAlignmentMask) == 0 || kArchVariant == kMips64r6) { int64_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, *ptr); return *ptr; } PrintF("Unaligned read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } void Simulator::Write2W(int64_t addr, int64_t 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%08" PRIx64 " , pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & kPointerAlignmentMask) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, DWORD); int64_t* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } double Simulator::ReadD(int64_t addr, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || kArchVariant == kMips64r6) { double* ptr = reinterpret_cast(addr); return *ptr; } PrintF("Unaligned (double) read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } void Simulator::WriteD(int64_t addr, double value, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || kArchVariant == kMips64r6) { double* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned (double) write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } uint16_t Simulator::ReadHU(int64_t addr, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { uint16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned unsigned halfword read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } int16_t Simulator::ReadH(int64_t addr, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { int16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned signed halfword read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } void Simulator::WriteH(int64_t addr, uint16_t value, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, HALF); uint16_t* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned unsigned halfword write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } void Simulator::WriteH(int64_t addr, int16_t value, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, HALF); int16_t* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned halfword write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } uint32_t Simulator::ReadBU(int64_t addr) { uint8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr & 0xff; } int32_t Simulator::ReadB(int64_t addr) { int8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } void Simulator::WriteB(int64_t addr, uint8_t value) { TraceMemWr(addr, value, BYTE); uint8_t* ptr = reinterpret_cast(addr); *ptr = value; } void Simulator::WriteB(int64_t addr, int8_t value) { TraceMemWr(addr, value, BYTE); int8_t* ptr = reinterpret_cast(addr); *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%08" PRIxPTR " : %s\n", reinterpret_cast(instr), format); UNIMPLEMENTED_MIPS(); } // Calls into the V8 runtime are based on this very simple interface. // Note: To be able to return two values from some calls the code in runtime.cc // uses the ObjectPair which is essentially two 32-bit values stuffed into a // 64-bit value. With the code below we assume that all runtime calls return // 64 bits of result. If they don't, the v1 result register contains a bogus // value, which is fine because it is caller-saved. typedef ObjectPair (*SimulatorRuntimeCall)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4, int64_t arg5); typedef ObjectTriple (*SimulatorRuntimeTripleCall)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4); // 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)(int64_t arg0); typedef void (*SimulatorRuntimeProfilingApiCall)(int64_t arg0, void* arg1); // This signature supports direct call to accessor getter callback. typedef void (*SimulatorRuntimeDirectGetterCall)(int64_t arg0, int64_t arg1); typedef void (*SimulatorRuntimeProfilingGetterCall)( int64_t arg0, int64_t arg1, void* arg2); // Software interrupt instructions are used by the simulator to call into the // C-based V8 runtime. They are also used for debugging with simulator. void Simulator::SoftwareInterrupt() { // There are several instructions that could get us here, // the break_ instruction, or several variants of traps. All // Are "SPECIAL" class opcode, and are distinuished by function. int32_t func = instr_.FunctionFieldRaw(); uint32_t code = (func == BREAK) ? instr_.Bits(25, 6) : -1; // We first check if we met a call_rt_redirected. if (instr_.InstructionBits() == rtCallRedirInstr) { Redirection* redirection = Redirection::FromSwiInstruction(instr_.instr()); int64_t arg0 = get_register(a0); int64_t arg1 = get_register(a1); int64_t arg2 = get_register(a2); int64_t arg3 = get_register(a3); int64_t arg4, arg5; arg4 = get_register(a4); // Abi n64 register a4. arg5 = get_register(a5); // Abi n64 register a5. 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: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(f13); arg2 = get_fpu_register(f14); arg3 = get_fpu_register(f15); break; case ExternalReference::BUILTIN_FP_CALL: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(f13); break; case ExternalReference::BUILTIN_FP_INT_CALL: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(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. int64_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", static_cast(FUNCTION_ADDR(generic_target)), dval0, dval1); break; case ExternalReference::BUILTIN_FP_CALL: PrintF("Call to host function at %p with arg %f", static_cast(FUNCTION_ADDR(generic_target)), dval0); break; case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Call to host function at %p with args %f, %d", static_cast(FUNCTION_ADDR(generic_target)), dval0, ival); break; default: UNREACHABLE(); break; } } switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: { SimulatorRuntimeCompareCall target = reinterpret_cast(external); iresult = target(dval0, dval1); set_register(v0, static_cast(iresult)); // set_register(v1, static_cast(iresult >> 32)); break; } case ExternalReference::BUILTIN_FP_FP_CALL: { SimulatorRuntimeFPFPCall target = reinterpret_cast(external); dresult = target(dval0, dval1); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_CALL: { SimulatorRuntimeFPCall target = reinterpret_cast(external); dresult = target(dval0); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_INT_CALL: { SimulatorRuntimeFPIntCall target = reinterpret_cast(external); dresult = target(dval0, ival); SetFpResult(dresult); break; } default: UNREACHABLE(); break; } if (::v8::internal::FLAG_trace_sim) { switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: PrintF("Returned %08x\n", static_cast(iresult)); break; case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_FP_CALL: case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Returned %f\n", dresult); break; default: UNREACHABLE(); break; } } } else if (redirection->type() == ExternalReference::DIRECT_API_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08" PRIx64 " \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 %08" PRIx64 " %08" PRIx64 " \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 %08" PRIx64 " %08" PRIx64 " \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 %08" PRIx64 " %08" PRIx64 " %08" PRIx64 " \n", reinterpret_cast(external), arg0, arg1, arg2); } SimulatorRuntimeProfilingGetterCall target = reinterpret_cast(external); target(arg0, arg1, Redirection::ReverseRedirection(arg2)); } else if (redirection->type() == ExternalReference::BUILTIN_CALL_TRIPLE) { // builtin call returning ObjectTriple. SimulatorRuntimeTripleCall target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { PrintF( "Call to host triple returning runtime function %p " "args %016" PRIx64 ", %016" PRIx64 ", %016" PRIx64 ", %016" PRIx64 ", %016" PRIx64 "\n", static_cast(FUNCTION_ADDR(target)), arg1, arg2, arg3, arg4, arg5); } // arg0 is a hidden argument pointing to the return location, so don't // pass it to the target function. ObjectTriple result = target(arg1, arg2, arg3, arg4, arg5); if (::v8::internal::FLAG_trace_sim) { PrintF("Returned { %p, %p, %p }\n", static_cast(result.x), static_cast(result.y), static_cast(result.z)); } // Return is passed back in address pointed to by hidden first argument. ObjectTriple* sim_result = reinterpret_cast(arg0); *sim_result = result; set_register(v0, arg0); } else { DCHECK(redirection->type() == ExternalReference::BUILTIN_CALL || redirection->type() == ExternalReference::BUILTIN_CALL_PAIR); SimulatorRuntimeCall target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { PrintF( "Call to host function at %p " "args %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " \n", static_cast(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)); ObjectPair result = target(arg0, arg1, arg2, arg3, arg4, arg5); set_register(v0, (int64_t)(result.x)); set_register(v1, (int64_t)(result.y)); } if (::v8::internal::FLAG_trace_sim) { PrintF("Returned %08" PRIx64 " : %08" PRIx64 " \n", get_register(v1), get_register(v0)); } set_register(ra, saved_ra); set_pc(get_register(ra)); } else if (func == BREAK && code <= kMaxStopCode) { if (IsWatchpoint(code)) { PrintWatchpoint(code); } else { IncreaseStopCounter(code); HandleStop(code, instr_.instr()); } } else { // All remaining break_ codes, and all traps are handled here. MipsDebugger dbg(this); dbg.Debug(); } } // Stop helper functions. bool Simulator::IsWatchpoint(uint64_t code) { return (code <= kMaxWatchpointCode); } void Simulator::PrintWatchpoint(uint64_t code) { MipsDebugger dbg(this); ++break_count_; PrintF("\n---- break %" PRId64 " marker: %3d (instr count: %8" PRId64 " ) ----------" "----------------------------------", code, break_count_, icount_); dbg.PrintAllRegs(); // Print registers and continue running. } void Simulator::HandleStop(uint64_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(uint64_t code) { DCHECK(code <= kMaxStopCode); DCHECK(code > kMaxWatchpointCode); return !(watched_stops_[code].count & kStopDisabledBit); } void Simulator::EnableStop(uint64_t code) { if (!IsEnabledStop(code)) { watched_stops_[code].count &= ~kStopDisabledBit; } } void Simulator::DisableStop(uint64_t code) { if (IsEnabledStop(code)) { watched_stops_[code].count |= kStopDisabledBit; } } void Simulator::IncreaseStopCounter(uint64_t code) { DCHECK(code <= kMaxStopCode); if ((watched_stops_[code].count & ~(1 << 31)) == 0x7fffffff) { PrintF("Stop counter for code %" PRId64 " 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(uint64_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 %" PRId64 " - 0x%" PRIx64 " : \t%s, \tcounter = %i, \t%s\n", code, code, state, count, watched_stops_[code].desc); } else { PrintF("stop %" PRId64 " - 0x%" PRIx64 " : \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)); } // Min/Max template functions for Double and Single arguments. template static T FPAbs(T a); template <> double FPAbs(double a) { return fabs(a); } template <> float FPAbs(float a) { return fabsf(a); } template static bool FPUProcessNaNsAndZeros(T a, T b, MaxMinKind kind, T& result) { if (std::isnan(a) && std::isnan(b)) { result = a; } else if (std::isnan(a)) { result = b; } else if (std::isnan(b)) { result = a; } else if (b == a) { // Handle -0.0 == 0.0 case. // std::signbit() returns int 0 or 1 so substracting MaxMinKind::kMax // negates the result. result = std::signbit(b) - static_cast(kind) ? b : a; } else { return false; } return true; } template static T FPUMin(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { return result; } else { return b < a ? b : a; } } template static T FPUMax(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMax, result)) { return result; } else { return b > a ? b : a; } } template static T FPUMinA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) < FPAbs(b)) { result = a; } else if (FPAbs(b) < FPAbs(a)) { result = b; } else { result = a < b ? a : b; } } return result; } template static T FPUMaxA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) > FPAbs(b)) { result = a; } else if (FPAbs(b) > FPAbs(a)) { result = b; } else { result = a > b ? a : b; } } return result; } enum class KeepSign : bool { no = false, yes }; template ::value, int>::type = 0> T FPUCanonalizeNaNArg(T result, T arg, KeepSign keepSign = KeepSign::no) { DCHECK(std::isnan(arg)); T qNaN = std::numeric_limits::quiet_NaN(); if (keepSign == KeepSign::yes) { return std::copysign(qNaN, result); } return qNaN; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return result; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first, Args... args) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return FPUCanonalizeNaNArgs(result, keepSign, args...); } template T FPUCanonalizeOperation(Func f, T first, Args... args) { return FPUCanonalizeOperation(f, KeepSign::no, first, args...); } template T FPUCanonalizeOperation(Func f, KeepSign keepSign, T first, Args... args) { T result = f(first, args...); if (std::isnan(result)) { result = FPUCanonalizeNaNArgs(result, keepSign, first, args...); } return result; } // Handle execution based on instruction types. void Simulator::DecodeTypeRegisterSRsType() { float fs, ft, fd; fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); fd = get_fpu_register_float(fd_reg()); int32_t ft_int = bit_cast(ft); int32_t fd_int = bit_cast(fd); uint32_t cc, fcsr_cc; cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(kArchVariant == kMips64r6); 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(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_S: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), fd + (fs * ft)); break; case MSUBF_S: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), fd - (fs * ft)); break; case MUL_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float fs) { return FPAbs(fs); }, fs)); break; case MOV_S: set_fpu_register_float(fd_reg(), fs); break; case NEG_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float src) { return std::sqrt(src); }, fs)); break; case RSQRT_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation( [](float src) { return 1.0 / std::sqrt(src); }, fs)); break; case RECIP_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float src) { return 1.0 / src; }, fs)); 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 CLASS_S: { // Mips64r6 instruction // Convert float input to uint32_t for easier bit manipulation 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 CVT_L_S: { float rounded; int64_t result; 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); } 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; } 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: { // Mips64r2 instruction. float rounded = trunc(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } 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: { // Mips64r2 instruction. 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); set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case FLOOR_L_S: { // Mips64r2 instruction. float rounded = floor(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } 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 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_invalid_result(fs, rounded); } } break; case CEIL_L_S: { // Mips64r2 instruction. float rounded = ceil(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case MINA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMinA(ft, fs)); break; case MAXA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMaxA(ft, fs)); break; case MIN: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMin(ft, fs)); break; case MAX: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMax(ft, fs)); break; case SEL: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), (ft_int & 0x1) == 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case SELNEZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), (ft_int & 0x1) != 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case MOVZ_C: { DCHECK(kArchVariant == kMips64r2); if (rt() == 0) { set_fpu_register_float(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(kArchVariant == kMips64r2); 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 (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; } default: // TRUNC_W_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::DecodeTypeRegisterDRsType() { double ft, fs, fd; uint32_t cc, fcsr_cc; fs = get_fpu_register_double(fs_reg()); ft = (instr_.FunctionFieldRaw() != MOVF) ? get_fpu_register_double(ft_reg()) : 0.0; fd = get_fpu_register_double(fd_reg()); cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); int64_t ft_int = bit_cast(ft); int64_t fd_int = bit_cast(fd); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(kArchVariant == kMips64r6); 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(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), (ft_int & 0x1) == 0 ? fs : 0.0); break; case SELNEZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), (ft_int & 0x1) != 0 ? fs : 0.0); break; case MOVZ_C: { DCHECK(kArchVariant == kMips64r2); if (rt() == 0) { set_fpu_register_double(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(kArchVariant == kMips64r2); 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 (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 MINA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMinA(ft, fs)); break; case MAXA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMaxA(ft, fs)); break; case MIN: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMin(ft, fs)); break; case MAX: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMax(ft, fs)); break; case ADD_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_D: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), fd + (fs * ft)); break; case MSUBF_D: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), fd - (fs * ft)); break; case MUL_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double fs) { return FPAbs(fs); }, fs)); break; case MOV_D: set_fpu_register_double(fd_reg(), fs); break; case NEG_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double fs) { return std::sqrt(fs); }, fs)); break; case RSQRT_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double fs) { return 1.0 / std::sqrt(fs); }, fs)); break; case RECIP_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double fs) { return 1.0 / fs; }, fs)); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); 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_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_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_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_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: { // Mips64r2: Truncate double to 64-bit long-word. double rounded; int64_t result; 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); } break; } case ROUND_L_D: { // Mips64r2 instruction. 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); set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case TRUNC_L_D: { // Mips64r2 instruction. double rounded = trunc(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case FLOOR_L_D: { // Mips64r2 instruction. double rounded = floor(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case CEIL_L_D: { // Mips64r2 instruction. double rounded = ceil(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case CLASS_D: { // Mips64r6 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()); int64_t alu_out = 0x12345678; switch (instr_.FunctionFieldRaw()) { case CVT_S_W: // Convert word to float (single). alu_out = get_fpu_register_signed_word(fs_reg()); 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::DecodeTypeRegisterLRsType() { double fs = get_fpu_register_double(fs_reg()); double ft = get_fpu_register_double(ft_reg()); int64_t i64; switch (instr_.FunctionFieldRaw()) { case CVT_D_L: // Mips32r2 instruction. i64 = get_fpu_register(fs_reg()); set_fpu_register_double(fd_reg(), static_cast(i64)); break; case CVT_S_L: i64 = get_fpu_register(fs_reg()); set_fpu_register_float(fd_reg(), static_cast(i64)); break; case CMP_AF: 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 (instr_.RsFieldRaw()) { case BC1: // Branch on coprocessor condition. case BC1EQZ: case BC1NEZ: UNREACHABLE(); break; 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(), static_cast(get_fpu_register_word(fs_reg()))); break; case DMFC1: set_register(rt_reg(), get_fpu_register(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); uint32_t reg = static_cast(rt()); if (kArchVariant == kMips64r6) { FCSR_ = reg | kFCSRNaN2008FlagMask; } else { DCHECK(kArchVariant == kMips64r2); 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(), static_cast(rt())); break; case DMTC1: set_fpu_register(fs_reg(), rt()); break; case MTHC1: set_fpu_register_hi_word(fs_reg(), static_cast(rt())); break; case S: DecodeTypeRegisterSRsType(); break; case D: DecodeTypeRegisterDRsType(); break; case W: DecodeTypeRegisterWRsType(); break; case L: DecodeTypeRegisterLRsType(); break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterCOP1X() { switch (instr_.FunctionFieldRaw()) { case MADD_S: { DCHECK(kArchVariant == kMips64r2); float fr, ft, fs; fr = get_fpu_register_float(fr_reg()); fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); set_fpu_register_float(fd_reg(), fs * ft + fr); break; } case MSUB_S: { DCHECK(kArchVariant == kMips64r2); float fr, ft, fs; fr = get_fpu_register_float(fr_reg()); fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); set_fpu_register_float(fd_reg(), fs * ft - fr); break; } case MADD_D: { DCHECK(kArchVariant == kMips64r2); 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; } case MSUB_D: { DCHECK(kArchVariant == kMips64r2); 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 i64hilo; uint64_t u64hilo; int64_t alu_out; bool do_interrupt = false; switch (instr_.FunctionFieldRaw()) { case SELEQZ_S: DCHECK(kArchVariant == kMips64r6); set_register(rd_reg(), rt() == 0 ? rs() : 0); break; case SELNEZ_S: DCHECK(kArchVariant == kMips64r6); set_register(rd_reg(), rt() != 0 ? rs() : 0); break; case JR: { int64_t next_pc = rs(); int64_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: { int64_t next_pc = rs(); int64_t current_pc = get_pc(); int32_t return_addr_reg = rd_reg(); 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: SetResult(rd_reg(), static_cast(rt()) << sa()); break; case DSLL: SetResult(rd_reg(), rt() << sa()); break; case DSLL32: SetResult(rd_reg(), rt() << sa() << 32); 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. // Sign-extend the 32-bit result. alu_out = static_cast(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, size_t 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(base::CustomMatcherHashMap* i_cache, void* start_addr, size_t size) { int64_t start = reinterpret_cast(start_addr); int64_t 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((int64_t)0, start & CachePage::kPageMask); offset = 0; } if (size != 0) { FlushOnePage(i_cache, start, size); } } CachePage* Simulator::GetCachePage(base::CustomMatcherHashMap* i_cache, void* page) { base::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(base::CustomMatcherHashMap* i_cache, intptr_t start, size_t 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(base::CustomMatcherHashMap* i_cache, Instruction* instr) { int64_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 base::CustomMatcherHashMap(&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_size_ = FLAG_sim_stack_size * KB; 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 (kArchVariant == kMips64r6) { FCSR_ = kFCSRNaN2008FlagMask; } else { FCSR_ = 0; } // The sp is initialized to point to the bottom (high address) of the // allocated stack area. To be safe in potential stack underflows we leave // some buffer below. registers_[sp] = reinterpret_cast(stack_) + stack_size_ - 64; // The ra and pc are initialized to a known bad value that will cause an // access violation if the simulator ever tries to execute it. registers_[pc] = bad_ra; registers_[ra] = bad_ra; last_debugger_input_ = 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(int64_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(base::CustomMatcherHashMap* i_cache, Redirection* first) { Redirection::DeleteChain(first); if (i_cache != nullptr) { for (base::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, int64_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[1]; registers_[reg] = registers_[reg] << 32; registers_[reg] += dbl[0]; } void Simulator::set_fpu_register(int fpureg, int64_t value) { 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. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* pword; if (kArchEndian == kLittle) { pword = reinterpret_cast(&FPUregisters_[fpureg]); } else { pword = reinterpret_cast(&FPUregisters_[fpureg]) + 1; } *pword = value; } void Simulator::set_fpu_register_hi_word(int fpureg, int32_t value) { // Set ONLY upper 32-bits, leaving lower bits untouched. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* phiword; if (kArchEndian == kLittle) { phiword = (reinterpret_cast(&FPUregisters_[fpureg])) + 1; } else { phiword = reinterpret_cast(&FPUregisters_[fpureg]); } *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) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg]) = value; } // Get the register from the architecture state. This function does handle // the special case of accessing the PC register. int64_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[sizeof(registers_[0])]; memcpy(buffer, ®isters_[reg], sizeof(registers_[0])); memcpy(&dm_val, buffer, sizeof(registers_[0])); return(dm_val); } int64_t Simulator::get_fpu_register(int fpureg) const { 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 { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(&FPUregisters_[fpureg]); } // Runtime FP routines take up to two double arguments and zero // or one integer arguments. All are constructed here, // from a0-a3 or f12 and f13 (n64), or f14 (O32). void Simulator::GetFpArgs(double* x, double* y, int32_t* z) { if (!IsMipsSoftFloatABI) { const int fparg2 = 13; *x = get_fpu_register_double(12); *y = get_fpu_register_double(fparg2); *z = static_cast(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])]; int64_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; } // 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; } 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_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; } // For cvt instructions only 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 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(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; } } // for cvt instructions only 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 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(int64_t value) { pc_modified_ = true; registers_[pc] = value; } bool Simulator::has_bad_pc() const { return ((registers_[pc] == bad_ra) || (registers_[pc] == end_sim_pc)); } // Raw access to the PC register without the special adjustment when reading. int64_t Simulator::get_pc() const { return registers_[pc]; } // 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. // TODO(plind): refactor this messy debug code when we do unaligned access. void Simulator::DieOrDebug() { if (1) { // Flag for this was removed. MipsDebugger dbg(this); dbg.Debug(); } else { base::OS::Abort(); } } void Simulator::TraceRegWr(int64_t value) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%016" PRIx64 " ", value); } } // TODO(plind): consider making icount_ printing a flag option. void Simulator::TraceMemRd(int64_t addr, int64_t value) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%016" PRIx64 " <-- [%016" PRIx64 " ] (%" PRId64 " )", value, addr, icount_); } } void Simulator::TraceMemWr(int64_t addr, int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case BYTE: SNPrintF(trace_buf_, " %02x --> [%016" PRIx64 " ]", static_cast(value), addr); break; case HALF: SNPrintF(trace_buf_, " %04x --> [%016" PRIx64 " ]", static_cast(value), addr); break; case WORD: SNPrintF(trace_buf_, " %08x --> [%016" PRIx64 " ]", static_cast(value), addr); break; case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " --> [%016" PRIx64 " ] (%" PRId64 " )", value, addr, icount_); break; } } } // TODO(plind): sign-extend and zero-extend not implmented properly // on all the ReadXX functions, I don't think re-interpret cast does it. int32_t Simulator::ReadW(int64_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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & 0x3) == 0 || kArchVariant == kMips64r6) { int32_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } uint32_t Simulator::ReadWU(int64_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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & 0x3) == 0 || kArchVariant == kMips64r6) { uint32_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } void Simulator::WriteW(int64_t addr, int32_t 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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & 0x3) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, WORD); int* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } int64_t Simulator::Read2W(int64_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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & kPointerAlignmentMask) == 0 || kArchVariant == kMips64r6) { int64_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, *ptr); return *ptr; } PrintF("Unaligned read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } void Simulator::Write2W(int64_t addr, int64_t 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%08" PRIx64 " , pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & kPointerAlignmentMask) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, DWORD); int64_t* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } double Simulator::ReadD(int64_t addr, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || kArchVariant == kMips64r6) { double* ptr = reinterpret_cast(addr); return *ptr; } PrintF("Unaligned (double) read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } void Simulator::WriteD(int64_t addr, double value, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || kArchVariant == kMips64r6) { double* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned (double) write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } uint16_t Simulator::ReadHU(int64_t addr, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { uint16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned unsigned halfword read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } int16_t Simulator::ReadH(int64_t addr, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { int16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned signed halfword read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } void Simulator::WriteH(int64_t addr, uint16_t value, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, HALF); uint16_t* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned unsigned halfword write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } void Simulator::WriteH(int64_t addr, int16_t value, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, HALF); int16_t* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned halfword write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } uint32_t Simulator::ReadBU(int64_t addr) { uint8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr & 0xff; } int32_t Simulator::ReadB(int64_t addr) { int8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } void Simulator::WriteB(int64_t addr, uint8_t value) { TraceMemWr(addr, value, BYTE); uint8_t* ptr = reinterpret_cast(addr); *ptr = value; } void Simulator::WriteB(int64_t addr, int8_t value) { TraceMemWr(addr, value, BYTE); int8_t* ptr = reinterpret_cast(addr); *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%08" PRIxPTR " : %s\n", reinterpret_cast(instr), format); UNIMPLEMENTED_MIPS(); } // Calls into the V8 runtime are based on this very simple interface. // Note: To be able to return two values from some calls the code in runtime.cc // uses the ObjectPair which is essentially two 32-bit values stuffed into a // 64-bit value. With the code below we assume that all runtime calls return // 64 bits of result. If they don't, the v1 result register contains a bogus // value, which is fine because it is caller-saved. typedef ObjectPair (*SimulatorRuntimeCall)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4, int64_t arg5); typedef ObjectTriple (*SimulatorRuntimeTripleCall)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4); // 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)(int64_t arg0); typedef void (*SimulatorRuntimeProfilingApiCall)(int64_t arg0, void* arg1); // This signature supports direct call to accessor getter callback. typedef void (*SimulatorRuntimeDirectGetterCall)(int64_t arg0, int64_t arg1); typedef void (*SimulatorRuntimeProfilingGetterCall)( int64_t arg0, int64_t arg1, void* arg2); // Software interrupt instructions are used by the simulator to call into the // C-based V8 runtime. They are also used for debugging with simulator. void Simulator::SoftwareInterrupt() { // There are several instructions that could get us here, // the break_ instruction, or several variants of traps. All // Are "SPECIAL" class opcode, and are distinuished by function. int32_t func = instr_.FunctionFieldRaw(); uint32_t code = (func == BREAK) ? instr_.Bits(25, 6) : -1; // We first check if we met a call_rt_redirected. if (instr_.InstructionBits() == rtCallRedirInstr) { Redirection* redirection = Redirection::FromSwiInstruction(instr_.instr()); int64_t arg0 = get_register(a0); int64_t arg1 = get_register(a1); int64_t arg2 = get_register(a2); int64_t arg3 = get_register(a3); int64_t arg4, arg5; arg4 = get_register(a4); // Abi n64 register a4. arg5 = get_register(a5); // Abi n64 register a5. 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: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(f13); arg2 = get_fpu_register(f14); arg3 = get_fpu_register(f15); break; case ExternalReference::BUILTIN_FP_CALL: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(f13); break; case ExternalReference::BUILTIN_FP_INT_CALL: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(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. int64_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", static_cast(FUNCTION_ADDR(generic_target)), dval0, dval1); break; case ExternalReference::BUILTIN_FP_CALL: PrintF("Call to host function at %p with arg %f", static_cast(FUNCTION_ADDR(generic_target)), dval0); break; case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Call to host function at %p with args %f, %d", static_cast(FUNCTION_ADDR(generic_target)), dval0, ival); break; default: UNREACHABLE(); break; } } switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: { SimulatorRuntimeCompareCall target = reinterpret_cast(external); iresult = target(dval0, dval1); set_register(v0, static_cast(iresult)); // set_register(v1, static_cast(iresult >> 32)); break; } case ExternalReference::BUILTIN_FP_FP_CALL: { SimulatorRuntimeFPFPCall target = reinterpret_cast(external); dresult = target(dval0, dval1); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_CALL: { SimulatorRuntimeFPCall target = reinterpret_cast(external); dresult = target(dval0); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_INT_CALL: { SimulatorRuntimeFPIntCall target = reinterpret_cast(external); dresult = target(dval0, ival); SetFpResult(dresult); break; } default: UNREACHABLE(); break; } if (::v8::internal::FLAG_trace_sim) { switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: PrintF("Returned %08x\n", static_cast(iresult)); break; case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_FP_CALL: case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Returned %f\n", dresult); break; default: UNREACHABLE(); break; } } } else if (redirection->type() == ExternalReference::DIRECT_API_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08" PRIx64 " \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 %08" PRIx64 " %08" PRIx64 " \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 %08" PRIx64 " %08" PRIx64 " \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 %08" PRIx64 " %08" PRIx64 " %08" PRIx64 " \n", reinterpret_cast(external), arg0, arg1, arg2); } SimulatorRuntimeProfilingGetterCall target = reinterpret_cast(external); target(arg0, arg1, Redirection::ReverseRedirection(arg2)); } else if (redirection->type() == ExternalReference::BUILTIN_CALL_TRIPLE) { // builtin call returning ObjectTriple. SimulatorRuntimeTripleCall target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { PrintF( "Call to host triple returning runtime function %p " "args %016" PRIx64 ", %016" PRIx64 ", %016" PRIx64 ", %016" PRIx64 ", %016" PRIx64 "\n", static_cast(FUNCTION_ADDR(target)), arg1, arg2, arg3, arg4, arg5); } // arg0 is a hidden argument pointing to the return location, so don't // pass it to the target function. ObjectTriple result = target(arg1, arg2, arg3, arg4, arg5); if (::v8::internal::FLAG_trace_sim) { PrintF("Returned { %p, %p, %p }\n", static_cast(result.x), static_cast(result.y), static_cast(result.z)); } // Return is passed back in address pointed to by hidden first argument. ObjectTriple* sim_result = reinterpret_cast(arg0); *sim_result = result; set_register(v0, arg0); } else { DCHECK(redirection->type() == ExternalReference::BUILTIN_CALL || redirection->type() == ExternalReference::BUILTIN_CALL_PAIR); SimulatorRuntimeCall target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { PrintF( "Call to host function at %p " "args %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " \n", static_cast(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)); ObjectPair result = target(arg0, arg1, arg2, arg3, arg4, arg5); set_register(v0, (int64_t)(result.x)); set_register(v1, (int64_t)(result.y)); } if (::v8::internal::FLAG_trace_sim) { PrintF("Returned %08" PRIx64 " : %08" PRIx64 " \n", get_register(v1), get_register(v0)); } set_register(ra, saved_ra); set_pc(get_register(ra)); } else if (func == BREAK && code <= kMaxStopCode) { if (IsWatchpoint(code)) { PrintWatchpoint(code); } else { IncreaseStopCounter(code); HandleStop(code, instr_.instr()); } } else { // All remaining break_ codes, and all traps are handled here. MipsDebugger dbg(this); dbg.Debug(); } } // Stop helper functions. bool Simulator::IsWatchpoint(uint64_t code) { return (code <= kMaxWatchpointCode); } void Simulator::PrintWatchpoint(uint64_t code) { MipsDebugger dbg(this); ++break_count_; PrintF("\n---- break %" PRId64 " marker: %3d (instr count: %8" PRId64 " ) ----------" "----------------------------------", code, break_count_, icount_); dbg.PrintAllRegs(); // Print registers and continue running. } void Simulator::HandleStop(uint64_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(uint64_t code) { DCHECK(code <= kMaxStopCode); DCHECK(code > kMaxWatchpointCode); return !(watched_stops_[code].count & kStopDisabledBit); } void Simulator::EnableStop(uint64_t code) { if (!IsEnabledStop(code)) { watched_stops_[code].count &= ~kStopDisabledBit; } } void Simulator::DisableStop(uint64_t code) { if (IsEnabledStop(code)) { watched_stops_[code].count |= kStopDisabledBit; } } void Simulator::IncreaseStopCounter(uint64_t code) { DCHECK(code <= kMaxStopCode); if ((watched_stops_[code].count & ~(1 << 31)) == 0x7fffffff) { PrintF("Stop counter for code %" PRId64 " 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(uint64_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 %" PRId64 " - 0x%" PRIx64 " : \t%s, \tcounter = %i, \t%s\n", code, code, state, count, watched_stops_[code].desc); } else { PrintF("stop %" PRId64 " - 0x%" PRIx64 " : \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)); } // Min/Max template functions for Double and Single arguments. template static T FPAbs(T a); template <> double FPAbs(double a) { return fabs(a); } template <> float FPAbs(float a) { return fabsf(a); } template static bool FPUProcessNaNsAndZeros(T a, T b, MaxMinKind kind, T& result) { if (std::isnan(a) && std::isnan(b)) { result = a; } else if (std::isnan(a)) { result = b; } else if (std::isnan(b)) { result = a; } else if (b == a) { // Handle -0.0 == 0.0 case. // std::signbit() returns int 0 or 1 so substracting MaxMinKind::kMax // negates the result. result = std::signbit(b) - static_cast(kind) ? b : a; } else { return false; } return true; } template static T FPUMin(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { return result; } else { return b < a ? b : a; } } template static T FPUMax(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMax, result)) { return result; } else { return b > a ? b : a; } } template static T FPUMinA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) < FPAbs(b)) { result = a; } else if (FPAbs(b) < FPAbs(a)) { result = b; } else { result = a < b ? a : b; } } return result; } template static T FPUMaxA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) > FPAbs(b)) { result = a; } else if (FPAbs(b) > FPAbs(a)) { result = b; } else { result = a > b ? a : b; } } return result; } enum class KeepSign : bool { no = false, yes }; template ::value, int>::type = 0> T FPUCanonalizeNaNArg(T result, T arg, KeepSign keepSign = KeepSign::no) { DCHECK(std::isnan(arg)); T qNaN = std::numeric_limits::quiet_NaN(); if (keepSign == KeepSign::yes) { return std::copysign(qNaN, result); } return qNaN; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return result; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first, Args... args) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return FPUCanonalizeNaNArgs(result, keepSign, args...); } template T FPUCanonalizeOperation(Func f, T first, Args... args) { return FPUCanonalizeOperation(f, KeepSign::no, first, args...); } template T FPUCanonalizeOperation(Func f, KeepSign keepSign, T first, Args... args) { T result = f(first, args...); if (std::isnan(result)) { result = FPUCanonalizeNaNArgs(result, keepSign, first, args...); } return result; } // Handle execution based on instruction types. void Simulator::DecodeTypeRegisterSRsType() { float fs, ft, fd; fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); fd = get_fpu_register_float(fd_reg()); int32_t ft_int = bit_cast(ft); int32_t fd_int = bit_cast(fd); uint32_t cc, fcsr_cc; cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(kArchVariant == kMips64r6); 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(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_S: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), fd + (fs * ft)); break; case MSUBF_S: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), fd - (fs * ft)); break; case MUL_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float fs) { return FPAbs(fs); }, fs)); break; case MOV_S: set_fpu_register_float(fd_reg(), fs); break; case NEG_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float src) { return std::sqrt(src); }, fs)); break; case RSQRT_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation( [](float src) { return 1.0 / std::sqrt(src); }, fs)); break; case RECIP_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float src) { return 1.0 / src; }, fs)); 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 CLASS_S: { // Mips64r6 instruction // Convert float input to uint32_t for easier bit manipulation 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 CVT_L_S: { float rounded; int64_t result; 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); } 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; } 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: { // Mips64r2 instruction. float rounded = trunc(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } 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: { // Mips64r2 instruction. 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); set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case FLOOR_L_S: { // Mips64r2 instruction. float rounded = floor(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } 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 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_invalid_result(fs, rounded); } } break; case CEIL_L_S: { // Mips64r2 instruction. float rounded = ceil(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case MINA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMinA(ft, fs)); break; case MAXA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMaxA(ft, fs)); break; case MIN: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMin(ft, fs)); break; case MAX: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMax(ft, fs)); break; case SEL: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), (ft_int & 0x1) == 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case SELNEZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), (ft_int & 0x1) != 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case MOVZ_C: { DCHECK(kArchVariant == kMips64r2); if (rt() == 0) { set_fpu_register_float(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(kArchVariant == kMips64r2); 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 (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; } default: // TRUNC_W_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::DecodeTypeRegisterDRsType() { double ft, fs, fd; uint32_t cc, fcsr_cc; fs = get_fpu_register_double(fs_reg()); ft = (instr_.FunctionFieldRaw() != MOVF) ? get_fpu_register_double(ft_reg()) : 0.0; fd = get_fpu_register_double(fd_reg()); cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); int64_t ft_int = bit_cast(ft); int64_t fd_int = bit_cast(fd); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(kArchVariant == kMips64r6); 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(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), (ft_int & 0x1) == 0 ? fs : 0.0); break; case SELNEZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), (ft_int & 0x1) != 0 ? fs : 0.0); break; case MOVZ_C: { DCHECK(kArchVariant == kMips64r2); if (rt() == 0) { set_fpu_register_double(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(kArchVariant == kMips64r2); 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 (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 MINA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMinA(ft, fs)); break; case MAXA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMaxA(ft, fs)); break; case MIN: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMin(ft, fs)); break; case MAX: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMax(ft, fs)); break; case ADD_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_D: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), fd + (fs * ft)); break; case MSUBF_D: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), fd - (fs * ft)); break; case MUL_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double fs) { return FPAbs(fs); }, fs)); break; case MOV_D: set_fpu_register_double(fd_reg(), fs); break; case NEG_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double fs) { return std::sqrt(fs); }, fs)); break; case RSQRT_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double fs) { return 1.0 / std::sqrt(fs); }, fs)); break; case RECIP_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double fs) { return 1.0 / fs; }, fs)); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); 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_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_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_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_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: { // Mips64r2: Truncate double to 64-bit long-word. double rounded; int64_t result; 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); } break; } case ROUND_L_D: { // Mips64r2 instruction. 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); set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case TRUNC_L_D: { // Mips64r2 instruction. double rounded = trunc(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case FLOOR_L_D: { // Mips64r2 instruction. double rounded = floor(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case CEIL_L_D: { // Mips64r2 instruction. double rounded = ceil(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case CLASS_D: { // Mips64r6 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()); int64_t alu_out = 0x12345678; switch (instr_.FunctionFieldRaw()) { case CVT_S_W: // Convert word to float (single). alu_out = get_fpu_register_signed_word(fs_reg()); 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::DecodeTypeRegisterLRsType() { double fs = get_fpu_register_double(fs_reg()); double ft = get_fpu_register_double(ft_reg()); int64_t i64; switch (instr_.FunctionFieldRaw()) { case CVT_D_L: // Mips32r2 instruction. i64 = get_fpu_register(fs_reg()); set_fpu_register_double(fd_reg(), static_cast(i64)); break; case CVT_S_L: i64 = get_fpu_register(fs_reg()); set_fpu_register_float(fd_reg(), static_cast(i64)); break; case CMP_AF: 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 (instr_.RsFieldRaw()) { case BC1: // Branch on coprocessor condition. case BC1EQZ: case BC1NEZ: UNREACHABLE(); break; 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(), static_cast(get_fpu_register_word(fs_reg()))); break; case DMFC1: set_register(rt_reg(), get_fpu_register(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); uint32_t reg = static_cast(rt()); if (kArchVariant == kMips64r6) { FCSR_ = reg | kFCSRNaN2008FlagMask; } else { DCHECK(kArchVariant == kMips64r2); 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(), static_cast(rt())); break; case DMTC1: set_fpu_register(fs_reg(), rt()); break; case MTHC1: set_fpu_register_hi_word(fs_reg(), static_cast(rt())); break; case S: DecodeTypeRegisterSRsType(); break; case D: DecodeTypeRegisterDRsType(); break; case W: DecodeTypeRegisterWRsType(); break; case L: DecodeTypeRegisterLRsType(); break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterCOP1X() { switch (instr_.FunctionFieldRaw()) { case MADD_S: { DCHECK(kArchVariant == kMips64r2); float fr, ft, fs; fr = get_fpu_register_float(fr_reg()); fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); set_fpu_register_float(fd_reg(), fs * ft + fr); break; } case MSUB_S: { DCHECK(kArchVariant == kMips64r2); float fr, ft, fs; fr = get_fpu_register_float(fr_reg()); fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); set_fpu_register_float(fd_reg(), fs * ft - fr); break; } case MADD_D: { DCHECK(kArchVariant == kMips64r2); 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; } case MSUB_D: { DCHECK(kArchVariant == kMips64r2); 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 i64hilo; uint64_t u64hilo; int64_t alu_out; bool do_interrupt = false; switch (instr_.FunctionFieldRaw()) { case SELEQZ_S: DCHECK(kArchVariant == kMips64r6); set_register(rd_reg(), rt() == 0 ? rs() : 0); break; case SELNEZ_S: DCHECK(kArchVariant == kMips64r6); set_register(rd_reg(), rt() != 0 ? rs() : 0); break; case JR: { int64_t next_pc = rs(); int64_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: { int64_t next_pc = rs(); int64_t current_pc = get_pc(); int32_t return_addr_reg = rd_reg(); 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: SetResult(rd_reg(), static_cast(rt()) << sa()); break; case DSLL: SetResult(rd_reg(), rt() << sa()); break; case DSLL32: SetResult(rd_reg(), rt() << sa() << 32); 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. // Sign-extend the 32-bit result. alu_out = static_cast(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, size_t 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(base::CustomMatcherHashMap* i_cache, void* start_addr, size_t size) { int64_t start = reinterpret_cast(start_addr); int64_t 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((int64_t)0, start & CachePage::kPageMask); offset = 0; } if (size != 0) { FlushOnePage(i_cache, start, size); } } CachePage* Simulator::GetCachePage(base::CustomMatcherHashMap* i_cache, void* page) { base::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(base::CustomMatcherHashMap* i_cache, intptr_t start, size_t 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(base::CustomMatcherHashMap* i_cache, Instruction* instr) { int64_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 base::CustomMatcherHashMap(&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_size_ = FLAG_sim_stack_size * KB; 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 (kArchVariant == kMips64r6) { FCSR_ = kFCSRNaN2008FlagMask; } else { FCSR_ = 0; } // The sp is initialized to point to the bottom (high address) of the // allocated stack area. To be safe in potential stack underflows we leave // some buffer below. registers_[sp] = reinterpret_cast(stack_) + stack_size_ - 64; // The ra and pc are initialized to a known bad value that will cause an // access violation if the simulator ever tries to execute it. registers_[pc] = bad_ra; registers_[ra] = bad_ra; last_debugger_input_ = 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(int64_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(base::CustomMatcherHashMap* i_cache, Redirection* first) { Redirection::DeleteChain(first); if (i_cache != nullptr) { for (base::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, int64_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[1]; registers_[reg] = registers_[reg] << 32; registers_[reg] += dbl[0]; } void Simulator::set_fpu_register(int fpureg, int64_t value) { 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. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* pword; if (kArchEndian == kLittle) { pword = reinterpret_cast(&FPUregisters_[fpureg]); } else { pword = reinterpret_cast(&FPUregisters_[fpureg]) + 1; } *pword = value; } void Simulator::set_fpu_register_hi_word(int fpureg, int32_t value) { // Set ONLY upper 32-bits, leaving lower bits untouched. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* phiword; if (kArchEndian == kLittle) { phiword = (reinterpret_cast(&FPUregisters_[fpureg])) + 1; } else { phiword = reinterpret_cast(&FPUregisters_[fpureg]); } *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) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg]) = value; } // Get the register from the architecture state. This function does handle // the special case of accessing the PC register. int64_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[sizeof(registers_[0])]; memcpy(buffer, ®isters_[reg], sizeof(registers_[0])); memcpy(&dm_val, buffer, sizeof(registers_[0])); return(dm_val); } int64_t Simulator::get_fpu_register(int fpureg) const { 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 { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(&FPUregisters_[fpureg]); } // Runtime FP routines take up to two double arguments and zero // or one integer arguments. All are constructed here, // from a0-a3 or f12 and f13 (n64), or f14 (O32). void Simulator::GetFpArgs(double* x, double* y, int32_t* z) { if (!IsMipsSoftFloatABI) { const int fparg2 = 13; *x = get_fpu_register_double(12); *y = get_fpu_register_double(fparg2); *z = static_cast(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])]; int64_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; } // 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; } 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_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; } // For cvt instructions only 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 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(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; } } // for cvt instructions only 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 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(int64_t value) { pc_modified_ = true; registers_[pc] = value; } bool Simulator::has_bad_pc() const { return ((registers_[pc] == bad_ra) || (registers_[pc] == end_sim_pc)); } // Raw access to the PC register without the special adjustment when reading. int64_t Simulator::get_pc() const { return registers_[pc]; } // 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. // TODO(plind): refactor this messy debug code when we do unaligned access. void Simulator::DieOrDebug() { if (1) { // Flag for this was removed. MipsDebugger dbg(this); dbg.Debug(); } else { base::OS::Abort(); } } void Simulator::TraceRegWr(int64_t value) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%016" PRIx64 " ", value); } } // TODO(plind): consider making icount_ printing a flag option. void Simulator::TraceMemRd(int64_t addr, int64_t value) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%016" PRIx64 " <-- [%016" PRIx64 " ] (%" PRId64 " )", value, addr, icount_); } } void Simulator::TraceMemWr(int64_t addr, int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case BYTE: SNPrintF(trace_buf_, " %02x --> [%016" PRIx64 " ]", static_cast(value), addr); break; case HALF: SNPrintF(trace_buf_, " %04x --> [%016" PRIx64 " ]", static_cast(value), addr); break; case WORD: SNPrintF(trace_buf_, " %08x --> [%016" PRIx64 " ]", static_cast(value), addr); break; case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " --> [%016" PRIx64 " ] (%" PRId64 " )", value, addr, icount_); break; } } } // TODO(plind): sign-extend and zero-extend not implmented properly // on all the ReadXX functions, I don't think re-interpret cast does it. int32_t Simulator::ReadW(int64_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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & 0x3) == 0 || kArchVariant == kMips64r6) { int32_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } uint32_t Simulator::ReadWU(int64_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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & 0x3) == 0 || kArchVariant == kMips64r6) { uint32_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } void Simulator::WriteW(int64_t addr, int32_t 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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & 0x3) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, WORD); int* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } int64_t Simulator::Read2W(int64_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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & kPointerAlignmentMask) == 0 || kArchVariant == kMips64r6) { int64_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, *ptr); return *ptr; } PrintF("Unaligned read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } void Simulator::Write2W(int64_t addr, int64_t 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%08" PRIx64 " , pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & kPointerAlignmentMask) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, DWORD); int64_t* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } double Simulator::ReadD(int64_t addr, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || kArchVariant == kMips64r6) { double* ptr = reinterpret_cast(addr); return *ptr; } PrintF("Unaligned (double) read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } void Simulator::WriteD(int64_t addr, double value, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || kArchVariant == kMips64r6) { double* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned (double) write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } uint16_t Simulator::ReadHU(int64_t addr, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { uint16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned unsigned halfword read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } int16_t Simulator::ReadH(int64_t addr, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { int16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned signed halfword read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } void Simulator::WriteH(int64_t addr, uint16_t value, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, HALF); uint16_t* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned unsigned halfword write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } void Simulator::WriteH(int64_t addr, int16_t value, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, HALF); int16_t* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned halfword write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } uint32_t Simulator::ReadBU(int64_t addr) { uint8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr & 0xff; } int32_t Simulator::ReadB(int64_t addr) { int8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } void Simulator::WriteB(int64_t addr, uint8_t value) { TraceMemWr(addr, value, BYTE); uint8_t* ptr = reinterpret_cast(addr); *ptr = value; } void Simulator::WriteB(int64_t addr, int8_t value) { TraceMemWr(addr, value, BYTE); int8_t* ptr = reinterpret_cast(addr); *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%08" PRIxPTR " : %s\n", reinterpret_cast(instr), format); UNIMPLEMENTED_MIPS(); } // Calls into the V8 runtime are based on this very simple interface. // Note: To be able to return two values from some calls the code in runtime.cc // uses the ObjectPair which is essentially two 32-bit values stuffed into a // 64-bit value. With the code below we assume that all runtime calls return // 64 bits of result. If they don't, the v1 result register contains a bogus // value, which is fine because it is caller-saved. typedef ObjectPair (*SimulatorRuntimeCall)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4, int64_t arg5); typedef ObjectTriple (*SimulatorRuntimeTripleCall)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4); // 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)(int64_t arg0); typedef void (*SimulatorRuntimeProfilingApiCall)(int64_t arg0, void* arg1); // This signature supports direct call to accessor getter callback. typedef void (*SimulatorRuntimeDirectGetterCall)(int64_t arg0, int64_t arg1); typedef void (*SimulatorRuntimeProfilingGetterCall)( int64_t arg0, int64_t arg1, void* arg2); // Software interrupt instructions are used by the simulator to call into the // C-based V8 runtime. They are also used for debugging with simulator. void Simulator::SoftwareInterrupt() { // There are several instructions that could get us here, // the break_ instruction, or several variants of traps. All // Are "SPECIAL" class opcode, and are distinuished by function. int32_t func = instr_.FunctionFieldRaw(); uint32_t code = (func == BREAK) ? instr_.Bits(25, 6) : -1; // We first check if we met a call_rt_redirected. if (instr_.InstructionBits() == rtCallRedirInstr) { Redirection* redirection = Redirection::FromSwiInstruction(instr_.instr()); int64_t arg0 = get_register(a0); int64_t arg1 = get_register(a1); int64_t arg2 = get_register(a2); int64_t arg3 = get_register(a3); int64_t arg4, arg5; arg4 = get_register(a4); // Abi n64 register a4. arg5 = get_register(a5); // Abi n64 register a5. 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: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(f13); arg2 = get_fpu_register(f14); arg3 = get_fpu_register(f15); break; case ExternalReference::BUILTIN_FP_CALL: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(f13); break; case ExternalReference::BUILTIN_FP_INT_CALL: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(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. int64_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", static_cast(FUNCTION_ADDR(generic_target)), dval0, dval1); break; case ExternalReference::BUILTIN_FP_CALL: PrintF("Call to host function at %p with arg %f", static_cast(FUNCTION_ADDR(generic_target)), dval0); break; case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Call to host function at %p with args %f, %d", static_cast(FUNCTION_ADDR(generic_target)), dval0, ival); break; default: UNREACHABLE(); break; } } switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: { SimulatorRuntimeCompareCall target = reinterpret_cast(external); iresult = target(dval0, dval1); set_register(v0, static_cast(iresult)); // set_register(v1, static_cast(iresult >> 32)); break; } case ExternalReference::BUILTIN_FP_FP_CALL: { SimulatorRuntimeFPFPCall target = reinterpret_cast(external); dresult = target(dval0, dval1); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_CALL: { SimulatorRuntimeFPCall target = reinterpret_cast(external); dresult = target(dval0); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_INT_CALL: { SimulatorRuntimeFPIntCall target = reinterpret_cast(external); dresult = target(dval0, ival); SetFpResult(dresult); break; } default: UNREACHABLE(); break; } if (::v8::internal::FLAG_trace_sim) { switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: PrintF("Returned %08x\n", static_cast(iresult)); break; case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_FP_CALL: case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Returned %f\n", dresult); break; default: UNREACHABLE(); break; } } } else if (redirection->type() == ExternalReference::DIRECT_API_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08" PRIx64 " \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 %08" PRIx64 " %08" PRIx64 " \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 %08" PRIx64 " %08" PRIx64 " \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 %08" PRIx64 " %08" PRIx64 " %08" PRIx64 " \n", reinterpret_cast(external), arg0, arg1, arg2); } SimulatorRuntimeProfilingGetterCall target = reinterpret_cast(external); target(arg0, arg1, Redirection::ReverseRedirection(arg2)); } else if (redirection->type() == ExternalReference::BUILTIN_CALL_TRIPLE) { // builtin call returning ObjectTriple. SimulatorRuntimeTripleCall target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { PrintF( "Call to host triple returning runtime function %p " "args %016" PRIx64 ", %016" PRIx64 ", %016" PRIx64 ", %016" PRIx64 ", %016" PRIx64 "\n", static_cast(FUNCTION_ADDR(target)), arg1, arg2, arg3, arg4, arg5); } // arg0 is a hidden argument pointing to the return location, so don't // pass it to the target function. ObjectTriple result = target(arg1, arg2, arg3, arg4, arg5); if (::v8::internal::FLAG_trace_sim) { PrintF("Returned { %p, %p, %p }\n", static_cast(result.x), static_cast(result.y), static_cast(result.z)); } // Return is passed back in address pointed to by hidden first argument. ObjectTriple* sim_result = reinterpret_cast(arg0); *sim_result = result; set_register(v0, arg0); } else { DCHECK(redirection->type() == ExternalReference::BUILTIN_CALL || redirection->type() == ExternalReference::BUILTIN_CALL_PAIR); SimulatorRuntimeCall target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { PrintF( "Call to host function at %p " "args %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " \n", static_cast(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)); ObjectPair result = target(arg0, arg1, arg2, arg3, arg4, arg5); set_register(v0, (int64_t)(result.x)); set_register(v1, (int64_t)(result.y)); } if (::v8::internal::FLAG_trace_sim) { PrintF("Returned %08" PRIx64 " : %08" PRIx64 " \n", get_register(v1), get_register(v0)); } set_register(ra, saved_ra); set_pc(get_register(ra)); } else if (func == BREAK && code <= kMaxStopCode) { if (IsWatchpoint(code)) { PrintWatchpoint(code); } else { IncreaseStopCounter(code); HandleStop(code, instr_.instr()); } } else { // All remaining break_ codes, and all traps are handled here. MipsDebugger dbg(this); dbg.Debug(); } } // Stop helper functions. bool Simulator::IsWatchpoint(uint64_t code) { return (code <= kMaxWatchpointCode); } void Simulator::PrintWatchpoint(uint64_t code) { MipsDebugger dbg(this); ++break_count_; PrintF("\n---- break %" PRId64 " marker: %3d (instr count: %8" PRId64 " ) ----------" "----------------------------------", code, break_count_, icount_); dbg.PrintAllRegs(); // Print registers and continue running. } void Simulator::HandleStop(uint64_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(uint64_t code) { DCHECK(code <= kMaxStopCode); DCHECK(code > kMaxWatchpointCode); return !(watched_stops_[code].count & kStopDisabledBit); } void Simulator::EnableStop(uint64_t code) { if (!IsEnabledStop(code)) { watched_stops_[code].count &= ~kStopDisabledBit; } } void Simulator::DisableStop(uint64_t code) { if (IsEnabledStop(code)) { watched_stops_[code].count |= kStopDisabledBit; } } void Simulator::IncreaseStopCounter(uint64_t code) { DCHECK(code <= kMaxStopCode); if ((watched_stops_[code].count & ~(1 << 31)) == 0x7fffffff) { PrintF("Stop counter for code %" PRId64 " 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(uint64_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 %" PRId64 " - 0x%" PRIx64 " : \t%s, \tcounter = %i, \t%s\n", code, code, state, count, watched_stops_[code].desc); } else { PrintF("stop %" PRId64 " - 0x%" PRIx64 " : \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)); } // Min/Max template functions for Double and Single arguments. template static T FPAbs(T a); template <> double FPAbs(double a) { return fabs(a); } template <> float FPAbs(float a) { return fabsf(a); } template static bool FPUProcessNaNsAndZeros(T a, T b, MaxMinKind kind, T& result) { if (std::isnan(a) && std::isnan(b)) { result = a; } else if (std::isnan(a)) { result = b; } else if (std::isnan(b)) { result = a; } else if (b == a) { // Handle -0.0 == 0.0 case. // std::signbit() returns int 0 or 1 so substracting MaxMinKind::kMax // negates the result. result = std::signbit(b) - static_cast(kind) ? b : a; } else { return false; } return true; } template static T FPUMin(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { return result; } else { return b < a ? b : a; } } template static T FPUMax(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMax, result)) { return result; } else { return b > a ? b : a; } } template static T FPUMinA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) < FPAbs(b)) { result = a; } else if (FPAbs(b) < FPAbs(a)) { result = b; } else { result = a < b ? a : b; } } return result; } template static T FPUMaxA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) > FPAbs(b)) { result = a; } else if (FPAbs(b) > FPAbs(a)) { result = b; } else { result = a > b ? a : b; } } return result; } enum class KeepSign : bool { no = false, yes }; template ::value, int>::type = 0> T FPUCanonalizeNaNArg(T result, T arg, KeepSign keepSign = KeepSign::no) { DCHECK(std::isnan(arg)); T qNaN = std::numeric_limits::quiet_NaN(); if (keepSign == KeepSign::yes) { return std::copysign(qNaN, result); } return qNaN; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return result; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first, Args... args) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return FPUCanonalizeNaNArgs(result, keepSign, args...); } template T FPUCanonalizeOperation(Func f, T first, Args... args) { return FPUCanonalizeOperation(f, KeepSign::no, first, args...); } template T FPUCanonalizeOperation(Func f, KeepSign keepSign, T first, Args... args) { T result = f(first, args...); if (std::isnan(result)) { result = FPUCanonalizeNaNArgs(result, keepSign, first, args...); } return result; } // Handle execution based on instruction types. void Simulator::DecodeTypeRegisterSRsType() { float fs, ft, fd; fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); fd = get_fpu_register_float(fd_reg()); int32_t ft_int = bit_cast(ft); int32_t fd_int = bit_cast(fd); uint32_t cc, fcsr_cc; cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(kArchVariant == kMips64r6); 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(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_S: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), fd + (fs * ft)); break; case MSUBF_S: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), fd - (fs * ft)); break; case MUL_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float fs) { return FPAbs(fs); }, fs)); break; case MOV_S: set_fpu_register_float(fd_reg(), fs); break; case NEG_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float src) { return std::sqrt(src); }, fs)); break; case RSQRT_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation( [](float src) { return 1.0 / std::sqrt(src); }, fs)); break; case RECIP_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float src) { return 1.0 / src; }, fs)); 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 CLASS_S: { // Mips64r6 instruction // Convert float input to uint32_t for easier bit manipulation 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 CVT_L_S: { float rounded; int64_t result; 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); } 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; } 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: { // Mips64r2 instruction. float rounded = trunc(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } 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: { // Mips64r2 instruction. 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); set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case FLOOR_L_S: { // Mips64r2 instruction. float rounded = floor(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } 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 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_invalid_result(fs, rounded); } } break; case CEIL_L_S: { // Mips64r2 instruction. float rounded = ceil(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case MINA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMinA(ft, fs)); break; case MAXA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMaxA(ft, fs)); break; case MIN: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMin(ft, fs)); break; case MAX: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMax(ft, fs)); break; case SEL: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), (ft_int & 0x1) == 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case SELNEZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), (ft_int & 0x1) != 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case MOVZ_C: { DCHECK(kArchVariant == kMips64r2); if (rt() == 0) { set_fpu_register_float(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(kArchVariant == kMips64r2); 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 (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; } default: // TRUNC_W_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::DecodeTypeRegisterDRsType() { double ft, fs, fd; uint32_t cc, fcsr_cc; fs = get_fpu_register_double(fs_reg()); ft = (instr_.FunctionFieldRaw() != MOVF) ? get_fpu_register_double(ft_reg()) : 0.0; fd = get_fpu_register_double(fd_reg()); cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); int64_t ft_int = bit_cast(ft); int64_t fd_int = bit_cast(fd); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(kArchVariant == kMips64r6); 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(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), (ft_int & 0x1) == 0 ? fs : 0.0); break; case SELNEZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), (ft_int & 0x1) != 0 ? fs : 0.0); break; case MOVZ_C: { DCHECK(kArchVariant == kMips64r2); if (rt() == 0) { set_fpu_register_double(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(kArchVariant == kMips64r2); 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 (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 MINA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMinA(ft, fs)); break; case MAXA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMaxA(ft, fs)); break; case MIN: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMin(ft, fs)); break; case MAX: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMax(ft, fs)); break; case ADD_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_D: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), fd + (fs * ft)); break; case MSUBF_D: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), fd - (fs * ft)); break; case MUL_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double fs) { return FPAbs(fs); }, fs)); break; case MOV_D: set_fpu_register_double(fd_reg(), fs); break; case NEG_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double fs) { return std::sqrt(fs); }, fs)); break; case RSQRT_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double fs) { return 1.0 / std::sqrt(fs); }, fs)); break; case RECIP_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double fs) { return 1.0 / fs; }, fs)); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); 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_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_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_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_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: { // Mips64r2: Truncate double to 64-bit long-word. double rounded; int64_t result; 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); } break; } case ROUND_L_D: { // Mips64r2 instruction. 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); set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case TRUNC_L_D: { // Mips64r2 instruction. double rounded = trunc(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case FLOOR_L_D: { // Mips64r2 instruction. double rounded = floor(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case CEIL_L_D: { // Mips64r2 instruction. double rounded = ceil(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case CLASS_D: { // Mips64r6 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()); int64_t alu_out = 0x12345678; switch (instr_.FunctionFieldRaw()) { case CVT_S_W: // Convert word to float (single). alu_out = get_fpu_register_signed_word(fs_reg()); 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::DecodeTypeRegisterLRsType() { double fs = get_fpu_register_double(fs_reg()); double ft = get_fpu_register_double(ft_reg()); int64_t i64; switch (instr_.FunctionFieldRaw()) { case CVT_D_L: // Mips32r2 instruction. i64 = get_fpu_register(fs_reg()); set_fpu_register_double(fd_reg(), static_cast(i64)); break; case CVT_S_L: i64 = get_fpu_register(fs_reg()); set_fpu_register_float(fd_reg(), static_cast(i64)); break; case CMP_AF: 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 (instr_.RsFieldRaw()) { case BC1: // Branch on coprocessor condition. case BC1EQZ: case BC1NEZ: UNREACHABLE(); break; 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(), static_cast(get_fpu_register_word(fs_reg()))); break; case DMFC1: set_register(rt_reg(), get_fpu_register(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); uint32_t reg = static_cast(rt()); if (kArchVariant == kMips64r6) { FCSR_ = reg | kFCSRNaN2008FlagMask; } else { DCHECK(kArchVariant == kMips64r2); 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(), static_cast(rt())); break; case DMTC1: set_fpu_register(fs_reg(), rt()); break; case MTHC1: set_fpu_register_hi_word(fs_reg(), static_cast(rt())); break; case S: DecodeTypeRegisterSRsType(); break; case D: DecodeTypeRegisterDRsType(); break; case W: DecodeTypeRegisterWRsType(); break; case L: DecodeTypeRegisterLRsType(); break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterCOP1X() { switch (instr_.FunctionFieldRaw()) { case MADD_S: { DCHECK(kArchVariant == kMips64r2); float fr, ft, fs; fr = get_fpu_register_float(fr_reg()); fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); set_fpu_register_float(fd_reg(), fs * ft + fr); break; } case MSUB_S: { DCHECK(kArchVariant == kMips64r2); float fr, ft, fs; fr = get_fpu_register_float(fr_reg()); fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); set_fpu_register_float(fd_reg(), fs * ft - fr); break; } case MADD_D: { DCHECK(kArchVariant == kMips64r2); 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; } case MSUB_D: { DCHECK(kArchVariant == kMips64r2); 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 i64hilo; uint64_t u64hilo; int64_t alu_out; bool do_interrupt = false; switch (instr_.FunctionFieldRaw()) { case SELEQZ_S: DCHECK(kArchVariant == kMips64r6); set_register(rd_reg(), rt() == 0 ? rs() : 0); break; case SELNEZ_S: DCHECK(kArchVariant == kMips64r6); set_register(rd_reg(), rt() != 0 ? rs() : 0); break; case JR: { int64_t next_pc = rs(); int64_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: { int64_t next_pc = rs(); int64_t current_pc = get_pc(); int32_t return_addr_reg = rd_reg(); 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: SetResult(rd_reg(), static_cast(rt()) << sa()); break; case DSLL: SetResult(rd_reg(), rt() << sa()); break; case DSLL32: SetResult(rd_reg(), rt() << sa() << 32); 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. // Sign-extend the 32-bit result. alu_out = static_cast(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, size_t 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(base::CustomMatcherHashMap* i_cache, void* start_addr, size_t size) { int64_t start = reinterpret_cast(start_addr); int64_t 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((int64_t)0, start & CachePage::kPageMask); offset = 0; } if (size != 0) { FlushOnePage(i_cache, start, size); } } CachePage* Simulator::GetCachePage(base::CustomMatcherHashMap* i_cache, void* page) { base::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(base::CustomMatcherHashMap* i_cache, intptr_t start, size_t 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(base::CustomMatcherHashMap* i_cache, Instruction* instr) { int64_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 base::CustomMatcherHashMap(&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_size_ = FLAG_sim_stack_size * KB; 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 (kArchVariant == kMips64r6) { FCSR_ = kFCSRNaN2008FlagMask; } else { FCSR_ = 0; } // The sp is initialized to point to the bottom (high address) of the // allocated stack area. To be safe in potential stack underflows we leave // some buffer below. registers_[sp] = reinterpret_cast(stack_) + stack_size_ - 64; // The ra and pc are initialized to a known bad value that will cause an // access violation if the simulator ever tries to execute it. registers_[pc] = bad_ra; registers_[ra] = bad_ra; last_debugger_input_ = 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(int64_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(base::CustomMatcherHashMap* i_cache, Redirection* first) { Redirection::DeleteChain(first); if (i_cache != nullptr) { for (base::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, int64_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[1]; registers_[reg] = registers_[reg] << 32; registers_[reg] += dbl[0]; } void Simulator::set_fpu_register(int fpureg, int64_t value) { 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. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* pword; if (kArchEndian == kLittle) { pword = reinterpret_cast(&FPUregisters_[fpureg]); } else { pword = reinterpret_cast(&FPUregisters_[fpureg]) + 1; } *pword = value; } void Simulator::set_fpu_register_hi_word(int fpureg, int32_t value) { // Set ONLY upper 32-bits, leaving lower bits untouched. DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); int32_t* phiword; if (kArchEndian == kLittle) { phiword = (reinterpret_cast(&FPUregisters_[fpureg])) + 1; } else { phiword = reinterpret_cast(&FPUregisters_[fpureg]); } *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) { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); *bit_cast(&FPUregisters_[fpureg]) = value; } // Get the register from the architecture state. This function does handle // the special case of accessing the PC register. int64_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[sizeof(registers_[0])]; memcpy(buffer, ®isters_[reg], sizeof(registers_[0])); memcpy(&dm_val, buffer, sizeof(registers_[0])); return(dm_val); } int64_t Simulator::get_fpu_register(int fpureg) const { 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 { DCHECK((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *bit_cast(&FPUregisters_[fpureg]); } // Runtime FP routines take up to two double arguments and zero // or one integer arguments. All are constructed here, // from a0-a3 or f12 and f13 (n64), or f14 (O32). void Simulator::GetFpArgs(double* x, double* y, int32_t* z) { if (!IsMipsSoftFloatABI) { const int fparg2 = 13; *x = get_fpu_register_double(12); *y = get_fpu_register_double(fparg2); *z = static_cast(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])]; int64_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; } // 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; } 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_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; } // For cvt instructions only 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 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(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; } } // for cvt instructions only 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 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(int64_t value) { pc_modified_ = true; registers_[pc] = value; } bool Simulator::has_bad_pc() const { return ((registers_[pc] == bad_ra) || (registers_[pc] == end_sim_pc)); } // Raw access to the PC register without the special adjustment when reading. int64_t Simulator::get_pc() const { return registers_[pc]; } // 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. // TODO(plind): refactor this messy debug code when we do unaligned access. void Simulator::DieOrDebug() { if (1) { // Flag for this was removed. MipsDebugger dbg(this); dbg.Debug(); } else { base::OS::Abort(); } } void Simulator::TraceRegWr(int64_t value) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%016" PRIx64 " ", value); } } // TODO(plind): consider making icount_ printing a flag option. void Simulator::TraceMemRd(int64_t addr, int64_t value) { if (::v8::internal::FLAG_trace_sim) { SNPrintF(trace_buf_, "%016" PRIx64 " <-- [%016" PRIx64 " ] (%" PRId64 " )", value, addr, icount_); } } void Simulator::TraceMemWr(int64_t addr, int64_t value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { switch (t) { case BYTE: SNPrintF(trace_buf_, " %02x --> [%016" PRIx64 " ]", static_cast(value), addr); break; case HALF: SNPrintF(trace_buf_, " %04x --> [%016" PRIx64 " ]", static_cast(value), addr); break; case WORD: SNPrintF(trace_buf_, " %08x --> [%016" PRIx64 " ]", static_cast(value), addr); break; case DWORD: SNPrintF(trace_buf_, "%016" PRIx64 " --> [%016" PRIx64 " ] (%" PRId64 " )", value, addr, icount_); break; } } } // TODO(plind): sign-extend and zero-extend not implmented properly // on all the ReadXX functions, I don't think re-interpret cast does it. int32_t Simulator::ReadW(int64_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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & 0x3) == 0 || kArchVariant == kMips64r6) { int32_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } uint32_t Simulator::ReadWU(int64_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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & 0x3) == 0 || kArchVariant == kMips64r6) { uint32_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } void Simulator::WriteW(int64_t addr, int32_t 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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & 0x3) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, WORD); int* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } int64_t Simulator::Read2W(int64_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%08" PRIx64 " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & kPointerAlignmentMask) == 0 || kArchVariant == kMips64r6) { int64_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, *ptr); return *ptr; } PrintF("Unaligned read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } void Simulator::Write2W(int64_t addr, int64_t 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%08" PRIx64 " , pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } if ((addr & kPointerAlignmentMask) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, DWORD); int64_t* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } double Simulator::ReadD(int64_t addr, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || kArchVariant == kMips64r6) { double* ptr = reinterpret_cast(addr); return *ptr; } PrintF("Unaligned (double) read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); base::OS::Abort(); return 0; } void Simulator::WriteD(int64_t addr, double value, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0 || kArchVariant == kMips64r6) { double* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned (double) write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } uint16_t Simulator::ReadHU(int64_t addr, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { uint16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned unsigned halfword read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } int16_t Simulator::ReadH(int64_t addr, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { int16_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } PrintF("Unaligned signed halfword read at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); return 0; } void Simulator::WriteH(int64_t addr, uint16_t value, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, HALF); uint16_t* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned unsigned halfword write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } void Simulator::WriteH(int64_t addr, int16_t value, Instruction* instr) { if ((addr & 1) == 0 || kArchVariant == kMips64r6) { TraceMemWr(addr, value, HALF); int16_t* ptr = reinterpret_cast(addr); *ptr = value; return; } PrintF("Unaligned halfword write at 0x%08" PRIx64 " , pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast(instr)); DieOrDebug(); } uint32_t Simulator::ReadBU(int64_t addr) { uint8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr & 0xff; } int32_t Simulator::ReadB(int64_t addr) { int8_t* ptr = reinterpret_cast(addr); TraceMemRd(addr, static_cast(*ptr)); return *ptr; } void Simulator::WriteB(int64_t addr, uint8_t value) { TraceMemWr(addr, value, BYTE); uint8_t* ptr = reinterpret_cast(addr); *ptr = value; } void Simulator::WriteB(int64_t addr, int8_t value) { TraceMemWr(addr, value, BYTE); int8_t* ptr = reinterpret_cast(addr); *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%08" PRIxPTR " : %s\n", reinterpret_cast(instr), format); UNIMPLEMENTED_MIPS(); } // Calls into the V8 runtime are based on this very simple interface. // Note: To be able to return two values from some calls the code in runtime.cc // uses the ObjectPair which is essentially two 32-bit values stuffed into a // 64-bit value. With the code below we assume that all runtime calls return // 64 bits of result. If they don't, the v1 result register contains a bogus // value, which is fine because it is caller-saved. typedef ObjectPair (*SimulatorRuntimeCall)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4, int64_t arg5); typedef ObjectTriple (*SimulatorRuntimeTripleCall)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4); // 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)(int64_t arg0); typedef void (*SimulatorRuntimeProfilingApiCall)(int64_t arg0, void* arg1); // This signature supports direct call to accessor getter callback. typedef void (*SimulatorRuntimeDirectGetterCall)(int64_t arg0, int64_t arg1); typedef void (*SimulatorRuntimeProfilingGetterCall)( int64_t arg0, int64_t arg1, void* arg2); // Software interrupt instructions are used by the simulator to call into the // C-based V8 runtime. They are also used for debugging with simulator. void Simulator::SoftwareInterrupt() { // There are several instructions that could get us here, // the break_ instruction, or several variants of traps. All // Are "SPECIAL" class opcode, and are distinuished by function. int32_t func = instr_.FunctionFieldRaw(); uint32_t code = (func == BREAK) ? instr_.Bits(25, 6) : -1; // We first check if we met a call_rt_redirected. if (instr_.InstructionBits() == rtCallRedirInstr) { Redirection* redirection = Redirection::FromSwiInstruction(instr_.instr()); int64_t arg0 = get_register(a0); int64_t arg1 = get_register(a1); int64_t arg2 = get_register(a2); int64_t arg3 = get_register(a3); int64_t arg4, arg5; arg4 = get_register(a4); // Abi n64 register a4. arg5 = get_register(a5); // Abi n64 register a5. 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: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(f13); arg2 = get_fpu_register(f14); arg3 = get_fpu_register(f15); break; case ExternalReference::BUILTIN_FP_CALL: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(f13); break; case ExternalReference::BUILTIN_FP_INT_CALL: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(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. int64_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", static_cast(FUNCTION_ADDR(generic_target)), dval0, dval1); break; case ExternalReference::BUILTIN_FP_CALL: PrintF("Call to host function at %p with arg %f", static_cast(FUNCTION_ADDR(generic_target)), dval0); break; case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Call to host function at %p with args %f, %d", static_cast(FUNCTION_ADDR(generic_target)), dval0, ival); break; default: UNREACHABLE(); break; } } switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: { SimulatorRuntimeCompareCall target = reinterpret_cast(external); iresult = target(dval0, dval1); set_register(v0, static_cast(iresult)); // set_register(v1, static_cast(iresult >> 32)); break; } case ExternalReference::BUILTIN_FP_FP_CALL: { SimulatorRuntimeFPFPCall target = reinterpret_cast(external); dresult = target(dval0, dval1); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_CALL: { SimulatorRuntimeFPCall target = reinterpret_cast(external); dresult = target(dval0); SetFpResult(dresult); break; } case ExternalReference::BUILTIN_FP_INT_CALL: { SimulatorRuntimeFPIntCall target = reinterpret_cast(external); dresult = target(dval0, ival); SetFpResult(dresult); break; } default: UNREACHABLE(); break; } if (::v8::internal::FLAG_trace_sim) { switch (redirection->type()) { case ExternalReference::BUILTIN_COMPARE_CALL: PrintF("Returned %08x\n", static_cast(iresult)); break; case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_FP_CALL: case ExternalReference::BUILTIN_FP_INT_CALL: PrintF("Returned %f\n", dresult); break; default: UNREACHABLE(); break; } } } else if (redirection->type() == ExternalReference::DIRECT_API_CALL) { if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08" PRIx64 " \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 %08" PRIx64 " %08" PRIx64 " \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 %08" PRIx64 " %08" PRIx64 " \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 %08" PRIx64 " %08" PRIx64 " %08" PRIx64 " \n", reinterpret_cast(external), arg0, arg1, arg2); } SimulatorRuntimeProfilingGetterCall target = reinterpret_cast(external); target(arg0, arg1, Redirection::ReverseRedirection(arg2)); } else if (redirection->type() == ExternalReference::BUILTIN_CALL_TRIPLE) { // builtin call returning ObjectTriple. SimulatorRuntimeTripleCall target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { PrintF( "Call to host triple returning runtime function %p " "args %016" PRIx64 ", %016" PRIx64 ", %016" PRIx64 ", %016" PRIx64 ", %016" PRIx64 "\n", static_cast(FUNCTION_ADDR(target)), arg1, arg2, arg3, arg4, arg5); } // arg0 is a hidden argument pointing to the return location, so don't // pass it to the target function. ObjectTriple result = target(arg1, arg2, arg3, arg4, arg5); if (::v8::internal::FLAG_trace_sim) { PrintF("Returned { %p, %p, %p }\n", static_cast(result.x), static_cast(result.y), static_cast(result.z)); } // Return is passed back in address pointed to by hidden first argument. ObjectTriple* sim_result = reinterpret_cast(arg0); *sim_result = result; set_register(v0, arg0); } else { DCHECK(redirection->type() == ExternalReference::BUILTIN_CALL || redirection->type() == ExternalReference::BUILTIN_CALL_PAIR); SimulatorRuntimeCall target = reinterpret_cast(external); if (::v8::internal::FLAG_trace_sim) { PrintF( "Call to host function at %p " "args %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " , %08" PRIx64 " \n", static_cast(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)); ObjectPair result = target(arg0, arg1, arg2, arg3, arg4, arg5); set_register(v0, (int64_t)(result.x)); set_register(v1, (int64_t)(result.y)); } if (::v8::internal::FLAG_trace_sim) { PrintF("Returned %08" PRIx64 " : %08" PRIx64 " \n", get_register(v1), get_register(v0)); } set_register(ra, saved_ra); set_pc(get_register(ra)); } else if (func == BREAK && code <= kMaxStopCode) { if (IsWatchpoint(code)) { PrintWatchpoint(code); } else { IncreaseStopCounter(code); HandleStop(code, instr_.instr()); } } else { // All remaining break_ codes, and all traps are handled here. MipsDebugger dbg(this); dbg.Debug(); } } // Stop helper functions. bool Simulator::IsWatchpoint(uint64_t code) { return (code <= kMaxWatchpointCode); } void Simulator::PrintWatchpoint(uint64_t code) { MipsDebugger dbg(this); ++break_count_; PrintF("\n---- break %" PRId64 " marker: %3d (instr count: %8" PRId64 " ) ----------" "----------------------------------", code, break_count_, icount_); dbg.PrintAllRegs(); // Print registers and continue running. } void Simulator::HandleStop(uint64_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(uint64_t code) { DCHECK(code <= kMaxStopCode); DCHECK(code > kMaxWatchpointCode); return !(watched_stops_[code].count & kStopDisabledBit); } void Simulator::EnableStop(uint64_t code) { if (!IsEnabledStop(code)) { watched_stops_[code].count &= ~kStopDisabledBit; } } void Simulator::DisableStop(uint64_t code) { if (IsEnabledStop(code)) { watched_stops_[code].count |= kStopDisabledBit; } } void Simulator::IncreaseStopCounter(uint64_t code) { DCHECK(code <= kMaxStopCode); if ((watched_stops_[code].count & ~(1 << 31)) == 0x7fffffff) { PrintF("Stop counter for code %" PRId64 " 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(uint64_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 %" PRId64 " - 0x%" PRIx64 " : \t%s, \tcounter = %i, \t%s\n", code, code, state, count, watched_stops_[code].desc); } else { PrintF("stop %" PRId64 " - 0x%" PRIx64 " : \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)); } // Min/Max template functions for Double and Single arguments. template static T FPAbs(T a); template <> double FPAbs(double a) { return fabs(a); } template <> float FPAbs(float a) { return fabsf(a); } template static bool FPUProcessNaNsAndZeros(T a, T b, MaxMinKind kind, T& result) { if (std::isnan(a) && std::isnan(b)) { result = a; } else if (std::isnan(a)) { result = b; } else if (std::isnan(b)) { result = a; } else if (b == a) { // Handle -0.0 == 0.0 case. // std::signbit() returns int 0 or 1 so substracting MaxMinKind::kMax // negates the result. result = std::signbit(b) - static_cast(kind) ? b : a; } else { return false; } return true; } template static T FPUMin(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { return result; } else { return b < a ? b : a; } } template static T FPUMax(T a, T b) { T result; if (FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMax, result)) { return result; } else { return b > a ? b : a; } } template static T FPUMinA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) < FPAbs(b)) { result = a; } else if (FPAbs(b) < FPAbs(a)) { result = b; } else { result = a < b ? a : b; } } return result; } template static T FPUMaxA(T a, T b) { T result; if (!FPUProcessNaNsAndZeros(a, b, MaxMinKind::kMin, result)) { if (FPAbs(a) > FPAbs(b)) { result = a; } else if (FPAbs(b) > FPAbs(a)) { result = b; } else { result = a > b ? a : b; } } return result; } enum class KeepSign : bool { no = false, yes }; template ::value, int>::type = 0> T FPUCanonalizeNaNArg(T result, T arg, KeepSign keepSign = KeepSign::no) { DCHECK(std::isnan(arg)); T qNaN = std::numeric_limits::quiet_NaN(); if (keepSign == KeepSign::yes) { return std::copysign(qNaN, result); } return qNaN; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return result; } template T FPUCanonalizeNaNArgs(T result, KeepSign keepSign, T first, Args... args) { if (std::isnan(first)) { return FPUCanonalizeNaNArg(result, first, keepSign); } return FPUCanonalizeNaNArgs(result, keepSign, args...); } template T FPUCanonalizeOperation(Func f, T first, Args... args) { return FPUCanonalizeOperation(f, KeepSign::no, first, args...); } template T FPUCanonalizeOperation(Func f, KeepSign keepSign, T first, Args... args) { T result = f(first, args...); if (std::isnan(result)) { result = FPUCanonalizeNaNArgs(result, keepSign, first, args...); } return result; } // Handle execution based on instruction types. void Simulator::DecodeTypeRegisterSRsType() { float fs, ft, fd; fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); fd = get_fpu_register_float(fd_reg()); int32_t ft_int = bit_cast(ft); int32_t fd_int = bit_cast(fd); uint32_t cc, fcsr_cc; cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(kArchVariant == kMips64r6); 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(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_S: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), fd + (fs * ft)); break; case MSUBF_S: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), fd - (fs * ft)); break; case MUL_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float lhs, float rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float fs) { return FPAbs(fs); }, fs)); break; case MOV_S: set_fpu_register_float(fd_reg(), fs); break; case NEG_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float src) { return std::sqrt(src); }, fs)); break; case RSQRT_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation( [](float src) { return 1.0 / std::sqrt(src); }, fs)); break; case RECIP_S: set_fpu_register_float( fd_reg(), FPUCanonalizeOperation([](float src) { return 1.0 / src; }, fs)); 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 CLASS_S: { // Mips64r6 instruction // Convert float input to uint32_t for easier bit manipulation 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 CVT_L_S: { float rounded; int64_t result; 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); } 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; } 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: { // Mips64r2 instruction. float rounded = trunc(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } 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: { // Mips64r2 instruction. 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); set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case FLOOR_L_S: { // Mips64r2 instruction. float rounded = floor(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } 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 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_invalid_result(fs, rounded); } } break; case CEIL_L_S: { // Mips64r2 instruction. float rounded = ceil(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case MINA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMinA(ft, fs)); break; case MAXA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMaxA(ft, fs)); break; case MIN: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMin(ft, fs)); break; case MAX: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), FPUMax(ft, fs)); break; case SEL: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), (ft_int & 0x1) == 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case SELNEZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_float(fd_reg(), (ft_int & 0x1) != 0 ? get_fpu_register_float(fs_reg()) : 0.0); break; case MOVZ_C: { DCHECK(kArchVariant == kMips64r2); if (rt() == 0) { set_fpu_register_float(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(kArchVariant == kMips64r2); 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 (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; } default: // TRUNC_W_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::DecodeTypeRegisterDRsType() { double ft, fs, fd; uint32_t cc, fcsr_cc; fs = get_fpu_register_double(fs_reg()); ft = (instr_.FunctionFieldRaw() != MOVF) ? get_fpu_register_double(ft_reg()) : 0.0; fd = get_fpu_register_double(fd_reg()); cc = instr_.FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); int64_t ft_int = bit_cast(ft); int64_t fd_int = bit_cast(fd); switch (instr_.FunctionFieldRaw()) { case RINT: { DCHECK(kArchVariant == kMips64r6); 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(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), (fd_int & 0x1) == 0 ? fs : ft); break; case SELEQZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), (ft_int & 0x1) == 0 ? fs : 0.0); break; case SELNEZ_C: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), (ft_int & 0x1) != 0 ? fs : 0.0); break; case MOVZ_C: { DCHECK(kArchVariant == kMips64r2); if (rt() == 0) { set_fpu_register_double(fd_reg(), fs); } break; } case MOVN_C: { DCHECK(kArchVariant == kMips64r2); 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 (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 MINA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMinA(ft, fs)); break; case MAXA: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMaxA(ft, fs)); break; case MIN: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMin(ft, fs)); break; case MAX: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), FPUMax(ft, fs)); break; case ADD_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs + rhs; }, fs, ft)); break; case SUB_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs - rhs; }, fs, ft)); break; case MADDF_D: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), fd + (fs * ft)); break; case MSUBF_D: DCHECK(kArchVariant == kMips64r6); set_fpu_register_double(fd_reg(), fd - (fs * ft)); break; case MUL_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs * rhs; }, fs, ft)); break; case DIV_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double lhs, double rhs) { return lhs / rhs; }, fs, ft)); break; case ABS_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double fs) { return FPAbs(fs); }, fs)); break; case MOV_D: set_fpu_register_double(fd_reg(), fs); break; case NEG_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double src) { return -src; }, KeepSign::yes, fs)); break; case SQRT_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double fs) { return std::sqrt(fs); }, fs)); break; case RSQRT_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation( [](double fs) { return 1.0 / std::sqrt(fs); }, fs)); break; case RECIP_D: set_fpu_register_double( fd_reg(), FPUCanonalizeOperation([](double fs) { return 1.0 / fs; }, fs)); break; case C_UN_D: set_fcsr_bit(fcsr_cc, std::isnan(fs) || std::isnan(ft)); 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_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_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_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_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: { // Mips64r2: Truncate double to 64-bit long-word. double rounded; int64_t result; 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); } break; } case ROUND_L_D: { // Mips64r2 instruction. 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); set_fpu_register(fd_reg(), i64); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case TRUNC_L_D: { // Mips64r2 instruction. double rounded = trunc(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case FLOOR_L_D: { // Mips64r2 instruction. double rounded = floor(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case CEIL_L_D: { // Mips64r2 instruction. double rounded = ceil(fs); int64_t result = static_cast(rounded); set_fpu_register(fd_reg(), result); if (set_fcsr_round64_error(fs, rounded)) { set_fpu_register_invalid_result64(fs, rounded); } break; } case CLASS_D: { // Mips64r6 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()); int64_t alu_out = 0x12345678; switch (instr_.FunctionFieldRaw()) { case CVT_S_W: // Convert word to float (single). alu_out = get_fpu_register_signed_word(fs_reg()); 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::DecodeTypeRegisterLRsType() { double fs = get_fpu_register_double(fs_reg()); double ft = get_fpu_register_double(ft_reg()); int64_t i64; switch (instr_.FunctionFieldRaw()) { case CVT_D_L: // Mips32r2 instruction. i64 = get_fpu_register(fs_reg()); set_fpu_register_double(fd_reg(), static_cast(i64)); break; case CVT_S_L: i64 = get_fpu_register(fs_reg()); set_fpu_register_float(fd_reg(), static_cast(i64)); break; case CMP_AF: 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 (instr_.RsFieldRaw()) { case BC1: // Branch on coprocessor condition. case BC1EQZ: case BC1NEZ: UNREACHABLE(); break; 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(), static_cast(get_fpu_register_word(fs_reg()))); break; case DMFC1: set_register(rt_reg(), get_fpu_register(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); uint32_t reg = static_cast(rt()); if (kArchVariant == kMips64r6) { FCSR_ = reg | kFCSRNaN2008FlagMask; } else { DCHECK(kArchVariant == kMips64r2); 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(), static_cast(rt())); break; case DMTC1: set_fpu_register(fs_reg(), rt()); break; case MTHC1: set_fpu_register_hi_word(fs_reg(), static_cast(rt())); break; case S: DecodeTypeRegisterSRsType(); break; case D: DecodeTypeRegisterDRsType(); break; case W: DecodeTypeRegisterWRsType(); break; case L: DecodeTypeRegisterLRsType(); break; default: UNREACHABLE(); } } void Simulator::DecodeTypeRegisterCOP1X() { switch (instr_.FunctionFieldRaw()) { case MADD_S: { DCHECK(kArchVariant == kMips64r2); float fr, ft, fs; fr = get_fpu_register_float(fr_reg()); fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); set_fpu_register_float(fd_reg(), fs * ft + fr); break; } case MSUB_S: { DCHECK(kArchVariant == kMips64r2); float fr, ft, fs; fr = get_fpu_register_float(fr_reg()); fs = get_fpu_register_float(fs_reg()); ft = get_fpu_register_float(ft_reg()); set_fpu_register_float(fd_reg(), fs * ft - fr); break; } case MADD_D: { DCHECK(kArchVariant == kMips64r2); 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; } case MSUB_D: { DCHECK(kArchVariant == kMips64r2); 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 i64hilo; uint64_t u64hilo; int64_t alu_out; bool do_interrupt = false; switch (instr_.FunctionFieldRaw()) { case SELEQZ_S: DCHECK(kArchVariant == kMips64r6); set_register(rd_reg(), rt() == 0 ? rs() : 0); break; case SELNEZ_S: DCHECK(kArchVariant == kMips64r6); set_register(rd_reg(), rt() != 0 ? rs() : 0); break; case JR: { int64_t next_pc = rs(); int64_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: { int64_t next_pc = rs(); int64_t current_pc = get_pc(); int32_t return_addr_reg = rd_reg(); 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: SetResult(rd_reg(), static_cast(rt()) << sa()); break; case DSLL: SetResult(rd_reg(), rt() << sa()); break; case DSLL32: SetResult(rd_reg(), rt() << sa() << 32); 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. // Sign-extend the 32-bit result. alu_out = static_cast(static_cast