| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #include <setjmp.h> // NOLINT |
| #include <stdlib.h> |
| |
| #include "vm/globals.h" |
| #if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) |
| |
| // Only build the simulator if not compiling for real RISCV hardware. |
| #if defined(USING_SIMULATOR) |
| |
| #include "vm/simulator.h" |
| |
| #include "vm/compiler/assembler/disassembler.h" |
| #include "vm/constants.h" |
| #include "vm/image_snapshot.h" |
| #include "vm/native_arguments.h" |
| #include "vm/os_thread.h" |
| #include "vm/stack_frame.h" |
| |
| namespace dart { |
| |
| DEFINE_FLAG(uint64_t, |
| trace_sim_after, |
| ULLONG_MAX, |
| "Trace simulator execution after instruction count reached."); |
| DEFINE_FLAG(uint64_t, |
| stop_sim_at, |
| ULLONG_MAX, |
| "Instruction address or instruction count to stop simulator at."); |
| |
| // SimulatorSetjmpBuffer are linked together, and the last created one |
| // is referenced by the Simulator. When an exception is thrown, the exception |
| // runtime looks at where to jump and finds the corresponding |
| // SimulatorSetjmpBuffer based on the stack pointer of the exception handler. |
| // The runtime then does a Longjmp on that buffer to return to the simulator. |
| class SimulatorSetjmpBuffer { |
| public: |
| void Longjmp() { |
| // "This" is now the last setjmp buffer. |
| simulator_->set_last_setjmp_buffer(this); |
| longjmp(buffer_, 1); |
| } |
| |
| explicit SimulatorSetjmpBuffer(Simulator* sim) { |
| simulator_ = sim; |
| link_ = sim->last_setjmp_buffer(); |
| sim->set_last_setjmp_buffer(this); |
| sp_ = static_cast<uword>(sim->get_register(SP)); |
| } |
| |
| ~SimulatorSetjmpBuffer() { |
| ASSERT(simulator_->last_setjmp_buffer() == this); |
| simulator_->set_last_setjmp_buffer(link_); |
| } |
| |
| SimulatorSetjmpBuffer* link() { return link_; } |
| |
| uword sp() { return sp_; } |
| |
| private: |
| uword sp_; |
| Simulator* simulator_; |
| SimulatorSetjmpBuffer* link_; |
| jmp_buf buffer_; |
| |
| friend class Simulator; |
| }; |
| |
| // 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 svc (supervisor call) instruction that is handled by |
| // the simulator. We write the original destination of the jump just at a known |
| // offset from the svc instruction so the simulator knows what to call. |
| class Redirection { |
| public: |
| uword address_of_ecall_instruction() { |
| return reinterpret_cast<uword>(&ecall_instruction_); |
| } |
| |
| uword external_function() const { return external_function_; } |
| |
| Simulator::CallKind call_kind() const { return call_kind_; } |
| |
| int argument_count() const { return argument_count_; } |
| |
| static Redirection* Get(uword external_function, |
| Simulator::CallKind call_kind, |
| int argument_count) { |
| MutexLocker ml(mutex_); |
| |
| Redirection* old_head = list_.load(std::memory_order_relaxed); |
| for (Redirection* current = old_head; current != nullptr; |
| current = current->next_) { |
| if (current->external_function_ == external_function) return current; |
| } |
| |
| Redirection* redirection = |
| new Redirection(external_function, call_kind, argument_count); |
| redirection->next_ = old_head; |
| |
| // Use a memory fence to ensure all pending writes are written at the time |
| // of updating the list head, so the profiling thread always has a valid |
| // list to look at. |
| list_.store(redirection, std::memory_order_release); |
| |
| return redirection; |
| } |
| |
| static Redirection* FromECallInstruction(uintx_t ecall_instruction) { |
| char* addr_of_ecall = reinterpret_cast<char*>(ecall_instruction); |
| char* addr_of_redirection = |
| addr_of_ecall - OFFSET_OF(Redirection, ecall_instruction_); |
| return reinterpret_cast<Redirection*>(addr_of_redirection); |
| } |
| |
| // Please note that this function is called by the signal handler of the |
| // profiling thread. It can therefore run at any point in time and is not |
| // allowed to hold any locks - which is precisely the reason why the list is |
| // prepend-only and a memory fence is used when writing the list head [list_]! |
| static uword FunctionForRedirect(uword address_of_ecall) { |
| for (Redirection* current = list_.load(std::memory_order_acquire); |
| current != nullptr; current = current->next_) { |
| if (current->address_of_ecall_instruction() == address_of_ecall) { |
| return current->external_function_; |
| } |
| } |
| return 0; |
| } |
| |
| private: |
| Redirection(uword external_function, |
| Simulator::CallKind call_kind, |
| int argument_count) |
| : external_function_(external_function), |
| call_kind_(call_kind), |
| argument_count_(argument_count), |
| ecall_instruction_(Instr::kSimulatorRedirectInstruction), |
| next_(NULL) {} |
| |
| uword external_function_; |
| Simulator::CallKind call_kind_; |
| int argument_count_; |
| uint32_t ecall_instruction_; |
| Redirection* next_; |
| static std::atomic<Redirection*> list_; |
| static Mutex* mutex_; |
| }; |
| |
| std::atomic<Redirection*> Redirection::list_ = {nullptr}; |
| Mutex* Redirection::mutex_ = new Mutex(); |
| |
| uword Simulator::RedirectExternalReference(uword function, |
| CallKind call_kind, |
| int argument_count) { |
| Redirection* redirection = |
| Redirection::Get(function, call_kind, argument_count); |
| return redirection->address_of_ecall_instruction(); |
| } |
| |
| uword Simulator::FunctionForRedirect(uword redirect) { |
| return Redirection::FunctionForRedirect(redirect); |
| } |
| |
| // Get the active Simulator for the current isolate. |
| Simulator* Simulator::Current() { |
| Isolate* isolate = Isolate::Current(); |
| Simulator* simulator = isolate->simulator(); |
| if (simulator == NULL) { |
| NoSafepointScope no_safepoint; |
| simulator = new Simulator(); |
| isolate->set_simulator(simulator); |
| } |
| return simulator; |
| } |
| |
| void Simulator::Init() {} |
| |
| Simulator::Simulator() |
| : pc_(0), |
| instret_(0), |
| reserved_address_(0), |
| reserved_value_(0), |
| fcsr_(0), |
| random_(), |
| last_setjmp_buffer_(NULL) { |
| // Setup simulator support first. Some of this information is needed to |
| // setup the architecture state. |
| // We allocate the stack here, the size is computed as the sum of |
| // the size specified by the user and the buffer space needed for |
| // handling stack overflow exceptions. To be safe in potential |
| // stack underflows we also add some underflow buffer space. |
| stack_ = |
| new char[(OSThread::GetSpecifiedStackSize() + |
| OSThread::kStackSizeBufferMax + kSimulatorStackUnderflowSize)]; |
| // Low address. |
| stack_limit_ = reinterpret_cast<uword>(stack_); |
| // Limit for StackOverflowError. |
| overflow_stack_limit_ = stack_limit_ + OSThread::kStackSizeBufferMax; |
| // High address. |
| stack_base_ = overflow_stack_limit_ + OSThread::GetSpecifiedStackSize(); |
| |
| // Setup architecture state. |
| xregs_[0] = 0; |
| for (intptr_t i = 1; i < kNumberOfCpuRegisters; i++) { |
| xregs_[i] = random_.NextUInt64(); |
| } |
| for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) { |
| // TODO(riscv): This generates values that are very wide when printed, |
| // making it hard to read register state. Maybe generate random values in |
| // the unit interval instead? |
| // fregs_[i] = bit_cast<double>(random_.NextUInt64()); |
| fregs_[i] = bit_cast<double>(kNaNBox); |
| } |
| |
| // The sp is initialized to point to the bottom (high address) of the |
| // allocated stack area. |
| set_xreg(SP, stack_base()); |
| // The lr and pc are initialized to a known bad value that will cause an |
| // access violation if the simulator ever tries to execute it. |
| set_xreg(RA, kBadLR); |
| pc_ = kBadLR; |
| } |
| |
| Simulator::~Simulator() { |
| delete[] stack_; |
| Isolate* isolate = Isolate::Current(); |
| if (isolate != NULL) { |
| isolate->set_simulator(NULL); |
| } |
| } |
| |
| void Simulator::PrepareCall(PreservedRegisters* preserved) { |
| #if defined(DEBUG) |
| for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) { |
| preserved->xregs[i] = xregs_[i]; |
| if ((kAbiVolatileCpuRegs & (1 << i)) != 0) { |
| xregs_[i] = random_.NextUInt64(); |
| } |
| } |
| for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) { |
| preserved->fregs[i] = fregs_[i]; |
| if ((kAbiVolatileFpuRegs & (1 << i)) != 0) { |
| // TODO(riscv): This generates values that are very wide when printed, |
| // making it hard to read register state. Maybe generate random values in |
| // the unit interval instead? |
| // fregs_[i] = bit_cast<double>(random_.NextUInt64()); |
| fregs_[i] = bit_cast<double>(kNaNBox); |
| } |
| } |
| #endif |
| } |
| |
| void Simulator::ClobberVolatileRegisters() { |
| #if defined(DEBUG) |
| reserved_address_ = reserved_value_ = 0; // Clear atomic reservation. |
| for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) { |
| if ((kAbiVolatileCpuRegs & (1 << i)) != 0) { |
| xregs_[i] = random_.NextUInt64(); |
| } |
| } |
| for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) { |
| if ((kAbiVolatileFpuRegs & (1 << i)) != 0) { |
| // TODO(riscv): This generates values that are very wide when printed, |
| // making it hard to read register state. Maybe generate random values in |
| // the unit interval instead? |
| // fregs_[i] = bit_cast<double>(random_.NextUInt64()); |
| fregs_[i] = bit_cast<double>(kNaNBox); |
| } |
| } |
| #endif |
| } |
| |
| void Simulator::SavePreservedRegisters(PreservedRegisters* preserved) { |
| #if defined(DEBUG) |
| for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) { |
| preserved->xregs[i] = xregs_[i]; |
| } |
| for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) { |
| preserved->fregs[i] = fregs_[i]; |
| } |
| #endif |
| } |
| |
| void Simulator::CheckPreservedRegisters(PreservedRegisters* preserved) { |
| #if defined(DEBUG) |
| if (preserved->xregs[SP] != xregs_[SP]) { |
| PrintRegisters(); |
| PrintStack(); |
| FATAL("Stack unbalanced"); |
| } |
| const intptr_t kPreservedAtCall = |
| kAbiPreservedCpuRegs | (1 << TP) | (1 << GP) | (1 << SP) | (1 << FP); |
| for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) { |
| if ((kPreservedAtCall & (1 << i)) != 0) { |
| if (preserved->xregs[i] != xregs_[i]) { |
| FATAL("%s was not preserved\n", cpu_reg_names[i]); |
| } |
| } |
| } |
| for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) { |
| if ((kAbiVolatileFpuRegs & (1 << i)) == 0) { |
| if (bit_cast<uint64_t>(preserved->fregs[i]) != |
| bit_cast<uint64_t>(fregs_[i])) { |
| FATAL("%s was not preserved\n", fpu_reg_names[i]); |
| } |
| } |
| } |
| #endif |
| } |
| |
| void Simulator::RunCall(intx_t entry, PreservedRegisters* preserved) { |
| pc_ = entry; |
| set_xreg(RA, kEndSimulatingPC); |
| Execute(); |
| CheckPreservedRegisters(preserved); |
| } |
| |
| int64_t Simulator::Call(intx_t entry, |
| intx_t parameter0, |
| intx_t parameter1, |
| intx_t parameter2, |
| intx_t parameter3, |
| bool fp_return, |
| bool fp_args) { |
| // Save the SP register before the call so we can restore it. |
| const intptr_t sp_before_call = get_xreg(SP); |
| |
| // Setup parameters. |
| if (fp_args) { |
| set_fregd(FA0, parameter0); |
| set_fregd(FA1, parameter1); |
| set_fregd(FA2, parameter2); |
| set_fregd(FA3, parameter3); |
| } else { |
| set_xreg(A0, parameter0); |
| set_xreg(A1, parameter1); |
| set_xreg(A2, parameter2); |
| set_xreg(A3, parameter3); |
| } |
| |
| // Make sure the activation frames are properly aligned. |
| intptr_t stack_pointer = sp_before_call; |
| if (OS::ActivationFrameAlignment() > 1) { |
| stack_pointer = |
| Utils::RoundDown(stack_pointer, OS::ActivationFrameAlignment()); |
| } |
| set_xreg(SP, stack_pointer); |
| |
| // Prepare to execute the code at entry. |
| pc_ = entry; |
| // Put down marker for end of simulation. The simulator will stop simulation |
| // when the PC reaches this value. By saving the "end simulation" value into |
| // the LR the simulation stops when returning to this call point. |
| set_xreg(RA, kEndSimulatingPC); |
| |
| // Remember the values of callee-saved registers, and set them up with a |
| // known value so that we are able to check that they are preserved |
| // properly across Dart execution. |
| PreservedRegisters preserved; |
| SavePreservedRegisters(&preserved); |
| |
| // Start the simulation. |
| Execute(); |
| |
| // Check that the callee-saved registers have been preserved, |
| // and restore them with the original value. |
| CheckPreservedRegisters(&preserved); |
| |
| // Restore the SP register and return R0. |
| set_xreg(SP, sp_before_call); |
| int64_t return_value; |
| if (fp_return) { |
| return_value = get_fregd(FA0); |
| } else { |
| return_value = get_xreg(A0); |
| } |
| return return_value; |
| } |
| |
| void Simulator::Execute() { |
| while (pc_ != kEndSimulatingPC) { |
| uint16_t parcel = *reinterpret_cast<uint16_t*>(pc_); |
| if (IsCInstruction(parcel)) { |
| CInstr instr(parcel); |
| if (IsTracingExecution()) { |
| Disassembler::Disassemble(pc_, pc_ + instr.length()); |
| } |
| Interpret(instr); |
| } else { |
| Instr instr(*reinterpret_cast<uint32_t*>(pc_)); |
| if (IsTracingExecution()) { |
| Disassembler::Disassemble(pc_, pc_ + instr.length()); |
| } |
| Interpret(instr); |
| } |
| instret_++; |
| } |
| } |
| |
| bool Simulator::IsTracingExecution() const { |
| return instret_ > FLAG_trace_sim_after; |
| } |
| |
| void Simulator::JumpToFrame(uword pc, uword sp, uword fp, Thread* thread) { |
| // Walk over all setjmp buffers (simulated --> C++ transitions) |
| // and try to find the setjmp associated with the simulated stack pointer. |
| SimulatorSetjmpBuffer* buf = last_setjmp_buffer(); |
| while (buf->link() != NULL && buf->link()->sp() <= sp) { |
| buf = buf->link(); |
| } |
| ASSERT(buf != NULL); |
| |
| // The C++ caller has not cleaned up the stack memory of C++ frames. |
| // Prepare for unwinding frames by destroying all the stack resources |
| // in the previous C++ frames. |
| StackResource::Unwind(thread); |
| |
| // Keep the following code in sync with `StubCode::JumpToFrameStub()`. |
| |
| // Unwind the C++ stack and continue simulation in the target frame. |
| pc_ = pc; |
| set_xreg(SP, static_cast<uintx_t>(sp)); |
| set_xreg(FP, static_cast<uintx_t>(fp)); |
| set_xreg(THR, reinterpret_cast<uintx_t>(thread)); |
| // Set the tag. |
| thread->set_vm_tag(VMTag::kDartTagId); |
| // Clear top exit frame. |
| thread->set_top_exit_frame_info(0); |
| // Restore pool pointer. |
| uintx_t code = |
| *reinterpret_cast<uintx_t*>(fp + kPcMarkerSlotFromFp * kWordSize); |
| uintx_t pp = FLAG_precompiled_mode |
| ? static_cast<uintx_t>(thread->global_object_pool()) |
| : *reinterpret_cast<uintx_t*>( |
| code + Code::object_pool_offset() - kHeapObjectTag); |
| pp -= kHeapObjectTag; // In the PP register, the pool pointer is untagged. |
| set_xreg(CODE_REG, code); |
| set_xreg(PP, pp); |
| set_xreg(WRITE_BARRIER_MASK, thread->write_barrier_mask()); |
| set_xreg(NULL_REG, static_cast<uintx_t>(Object::null())); |
| if (FLAG_precompiled_mode) { |
| set_xreg(DISPATCH_TABLE_REG, |
| reinterpret_cast<uintx_t>(thread->dispatch_table_array())); |
| } |
| |
| buf->Longjmp(); |
| } |
| |
| void Simulator::PrintRegisters() { |
| ASSERT(static_cast<intptr_t>(kNumberOfCpuRegisters) == |
| static_cast<intptr_t>(kNumberOfFpuRegisters)); |
| for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) { |
| #if XLEN == 32 |
| OS::Print("%4s: %8x %11d", cpu_reg_names[i], xregs_[i], xregs_[i]); |
| #elif XLEN == 64 |
| OS::Print("%4s: %16" Px64 " %20" Pd64, cpu_reg_names[i], xregs_[i], |
| xregs_[i]); |
| #endif |
| OS::Print(" %4s: %lf\n", fpu_reg_names[i], fregs_[i]); |
| } |
| #if XLEN == 32 |
| OS::Print(" pc: %8x\n", pc_); |
| #elif XLEN == 64 |
| OS::Print(" pc: %16" Px64 "\n", pc_); |
| #endif |
| } |
| |
| void Simulator::PrintStack() { |
| StackFrameIterator frames(get_register(FP), get_register(SP), get_pc(), |
| ValidationPolicy::kDontValidateFrames, |
| Thread::Current(), |
| StackFrameIterator::kNoCrossThreadIteration); |
| StackFrame* frame = frames.NextFrame(); |
| while (frame != nullptr) { |
| OS::PrintErr("%s\n", frame->ToCString()); |
| frame = frames.NextFrame(); |
| } |
| } |
| |
| void Simulator::Interpret(Instr instr) { |
| switch (instr.opcode()) { |
| case LUI: |
| InterpretLUI(instr); |
| break; |
| case AUIPC: |
| InterpretAUIPC(instr); |
| break; |
| case JAL: |
| InterpretJAL(instr); |
| break; |
| case JALR: |
| InterpretJALR(instr); |
| break; |
| case BRANCH: |
| InterpretBRANCH(instr); |
| break; |
| case LOAD: |
| InterpretLOAD(instr); |
| break; |
| case STORE: |
| InterpretSTORE(instr); |
| break; |
| case OPIMM: |
| InterpretOPIMM(instr); |
| break; |
| case OPIMM32: |
| InterpretOPIMM32(instr); |
| break; |
| case OP: |
| InterpretOP(instr); |
| break; |
| case OP32: |
| InterpretOP32(instr); |
| break; |
| case MISCMEM: |
| InterpretMISCMEM(instr); |
| break; |
| case SYSTEM: |
| InterpretSYSTEM(instr); |
| break; |
| case AMO: |
| InterpretAMO(instr); |
| break; |
| case LOADFP: |
| InterpretLOADFP(instr); |
| break; |
| case STOREFP: |
| InterpretSTOREFP(instr); |
| break; |
| case FMADD: |
| InterpretFMADD(instr); |
| break; |
| case FMSUB: |
| InterpretFMSUB(instr); |
| break; |
| case FNMADD: |
| InterpretFNMADD(instr); |
| break; |
| case FNMSUB: |
| InterpretFNMSUB(instr); |
| break; |
| case OPFP: |
| InterpretOPFP(instr); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| } |
| |
| void Simulator::Interpret(CInstr instr) { |
| switch (instr.opcode()) { |
| case C_LWSP: { |
| uintx_t addr = get_xreg(SP) + instr.spload4_imm(); |
| set_xreg(instr.rd(), MemoryRead<int32_t>(addr, SP)); |
| break; |
| } |
| #if XLEN == 32 |
| case C_FLWSP: { |
| uintx_t addr = get_xreg(SP) + instr.spload4_imm(); |
| set_fregs(instr.frd(), MemoryRead<float>(addr, SP)); |
| break; |
| } |
| #else |
| case C_LDSP: { |
| uintx_t addr = get_xreg(SP) + instr.spload8_imm(); |
| set_xreg(instr.rd(), MemoryRead<int64_t>(addr, SP)); |
| break; |
| } |
| #endif |
| case C_FLDSP: { |
| uintx_t addr = get_xreg(SP) + instr.spload8_imm(); |
| set_fregd(instr.frd(), MemoryRead<double>(addr, SP)); |
| break; |
| } |
| case C_SWSP: { |
| uintx_t addr = get_xreg(SP) + instr.spstore4_imm(); |
| MemoryWrite<uint32_t>(addr, get_xreg(instr.rs2()), SP); |
| break; |
| } |
| #if XLEN == 32 |
| case C_FSWSP: { |
| uintx_t addr = get_xreg(SP) + instr.spstore4_imm(); |
| MemoryWrite<float>(addr, get_fregs(instr.frs2()), SP); |
| break; |
| } |
| #else |
| case C_SDSP: { |
| uintx_t addr = get_xreg(SP) + instr.spstore8_imm(); |
| MemoryWrite<uint64_t>(addr, get_xreg(instr.rs2()), SP); |
| break; |
| } |
| #endif |
| case C_FSDSP: { |
| uintx_t addr = get_xreg(SP) + instr.spstore8_imm(); |
| MemoryWrite<double>(addr, get_fregd(instr.frs2()), SP); |
| break; |
| } |
| case C_LW: { |
| uintx_t addr = get_xreg(instr.rs1p()) + instr.mem4_imm(); |
| set_xreg(instr.rdp(), MemoryRead<int32_t>(addr, instr.rs1p())); |
| break; |
| } |
| #if XLEN == 32 |
| case C_FLW: { |
| uintx_t addr = get_xreg(instr.rs1p()) + instr.mem4_imm(); |
| set_fregs(instr.frdp(), MemoryRead<float>(addr, instr.rs1p())); |
| break; |
| } |
| #else |
| case C_LD: { |
| uintx_t addr = get_xreg(instr.rs1p()) + instr.mem8_imm(); |
| set_xreg(instr.rdp(), MemoryRead<int64_t>(addr, instr.rs1p())); |
| break; |
| } |
| #endif |
| case C_FLD: { |
| uintx_t addr = get_xreg(instr.rs1p()) + instr.mem8_imm(); |
| set_fregd(instr.frdp(), MemoryRead<double>(addr, instr.rs1p())); |
| break; |
| } |
| case C_SW: { |
| uintx_t addr = get_xreg(instr.rs1p()) + instr.mem4_imm(); |
| MemoryWrite<uint32_t>(addr, get_xreg(instr.rs2p()), instr.rs1p()); |
| break; |
| } |
| #if XLEN == 32 |
| case C_FSW: { |
| uintx_t addr = get_xreg(instr.rs1p()) + instr.mem4_imm(); |
| MemoryWrite<float>(addr, get_fregs(instr.frs2p()), instr.rs1p()); |
| break; |
| } |
| #else |
| case C_SD: { |
| uintx_t addr = get_xreg(instr.rs1p()) + instr.mem8_imm(); |
| MemoryWrite<uint64_t>(addr, get_xreg(instr.rs2p()), instr.rs1p()); |
| break; |
| } |
| #endif |
| case C_FSD: { |
| uintx_t addr = get_xreg(instr.rs1p()) + instr.mem8_imm(); |
| MemoryWrite<double>(addr, get_fregd(instr.frs2p()), instr.rs1p()); |
| break; |
| } |
| case C_J: { |
| pc_ += sign_extend((int32_t)instr.j_imm()); |
| return; |
| } |
| #if XLEN == 32 |
| case C_JAL: { |
| set_xreg(RA, pc_ + instr.length()); |
| pc_ += sign_extend((int32_t)instr.j_imm()); |
| return; |
| } |
| #endif |
| case C_JR: { |
| if (instr.encoding() & (C_JALR ^ C_JR)) { |
| if ((instr.rs1() == ZR) && (instr.rs2() == ZR)) { |
| InterpretEBREAK(instr); |
| } else if (instr.rs2() == ZR) { |
| // JALR |
| uintx_t target = get_xreg(instr.rs1()); |
| set_xreg(RA, pc_ + instr.length()); |
| pc_ = target; |
| return; |
| } else { |
| // ADD |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) + get_xreg(instr.rs2())); |
| } |
| } else { |
| if ((instr.rd() != ZR) && (instr.rs2() != ZR)) { |
| // MV |
| set_xreg(instr.rd(), get_xreg(instr.rs2())); |
| } else if (instr.rs2() != ZR) { |
| IllegalInstruction(instr); |
| } else { |
| // JR |
| pc_ = get_xreg(instr.rs1()); |
| return; |
| } |
| } |
| break; |
| } |
| case C_BEQZ: |
| if (get_xreg(instr.rs1p()) == 0) { |
| pc_ += instr.b_imm(); |
| return; |
| } |
| break; |
| case C_BNEZ: |
| if (get_xreg(instr.rs1p()) != 0) { |
| pc_ += instr.b_imm(); |
| return; |
| } |
| break; |
| case C_LI: |
| if (instr.rd() == ZR) { |
| IllegalInstruction(instr); |
| } else { |
| set_xreg(instr.rd(), sign_extend(instr.i_imm())); |
| } |
| break; |
| case C_LUI: |
| if (instr.rd() == SP) { |
| if (instr.i16_imm() == 0) { |
| IllegalInstruction(instr); |
| } else { |
| set_xreg(instr.rd(), |
| get_xreg(instr.rs1()) + sign_extend(instr.i16_imm())); |
| } |
| } else if ((instr.rd() == ZR) || (instr.u_imm() == 0)) { |
| IllegalInstruction(instr); |
| } else { |
| set_xreg(instr.rd(), sign_extend(instr.u_imm())); |
| } |
| break; |
| case C_ADDI: |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) + instr.i_imm()); |
| break; |
| #if XLEN >= 64 |
| case C_ADDIW: { |
| uint32_t a = get_xreg(instr.rs1()); |
| uint32_t b = instr.i_imm(); |
| set_xreg(instr.rd(), sign_extend(a + b)); |
| break; |
| } |
| #endif // XLEN >= 64 |
| case C_ADDI4SPN: |
| if (instr.i4spn_imm() == 0) { |
| IllegalInstruction(instr); |
| } else { |
| set_xreg(instr.rdp(), get_xreg(SP) + instr.i4spn_imm()); |
| } |
| break; |
| case C_SLLI: |
| if (instr.i_imm() == 0) { |
| IllegalInstruction(instr); |
| } else { |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) |
| << (instr.i_imm() & (XLEN - 1))); |
| } |
| break; |
| case C_MISCALU: |
| // Note MISCALU has a different notion of rsd′ than other instructions, |
| // so use rs1′ instead. |
| switch (instr.encoding() & C_MISCALU_MASK) { |
| case C_SRLI: |
| if (instr.i_imm() == 0) { |
| IllegalInstruction(instr); |
| } else { |
| set_xreg(instr.rs1p(), |
| get_xreg(instr.rs1p()) >> (instr.i_imm() & (XLEN - 1))); |
| } |
| break; |
| case C_SRAI: |
| if (instr.i_imm() == 0) { |
| IllegalInstruction(instr); |
| } else { |
| set_xreg(instr.rs1p(), |
| static_cast<intx_t>(get_xreg(instr.rs1p())) >> |
| (instr.i_imm() & (XLEN - 1))); |
| } |
| break; |
| case C_ANDI: |
| set_xreg(instr.rs1p(), get_xreg(instr.rs1p()) & instr.i_imm()); |
| break; |
| case C_RR: |
| switch (instr.encoding() & C_RR_MASK) { |
| case C_AND: |
| set_xreg(instr.rs1p(), |
| get_xreg(instr.rs1p()) & get_xreg(instr.rs2p())); |
| break; |
| case C_OR: |
| set_xreg(instr.rs1p(), |
| get_xreg(instr.rs1p()) | get_xreg(instr.rs2p())); |
| break; |
| case C_XOR: |
| set_xreg(instr.rs1p(), |
| get_xreg(instr.rs1p()) ^ get_xreg(instr.rs2p())); |
| break; |
| case C_SUB: |
| set_xreg(instr.rs1p(), |
| get_xreg(instr.rs1p()) - get_xreg(instr.rs2p())); |
| break; |
| case C_ADDW: { |
| uint32_t a = get_xreg(instr.rs1p()); |
| uint32_t b = get_xreg(instr.rs2p()); |
| set_xreg(instr.rs1p(), sign_extend(a + b)); |
| break; |
| } |
| case C_SUBW: { |
| uint32_t a = get_xreg(instr.rs1p()); |
| uint32_t b = get_xreg(instr.rs2p()); |
| set_xreg(instr.rs1p(), sign_extend(a - b)); |
| break; |
| } |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretLUI(Instr instr) { |
| set_xreg(instr.rd(), sign_extend(instr.utype_imm())); |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretAUIPC(Instr instr) { |
| set_xreg(instr.rd(), pc_ + sign_extend(instr.utype_imm())); |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretJAL(Instr instr) { |
| set_xreg(instr.rd(), pc_ + instr.length()); |
| pc_ += sign_extend(instr.jtype_imm()); |
| } |
| |
| void Simulator::InterpretJALR(Instr instr) { |
| uintx_t base = get_xreg(instr.rs1()); |
| uintx_t offset = static_cast<uintx_t>(instr.itype_imm()); |
| set_xreg(instr.rd(), pc_ + instr.length()); |
| pc_ = base + offset; |
| } |
| |
| void Simulator::InterpretBRANCH(Instr instr) { |
| switch (instr.funct3()) { |
| case BEQ: |
| if (get_xreg(instr.rs1()) == get_xreg(instr.rs2())) { |
| pc_ += instr.btype_imm(); |
| } else { |
| pc_ += instr.length(); |
| } |
| break; |
| case BNE: |
| if (get_xreg(instr.rs1()) != get_xreg(instr.rs2())) { |
| pc_ += instr.btype_imm(); |
| } else { |
| pc_ += instr.length(); |
| } |
| break; |
| case BLT: |
| if (static_cast<intx_t>(get_xreg(instr.rs1())) < |
| static_cast<intx_t>(get_xreg(instr.rs2()))) { |
| pc_ += instr.btype_imm(); |
| } else { |
| pc_ += instr.length(); |
| } |
| break; |
| case BGE: |
| if (static_cast<intx_t>(get_xreg(instr.rs1())) >= |
| static_cast<intx_t>(get_xreg(instr.rs2()))) { |
| pc_ += instr.btype_imm(); |
| } else { |
| pc_ += instr.length(); |
| } |
| break; |
| case BLTU: |
| if (static_cast<uintx_t>(get_xreg(instr.rs1())) < |
| static_cast<uintx_t>(get_xreg(instr.rs2()))) { |
| pc_ += instr.btype_imm(); |
| } else { |
| pc_ += instr.length(); |
| } |
| break; |
| case BGEU: |
| if (static_cast<uintx_t>(get_xreg(instr.rs1())) >= |
| static_cast<uintx_t>(get_xreg(instr.rs2()))) { |
| pc_ += instr.btype_imm(); |
| } else { |
| pc_ += instr.length(); |
| } |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| } |
| |
| void Simulator::InterpretLOAD(Instr instr) { |
| uintx_t addr = get_xreg(instr.rs1()) + instr.itype_imm(); |
| switch (instr.funct3()) { |
| case LB: |
| set_xreg(instr.rd(), MemoryRead<int8_t>(addr, instr.rs1())); |
| break; |
| case LH: |
| set_xreg(instr.rd(), MemoryRead<int16_t>(addr, instr.rs1())); |
| break; |
| case LW: |
| set_xreg(instr.rd(), MemoryRead<int32_t>(addr, instr.rs1())); |
| break; |
| case LBU: |
| set_xreg(instr.rd(), MemoryRead<uint8_t>(addr, instr.rs1())); |
| break; |
| case LHU: |
| set_xreg(instr.rd(), MemoryRead<uint16_t>(addr, instr.rs1())); |
| break; |
| #if XLEN >= 64 |
| case LWU: |
| set_xreg(instr.rd(), MemoryRead<uint32_t>(addr, instr.rs1())); |
| break; |
| case LD: |
| set_xreg(instr.rd(), MemoryRead<int64_t>(addr, instr.rs1())); |
| break; |
| #endif // XLEN >= 64 |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretLOADFP(Instr instr) { |
| uintx_t addr = get_xreg(instr.rs1()) + instr.itype_imm(); |
| switch (instr.funct3()) { |
| case S: |
| set_fregs(instr.frd(), MemoryRead<float>(addr, instr.rs1())); |
| break; |
| case D: |
| set_fregd(instr.frd(), MemoryRead<double>(addr, instr.rs1())); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretSTORE(Instr instr) { |
| uintx_t addr = get_xreg(instr.rs1()) + instr.stype_imm(); |
| switch (instr.funct3()) { |
| case SB: |
| MemoryWrite<uint8_t>(addr, get_xreg(instr.rs2()), instr.rs1()); |
| break; |
| case SH: |
| MemoryWrite<uint16_t>(addr, get_xreg(instr.rs2()), instr.rs1()); |
| break; |
| case SW: |
| MemoryWrite<uint32_t>(addr, get_xreg(instr.rs2()), instr.rs1()); |
| break; |
| #if XLEN >= 64 |
| case SD: |
| MemoryWrite<uint64_t>(addr, get_xreg(instr.rs2()), instr.rs1()); |
| break; |
| #endif // XLEN >= 64 |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretSTOREFP(Instr instr) { |
| uintx_t addr = get_xreg(instr.rs1()) + instr.stype_imm(); |
| switch (instr.funct3()) { |
| case S: |
| MemoryWrite<float>(addr, get_fregs(instr.frs2()), instr.rs1()); |
| break; |
| case D: |
| MemoryWrite<double>(addr, get_fregd(instr.frs2()), instr.rs1()); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretOPIMM(Instr instr) { |
| switch (instr.funct3()) { |
| case ADDI: |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) + instr.itype_imm()); |
| break; |
| case SLTI: { |
| set_xreg(instr.rd(), static_cast<intx_t>(get_xreg(instr.rs1())) < |
| static_cast<intx_t>(instr.itype_imm()) |
| ? 1 |
| : 0); |
| break; |
| } |
| case SLTIU: |
| set_xreg(instr.rd(), static_cast<uintx_t>(get_xreg(instr.rs1())) < |
| static_cast<uintx_t>(instr.itype_imm()) |
| ? 1 |
| : 0); |
| break; |
| case XORI: |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) ^ instr.itype_imm()); |
| break; |
| case ORI: |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) | instr.itype_imm()); |
| break; |
| case ANDI: |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) & instr.itype_imm()); |
| |
| break; |
| case SLLI: |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) << instr.shamt()); |
| break; |
| case SRI: |
| if ((instr.funct7() & 0b1111110) == SRA) { |
| set_xreg(instr.rd(), |
| static_cast<intx_t>(get_xreg(instr.rs1())) >> instr.shamt()); |
| } else { |
| set_xreg(instr.rd(), |
| static_cast<uintx_t>(get_xreg(instr.rs1())) >> instr.shamt()); |
| } |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretOPIMM32(Instr instr) { |
| switch (instr.funct3()) { |
| case ADDI: { |
| uint32_t a = get_xreg(instr.rs1()); |
| uint32_t b = instr.itype_imm(); |
| set_xreg(instr.rd(), sign_extend(a + b)); |
| break; |
| } |
| case SLLI: { |
| uint32_t a = get_xreg(instr.rs1()); |
| uint32_t b = instr.shamt(); |
| set_xreg(instr.rd(), sign_extend(a << b)); |
| break; |
| } |
| case SRI: |
| if (instr.funct7() == SRA) { |
| int32_t a = get_xreg(instr.rs1()); |
| int32_t b = instr.shamt(); |
| set_xreg(instr.rd(), sign_extend(a >> b)); |
| } else { |
| uint32_t a = get_xreg(instr.rs1()); |
| uint32_t b = instr.shamt(); |
| set_xreg(instr.rd(), sign_extend(a >> b)); |
| } |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretOP(Instr instr) { |
| switch (instr.funct7()) { |
| case 0: |
| InterpretOP_0(instr); |
| break; |
| case SUB: |
| InterpretOP_SUB(instr); |
| break; |
| case MULDIV: |
| InterpretOP_MULDIV(instr); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| } |
| |
| void Simulator::InterpretOP_0(Instr instr) { |
| switch (instr.funct3()) { |
| case ADD: |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) + get_xreg(instr.rs2())); |
| break; |
| case SLL: { |
| uintx_t shamt = get_xreg(instr.rs2()) & (XLEN - 1); |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) << shamt); |
| break; |
| } |
| case SLT: |
| set_xreg(instr.rd(), static_cast<intx_t>(get_xreg(instr.rs1())) < |
| static_cast<intx_t>(get_xreg(instr.rs2())) |
| ? 1 |
| : 0); |
| break; |
| case SLTU: |
| set_xreg(instr.rd(), static_cast<uintx_t>(get_xreg(instr.rs1())) < |
| static_cast<uintx_t>(get_xreg(instr.rs2())) |
| ? 1 |
| : 0); |
| break; |
| case XOR: |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) ^ get_xreg(instr.rs2())); |
| break; |
| case SR: { |
| uintx_t shamt = get_xreg(instr.rs2()) & (XLEN - 1); |
| set_xreg(instr.rd(), |
| static_cast<uintx_t>(get_xreg(instr.rs1())) >> shamt); |
| break; |
| } |
| case OR: |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) | get_xreg(instr.rs2())); |
| break; |
| case AND: |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) & get_xreg(instr.rs2())); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| static intx_t mul(intx_t a, intx_t b) { |
| return a * b; |
| } |
| |
| static intx_t mulh(intx_t a, intx_t b) { |
| const uintx_t kLoMask = (static_cast<uintx_t>(1) << (XLEN / 2)) - 1; |
| const uintx_t kHiShift = XLEN / 2; |
| |
| uintx_t a_lo = a & kLoMask; |
| intx_t a_hi = a >> kHiShift; |
| uintx_t b_lo = b & kLoMask; |
| intx_t b_hi = b >> kHiShift; |
| |
| uintx_t x = a_lo * b_lo; |
| intx_t y = a_hi * b_lo; |
| intx_t z = a_lo * b_hi; |
| intx_t w = a_hi * b_hi; |
| |
| intx_t r0 = (x >> kHiShift) + y; |
| intx_t r1 = (r0 & kLoMask) + z; |
| return w + (r0 >> kHiShift) + (r1 >> kHiShift); |
| } |
| |
| static uintx_t mulhu(uintx_t a, uintx_t b) { |
| const uintx_t kLoMask = (static_cast<uintx_t>(1) << (XLEN / 2)) - 1; |
| const uintx_t kHiShift = XLEN / 2; |
| |
| uintx_t a_lo = a & kLoMask; |
| uintx_t a_hi = a >> kHiShift; |
| uintx_t b_lo = b & kLoMask; |
| uintx_t b_hi = b >> kHiShift; |
| |
| uintx_t x = a_lo * b_lo; |
| uintx_t y = a_hi * b_lo; |
| uintx_t z = a_lo * b_hi; |
| uintx_t w = a_hi * b_hi; |
| |
| uintx_t r0 = (x >> kHiShift) + y; |
| uintx_t r1 = (r0 & kLoMask) + z; |
| return w + (r0 >> kHiShift) + (r1 >> kHiShift); |
| } |
| |
| static uintx_t mulhsu(intx_t a, uintx_t b) { |
| const uintx_t kLoMask = (static_cast<uintx_t>(1) << (XLEN / 2)) - 1; |
| const uintx_t kHiShift = XLEN / 2; |
| |
| uintx_t a_lo = a & kLoMask; |
| intx_t a_hi = a >> kHiShift; |
| uintx_t b_lo = b & kLoMask; |
| uintx_t b_hi = b >> kHiShift; |
| |
| uintx_t x = a_lo * b_lo; |
| intx_t y = a_hi * b_lo; |
| uintx_t z = a_lo * b_hi; |
| intx_t w = a_hi * b_hi; |
| |
| intx_t r0 = (x >> kHiShift) + y; |
| uintx_t r1 = (r0 & kLoMask) + z; |
| return w + (r0 >> kHiShift) + (r1 >> kHiShift); |
| } |
| |
| static intx_t div(intx_t a, intx_t b) { |
| if (b == 0) { |
| return -1; |
| } else if (b == -1 && a == kMinIntX) { |
| return kMinIntX; |
| } else { |
| return a / b; |
| } |
| } |
| |
| static uintx_t divu(uintx_t a, uintx_t b) { |
| if (b == 0) { |
| return kMaxUIntX; |
| } else { |
| return a / b; |
| } |
| } |
| |
| static intx_t rem(intx_t a, intx_t b) { |
| if (b == 0) { |
| return a; |
| } else if (b == -1 && a == kMinIntX) { |
| return 0; |
| } else { |
| return a % b; |
| } |
| } |
| |
| static uintx_t remu(uintx_t a, uintx_t b) { |
| if (b == 0) { |
| return a; |
| } else { |
| return a % b; |
| } |
| } |
| |
| #if XLEN >= 64 |
| static int32_t mulw(int32_t a, int32_t b) { |
| return a * b; |
| } |
| |
| static int32_t divw(int32_t a, int32_t b) { |
| if (b == 0) { |
| return -1; |
| } else if (b == -1 && a == kMinInt32) { |
| return kMinInt32; |
| } else { |
| return a / b; |
| } |
| } |
| |
| static uint32_t divuw(uint32_t a, uint32_t b) { |
| if (b == 0) { |
| return kMaxUint32; |
| } else { |
| return a / b; |
| } |
| } |
| |
| static int32_t remw(int32_t a, int32_t b) { |
| if (b == 0) { |
| return a; |
| } else if (b == -1 && a == kMinInt32) { |
| return 0; |
| } else { |
| return a % b; |
| } |
| } |
| |
| static uint32_t remuw(uint32_t a, uint32_t b) { |
| if (b == 0) { |
| return a; |
| } else { |
| return a % b; |
| } |
| } |
| #endif // XLEN >= 64 |
| |
| void Simulator::InterpretOP_MULDIV(Instr instr) { |
| switch (instr.funct3()) { |
| case MUL: |
| set_xreg(instr.rd(), mul(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); |
| break; |
| case MULH: |
| set_xreg(instr.rd(), mulh(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); |
| break; |
| case MULHSU: |
| set_xreg(instr.rd(), |
| mulhsu(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); |
| break; |
| case MULHU: |
| set_xreg(instr.rd(), mulhu(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); |
| break; |
| case DIV: |
| set_xreg(instr.rd(), div(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); |
| break; |
| case DIVU: |
| set_xreg(instr.rd(), divu(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); |
| break; |
| case REM: |
| set_xreg(instr.rd(), rem(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); |
| break; |
| case REMU: |
| set_xreg(instr.rd(), remu(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretOP_SUB(Instr instr) { |
| switch (instr.funct3()) { |
| case ADD: |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) - get_xreg(instr.rs2())); |
| break; |
| case SR: { |
| uintx_t shamt = get_xreg(instr.rs2()) & (XLEN - 1); |
| set_xreg(instr.rd(), static_cast<intx_t>(get_xreg(instr.rs1())) >> shamt); |
| break; |
| } |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretOP32(Instr instr) { |
| switch (instr.funct7()) { |
| #if XLEN >= 64 |
| case 0: |
| InterpretOP32_0(instr); |
| break; |
| case SUB: |
| InterpretOP32_SUB(instr); |
| break; |
| case MULDIV: |
| InterpretOP32_MULDIV(instr); |
| break; |
| #endif // XLEN >= 64 |
| default: |
| IllegalInstruction(instr); |
| } |
| } |
| |
| void Simulator::InterpretOP32_0(Instr instr) { |
| switch (instr.funct3()) { |
| #if XLEN >= 64 |
| case ADD: { |
| uint32_t a = get_xreg(instr.rs1()); |
| uint32_t b = get_xreg(instr.rs2()); |
| set_xreg(instr.rd(), sign_extend(a + b)); |
| break; |
| } |
| case SLL: { |
| uint32_t a = get_xreg(instr.rs1()); |
| uint32_t b = get_xreg(instr.rs2()) & (32 - 1); |
| set_xreg(instr.rd(), sign_extend(a << b)); |
| break; |
| } |
| case SR: { |
| uint32_t b = get_xreg(instr.rs2()) & (32 - 1); |
| uint32_t a = get_xreg(instr.rs1()); |
| set_xreg(instr.rd(), sign_extend(a >> b)); |
| break; |
| } |
| #endif // XLEN >= 64 |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretOP32_SUB(Instr instr) { |
| switch (instr.funct3()) { |
| #if XLEN >= 64 |
| case ADD: { |
| uint32_t a = get_xreg(instr.rs1()); |
| uint32_t b = get_xreg(instr.rs2()); |
| set_xreg(instr.rd(), sign_extend(a - b)); |
| break; |
| } |
| case SR: { |
| uint32_t b = get_xreg(instr.rs2()) & (32 - 1); |
| int32_t a = get_xreg(instr.rs1()); |
| set_xreg(instr.rd(), sign_extend(a >> b)); |
| break; |
| } |
| #endif // XLEN >= 64 |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretOP32_MULDIV(Instr instr) { |
| switch (instr.funct3()) { |
| #if XLEN >= 64 |
| case MULW: |
| set_xreg(instr.rd(), |
| sign_extend(mulw(get_xreg(instr.rs1()), get_xreg(instr.rs2())))); |
| break; |
| case DIVW: |
| set_xreg(instr.rd(), |
| sign_extend(divw(get_xreg(instr.rs1()), get_xreg(instr.rs2())))); |
| break; |
| case DIVUW: |
| set_xreg(instr.rd(), sign_extend(divuw(get_xreg(instr.rs1()), |
| get_xreg(instr.rs2())))); |
| break; |
| case REMW: |
| set_xreg(instr.rd(), |
| sign_extend(remw(get_xreg(instr.rs1()), get_xreg(instr.rs2())))); |
| break; |
| case REMUW: |
| set_xreg(instr.rd(), sign_extend(remuw(get_xreg(instr.rs1()), |
| get_xreg(instr.rs2())))); |
| break; |
| #endif // XLEN >= 64 |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretMISCMEM(Instr instr) { |
| switch (instr.funct3()) { |
| case FENCE: |
| std::atomic_thread_fence(std::memory_order_acq_rel); |
| break; |
| case FENCEI: |
| // Nothing to do: simulated instructions are data on the host. |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretSYSTEM(Instr instr) { |
| switch (instr.funct3()) { |
| case 0: |
| switch (instr.funct12()) { |
| case ECALL: |
| InterpretECALL(instr); |
| return; |
| case EBREAK: |
| InterpretEBREAK(instr); |
| return; |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| case CSRRW: { |
| if (instr.rd() == ZR) { |
| // No read effect. |
| CSRWrite(instr.csr(), get_xreg(instr.rs1())); |
| } else { |
| intx_t result = CSRRead(instr.csr()); |
| CSRWrite(instr.csr(), get_xreg(instr.rs1())); |
| set_xreg(instr.rd(), result); |
| } |
| break; |
| } |
| case CSRRS: { |
| intx_t result = CSRRead(instr.csr()); |
| if (instr.rs1() == ZR) { |
| // No write effect. |
| } else { |
| CSRSet(instr.csr(), get_xreg(instr.rs1())); |
| } |
| set_xreg(instr.rd(), result); |
| break; |
| } |
| case CSRRC: { |
| intx_t result = CSRRead(instr.csr()); |
| if (instr.rs1() == ZR) { |
| // No write effect. |
| } else { |
| CSRClear(instr.csr(), get_xreg(instr.rs1())); |
| } |
| set_xreg(instr.rd(), result); |
| break; |
| } |
| case CSRRWI: { |
| if (instr.rd() == ZR) { |
| // No read effect. |
| CSRWrite(instr.csr(), instr.zimm()); |
| } else { |
| intx_t result = CSRRead(instr.csr()); |
| CSRWrite(instr.csr(), instr.zimm()); |
| set_xreg(instr.rd(), result); |
| } |
| break; |
| } |
| case CSRRSI: { |
| intx_t result = CSRRead(instr.csr()); |
| if (instr.zimm() == 0) { |
| // No write effect. |
| } else { |
| CSRSet(instr.csr(), instr.zimm()); |
| } |
| set_xreg(instr.rd(), result); |
| break; |
| } |
| case CSRRCI: { |
| intx_t result = CSRRead(instr.csr()); |
| if (instr.zimm() == 0) { |
| // No write effect. |
| } else { |
| CSRClear(instr.csr(), instr.zimm()); |
| } |
| set_xreg(instr.rd(), result); |
| break; |
| } |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| // Calls into the Dart runtime are based on this interface. |
| typedef void (*SimulatorRuntimeCall)(NativeArguments arguments); |
| |
| // Calls to leaf Dart runtime functions are based on this interface. |
| typedef intx_t (*SimulatorLeafRuntimeCall)(intx_t r0, |
| intx_t r1, |
| intx_t r2, |
| intx_t r3, |
| intx_t r4, |
| intx_t r5, |
| intx_t r6, |
| intx_t r7); |
| |
| // [target] has several different signatures that differ from |
| // SimulatorLeafRuntimeCall. We can call them all from here only because in |
| // X64's calling conventions a function can be called with extra arguments |
| // and the callee will see the first arguments and won't unbalance the stack. |
| NO_SANITIZE_UNDEFINED("function") |
| static intx_t InvokeLeafRuntime(SimulatorLeafRuntimeCall target, |
| intx_t r0, |
| intx_t r1, |
| intx_t r2, |
| intx_t r3, |
| intx_t r4, |
| intx_t r5, |
| intx_t r6, |
| intx_t r7) { |
| return target(r0, r1, r2, r3, r4, r5, r6, r7); |
| } |
| |
| // Calls to leaf float Dart runtime functions are based on this interface. |
| typedef double (*SimulatorLeafFloatRuntimeCall)(double d0, |
| double d1, |
| double d2, |
| double d3, |
| double d4, |
| double d5, |
| double d6, |
| double d7); |
| |
| // [target] has several different signatures that differ from |
| // SimulatorFloatLeafRuntimeCall. We can call them all from here only because in |
| // X64's calling conventions a function can be called with extra arguments |
| // and the callee will see the first arguments and won't unbalance the stack. |
| NO_SANITIZE_UNDEFINED("function") |
| static double InvokeFloatLeafRuntime(SimulatorLeafFloatRuntimeCall target, |
| double d0, |
| double d1, |
| double d2, |
| double d3, |
| double d4, |
| double d5, |
| double d6, |
| double d7) { |
| return target(d0, d1, d2, d3, d4, d5, d6, d7); |
| } |
| |
| // Calls to native Dart functions are based on this interface. |
| typedef void (*SimulatorNativeCallWrapper)(Dart_NativeArguments arguments, |
| Dart_NativeFunction target); |
| |
| void Simulator::InterpretECALL(Instr instr) { |
| if (instr.rs1() != ZR) { |
| // Fake instruction generated by Assembler::SimulatorPrintObject. |
| if (true || IsTracingExecution()) { |
| uintx_t raw = get_xreg(instr.rs1()); |
| Object& obj = Object::Handle(static_cast<ObjectPtr>(raw)); |
| THR_Print("%" Px ": %s = %s\n", pc_, cpu_reg_names[instr.rs1()], |
| obj.ToCString()); |
| FLAG_trace_sim_after = 1; |
| } |
| pc_ += instr.length(); |
| return; |
| } |
| |
| // The C ABI stack alignment is 16 for both 32 and 64 bit. |
| if (!Utils::IsAligned(get_xreg(SP), 16)) { |
| PrintRegisters(); |
| PrintStack(); |
| FATAL("Stack misaligned at call to C function"); |
| } |
| |
| SimulatorSetjmpBuffer buffer(this); |
| if (!setjmp(buffer.buffer_)) { |
| uintx_t saved_ra = get_xreg(RA); |
| Redirection* redirection = Redirection::FromECallInstruction(pc_); |
| uword external = redirection->external_function(); |
| if (IsTracingExecution()) { |
| THR_Print("Call to host function at 0x%" Pd "\n", external); |
| } |
| |
| if (redirection->call_kind() == kRuntimeCall) { |
| NativeArguments* arguments = |
| reinterpret_cast<NativeArguments*>(get_register(A0)); |
| SimulatorRuntimeCall target = |
| reinterpret_cast<SimulatorRuntimeCall>(external); |
| target(*arguments); |
| ClobberVolatileRegisters(); |
| } else if (redirection->call_kind() == kLeafRuntimeCall) { |
| ASSERT((0 <= redirection->argument_count()) && |
| (redirection->argument_count() <= 8)); |
| SimulatorLeafRuntimeCall target = |
| reinterpret_cast<SimulatorLeafRuntimeCall>(external); |
| const intx_t r0 = get_register(A0); |
| const intx_t r1 = get_register(A1); |
| const intx_t r2 = get_register(A2); |
| const intx_t r3 = get_register(A3); |
| const intx_t r4 = get_register(A4); |
| const intx_t r5 = get_register(A5); |
| const intx_t r6 = get_register(A6); |
| const intx_t r7 = get_register(A7); |
| const intx_t res = |
| InvokeLeafRuntime(target, r0, r1, r2, r3, r4, r5, r6, r7); |
| ClobberVolatileRegisters(); |
| set_xreg(A0, res); // Set returned result from function. |
| } else if (redirection->call_kind() == kLeafFloatRuntimeCall) { |
| ASSERT((0 <= redirection->argument_count()) && |
| (redirection->argument_count() <= 8)); |
| SimulatorLeafFloatRuntimeCall target = |
| reinterpret_cast<SimulatorLeafFloatRuntimeCall>(external); |
| const double d0 = get_fregd(FA0); |
| const double d1 = get_fregd(FA1); |
| const double d2 = get_fregd(FA2); |
| const double d3 = get_fregd(FA3); |
| const double d4 = get_fregd(FA4); |
| const double d5 = get_fregd(FA5); |
| const double d6 = get_fregd(FA6); |
| const double d7 = get_fregd(FA7); |
| const double res = |
| InvokeFloatLeafRuntime(target, d0, d1, d2, d3, d4, d5, d6, d7); |
| ClobberVolatileRegisters(); |
| set_fregd(FA0, res); |
| } else if (redirection->call_kind() == kNativeCallWrapper) { |
| SimulatorNativeCallWrapper wrapper = |
| reinterpret_cast<SimulatorNativeCallWrapper>(external); |
| Dart_NativeArguments arguments = |
| reinterpret_cast<Dart_NativeArguments>(get_register(A0)); |
| Dart_NativeFunction target = |
| reinterpret_cast<Dart_NativeFunction>(get_register(A1)); |
| wrapper(arguments, target); |
| ClobberVolatileRegisters(); |
| } else { |
| UNREACHABLE(); |
| } |
| |
| // Return. |
| pc_ = saved_ra; |
| } else { |
| // Coming via long jump from a throw. Continue to exception handler. |
| } |
| } |
| |
| void Simulator::InterpretAMO(Instr instr) { |
| switch (instr.funct3()) { |
| case WIDTH32: |
| InterpretAMO32(instr); |
| break; |
| case WIDTH64: |
| InterpretAMO64(instr); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| } |
| |
| // Note: This implementation does not give full LR/SC semantics because it |
| // suffers from the ABA problem. |
| |
| template <typename type> |
| void Simulator::InterpretLR(Instr instr) { |
| uintx_t addr = get_xreg(instr.rs1()); |
| if ((addr & (sizeof(type) - 1)) != 0) { |
| FATAL("Misaligned atomic memory operation"); |
| } |
| std::atomic<type>* atomic = reinterpret_cast<std::atomic<type>*>(addr); |
| reserved_address_ = addr; |
| reserved_value_ = atomic->load(instr.memory_order()); |
| set_xreg(instr.rd(), reserved_value_); |
| } |
| |
| template <typename type> |
| void Simulator::InterpretSC(Instr instr) { |
| uintx_t addr = get_xreg(instr.rs1()); |
| if ((addr & (sizeof(type) - 1)) != 0) { |
| FATAL("Misaligned atomic memory operation"); |
| } |
| std::atomic<type>* atomic = reinterpret_cast<std::atomic<type>*>(addr); |
| if (addr != reserved_address_) { |
| set_xreg(instr.rd(), 1); |
| return; |
| } |
| type expected = reserved_value_; |
| type desired = get_xreg(instr.rs2()); |
| bool success = |
| atomic->compare_exchange_strong(expected, desired, instr.memory_order()); |
| set_xreg(instr.rd(), success ? 0 : 1); |
| } |
| |
| template <typename type> |
| void Simulator::InterpretAMOSWAP(Instr instr) { |
| uintx_t addr = get_xreg(instr.rs1()); |
| if ((addr & (sizeof(type) - 1)) != 0) { |
| FATAL("Misaligned atomic memory operation"); |
| } |
| std::atomic<type>* atomic = reinterpret_cast<std::atomic<type>*>(addr); |
| type desired = get_xreg(instr.rs2()); |
| type result = atomic->exchange(desired, instr.memory_order()); |
| set_xreg(instr.rd(), sign_extend(result)); |
| } |
| |
| template <typename type> |
| void Simulator::InterpretAMOADD(Instr instr) { |
| uintx_t addr = get_xreg(instr.rs1()); |
| if ((addr & (sizeof(type) - 1)) != 0) { |
| FATAL("Misaligned atomic memory operation"); |
| } |
| std::atomic<type>* atomic = reinterpret_cast<std::atomic<type>*>(addr); |
| type arg = get_xreg(instr.rs2()); |
| type result = atomic->fetch_add(arg, instr.memory_order()); |
| set_xreg(instr.rd(), sign_extend(result)); |
| } |
| |
| template <typename type> |
| void Simulator::InterpretAMOXOR(Instr instr) { |
| uintx_t addr = get_xreg(instr.rs1()); |
| if ((addr & (sizeof(type) - 1)) != 0) { |
| FATAL("Misaligned atomic memory operation"); |
| } |
| std::atomic<type>* atomic = reinterpret_cast<std::atomic<type>*>(addr); |
| type arg = get_xreg(instr.rs2()); |
| type result = atomic->fetch_xor(arg, instr.memory_order()); |
| set_xreg(instr.rd(), sign_extend(result)); |
| } |
| |
| template <typename type> |
| void Simulator::InterpretAMOAND(Instr instr) { |
| uintx_t addr = get_xreg(instr.rs1()); |
| if ((addr & (sizeof(type) - 1)) != 0) { |
| FATAL("Misaligned atomic memory operation"); |
| } |
| std::atomic<type>* atomic = reinterpret_cast<std::atomic<type>*>(addr); |
| type arg = get_xreg(instr.rs2()); |
| type result = atomic->fetch_and(arg, instr.memory_order()); |
| set_xreg(instr.rd(), sign_extend(result)); |
| } |
| |
| template <typename type> |
| void Simulator::InterpretAMOOR(Instr instr) { |
| uintx_t addr = get_xreg(instr.rs1()); |
| if ((addr & (sizeof(type) - 1)) != 0) { |
| FATAL("Misaligned atomic memory operation"); |
| } |
| std::atomic<type>* atomic = reinterpret_cast<std::atomic<type>*>(addr); |
| type arg = get_xreg(instr.rs2()); |
| type result = atomic->fetch_or(arg, instr.memory_order()); |
| set_xreg(instr.rd(), sign_extend(result)); |
| } |
| |
| template <typename type> |
| void Simulator::InterpretAMOMIN(Instr instr) { |
| uintx_t addr = get_xreg(instr.rs1()); |
| if ((addr & (sizeof(type) - 1)) != 0) { |
| FATAL("Misaligned atomic memory operation"); |
| } |
| std::atomic<type>* atomic = reinterpret_cast<std::atomic<type>*>(addr); |
| type expected = atomic->load(std::memory_order_relaxed); |
| type compare = get_xreg(instr.rs2()); |
| type desired; |
| do { |
| desired = expected < compare ? expected : compare; |
| } while ( |
| !atomic->compare_exchange_weak(expected, desired, instr.memory_order())); |
| set_xreg(instr.rd(), sign_extend(expected)); |
| } |
| |
| template <typename type> |
| void Simulator::InterpretAMOMAX(Instr instr) { |
| uintx_t addr = get_xreg(instr.rs1()); |
| if ((addr & (sizeof(type) - 1)) != 0) { |
| FATAL("Misaligned atomic memory operation"); |
| } |
| std::atomic<type>* atomic = reinterpret_cast<std::atomic<type>*>(addr); |
| type expected = atomic->load(std::memory_order_relaxed); |
| type compare = get_xreg(instr.rs2()); |
| type desired; |
| do { |
| desired = expected > compare ? expected : compare; |
| } while ( |
| !atomic->compare_exchange_weak(expected, desired, instr.memory_order())); |
| set_xreg(instr.rd(), sign_extend(expected)); |
| } |
| |
| void Simulator::InterpretAMO32(Instr instr) { |
| switch (instr.funct5()) { |
| case LR: |
| InterpretLR<int32_t>(instr); |
| break; |
| case SC: |
| InterpretSC<int32_t>(instr); |
| break; |
| case AMOSWAP: |
| InterpretAMOSWAP<int32_t>(instr); |
| break; |
| case AMOADD: |
| InterpretAMOADD<int32_t>(instr); |
| break; |
| case AMOXOR: |
| InterpretAMOXOR<int32_t>(instr); |
| break; |
| case AMOAND: |
| InterpretAMOAND<int32_t>(instr); |
| break; |
| case AMOOR: |
| InterpretAMOOR<int32_t>(instr); |
| break; |
| case AMOMIN: |
| InterpretAMOMIN<int32_t>(instr); |
| break; |
| case AMOMAX: |
| InterpretAMOMAX<int32_t>(instr); |
| break; |
| case AMOMINU: |
| InterpretAMOMIN<uint32_t>(instr); |
| break; |
| case AMOMAXU: |
| InterpretAMOMAX<uint32_t>(instr); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretAMO64(Instr instr) { |
| switch (instr.funct5()) { |
| #if XLEN >= 64 |
| case LR: |
| InterpretLR<int64_t>(instr); |
| break; |
| case SC: |
| InterpretSC<int64_t>(instr); |
| break; |
| case AMOSWAP: |
| InterpretAMOSWAP<int64_t>(instr); |
| break; |
| case AMOADD: |
| InterpretAMOADD<int64_t>(instr); |
| break; |
| case AMOXOR: |
| InterpretAMOXOR<int64_t>(instr); |
| break; |
| case AMOAND: |
| InterpretAMOAND<int64_t>(instr); |
| break; |
| case AMOOR: |
| InterpretAMOOR<int64_t>(instr); |
| break; |
| case AMOMIN: |
| InterpretAMOMIN<int64_t>(instr); |
| break; |
| case AMOMAX: |
| InterpretAMOMAX<int64_t>(instr); |
| break; |
| case AMOMINU: |
| InterpretAMOMIN<uint64_t>(instr); |
| break; |
| case AMOMAXU: |
| InterpretAMOMAX<uint64_t>(instr); |
| break; |
| #endif // XLEN >= 64 |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretFMADD(Instr instr) { |
| switch (instr.funct2()) { |
| case F2_S: { |
| float rs1 = get_fregs(instr.frs1()); |
| float rs2 = get_fregs(instr.frs2()); |
| float rs3 = get_fregs(instr.frs3()); |
| set_fregs(instr.frd(), (rs1 * rs2) + rs3); |
| break; |
| } |
| case F2_D: { |
| double rs1 = get_fregd(instr.frs1()); |
| double rs2 = get_fregd(instr.frs2()); |
| double rs3 = get_fregd(instr.frs3()); |
| set_fregd(instr.frd(), (rs1 * rs2) + rs3); |
| break; |
| } |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretFMSUB(Instr instr) { |
| switch (instr.funct2()) { |
| case F2_S: { |
| float rs1 = get_fregs(instr.frs1()); |
| float rs2 = get_fregs(instr.frs2()); |
| float rs3 = get_fregs(instr.frs3()); |
| set_fregs(instr.frd(), (rs1 * rs2) - rs3); |
| break; |
| } |
| case F2_D: { |
| double rs1 = get_fregd(instr.frs1()); |
| double rs2 = get_fregd(instr.frs2()); |
| double rs3 = get_fregd(instr.frs3()); |
| set_fregd(instr.frd(), (rs1 * rs2) - rs3); |
| break; |
| } |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretFNMSUB(Instr instr) { |
| switch (instr.funct2()) { |
| case F2_S: { |
| float rs1 = get_fregs(instr.frs1()); |
| float rs2 = get_fregs(instr.frs2()); |
| float rs3 = get_fregs(instr.frs3()); |
| set_fregs(instr.frd(), -(rs1 * rs2) + rs3); |
| break; |
| } |
| case F2_D: { |
| double rs1 = get_fregd(instr.frs1()); |
| double rs2 = get_fregd(instr.frs2()); |
| double rs3 = get_fregd(instr.frs3()); |
| set_fregd(instr.frd(), -(rs1 * rs2) + rs3); |
| break; |
| } |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretFNMADD(Instr instr) { |
| switch (instr.funct2()) { |
| case F2_S: { |
| float rs1 = get_fregs(instr.frs1()); |
| float rs2 = get_fregs(instr.frs2()); |
| float rs3 = get_fregs(instr.frs3()); |
| set_fregs(instr.frd(), -(rs1 * rs2) - rs3); |
| break; |
| } |
| case F2_D: { |
| double rs1 = get_fregd(instr.frs1()); |
| double rs2 = get_fregd(instr.frs2()); |
| double rs3 = get_fregd(instr.frs3()); |
| set_fregd(instr.frd(), -(rs1 * rs2) - rs3); |
| break; |
| } |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| static bool is_quiet(float x) { |
| // Warning: This is true on Intel/ARM, but not everywhere. |
| return (bit_cast<uint32_t>(x) & (static_cast<uint32_t>(1) << 22)) != 0; |
| } |
| |
| static uintx_t fclass(float x) { |
| ASSERT(!is_quiet(std::numeric_limits<float>::signaling_NaN())); |
| ASSERT(is_quiet(std::numeric_limits<float>::quiet_NaN())); |
| |
| switch (fpclassify(x)) { |
| case FP_INFINITE: |
| return signbit(x) ? kFClassNegInfinity : kFClassPosInfinity; |
| case FP_NAN: |
| return is_quiet(x) ? kFClassQuietNan : kFClassSignallingNan; |
| case FP_ZERO: |
| return signbit(x) ? kFClassNegZero : kFClassPosZero; |
| case FP_SUBNORMAL: |
| return signbit(x) ? kFClassNegSubnormal : kFClassPosSubnormal; |
| case FP_NORMAL: |
| return signbit(x) ? kFClassNegNormal : kFClassPosNormal; |
| default: |
| UNREACHABLE(); |
| return 0; |
| } |
| } |
| |
| static bool is_quiet(double x) { |
| // Warning: This is true on Intel/ARM, but not everywhere. |
| return (bit_cast<uint64_t>(x) & (static_cast<uint64_t>(1) << 51)) != 0; |
| } |
| |
| static uintx_t fclass(double x) { |
| ASSERT(!is_quiet(std::numeric_limits<double>::signaling_NaN())); |
| ASSERT(is_quiet(std::numeric_limits<double>::quiet_NaN())); |
| |
| switch (fpclassify(x)) { |
| case FP_INFINITE: |
| return signbit(x) ? kFClassNegInfinity : kFClassPosInfinity; |
| case FP_NAN: |
| return is_quiet(x) ? kFClassQuietNan : kFClassSignallingNan; |
| case FP_ZERO: |
| return signbit(x) ? kFClassNegZero : kFClassPosZero; |
| case FP_SUBNORMAL: |
| return signbit(x) ? kFClassNegSubnormal : kFClassPosSubnormal; |
| case FP_NORMAL: |
| return signbit(x) ? kFClassNegNormal : kFClassPosNormal; |
| default: |
| UNREACHABLE(); |
| return 0; |
| } |
| } |
| |
| static float roundevenf(float x) { |
| float rounded = roundf(x); |
| if (fabsf(x - rounded) == 0.5f) { // Tie |
| if (fmodf(rounded, 2) != 0) { // Not even |
| if (rounded > 0.0f) { |
| rounded -= 1.0f; |
| } else { |
| rounded += 1.0f; |
| } |
| ASSERT(fmodf(rounded, 2) == 0); |
| } |
| } |
| return rounded; |
| } |
| |
| static double roundeven(double x) { |
| double rounded = round(x); |
| if (fabs(x - rounded) == 0.5f) { // Tie |
| if (fmod(rounded, 2) != 0) { // Not even |
| if (rounded > 0.0f) { |
| rounded -= 1.0f; |
| } else { |
| rounded += 1.0f; |
| } |
| ASSERT(fmod(rounded, 2) == 0); |
| } |
| } |
| return rounded; |
| } |
| |
| static float Round(float x, RoundingMode rounding) { |
| switch (rounding) { |
| case RNE: // Round to Nearest, ties to Even |
| return roundevenf(x); |
| case RTZ: // Round towards Zero |
| return truncf(x); |
| case RDN: // Round Down (toward negative infinity) |
| return floorf(x); |
| case RUP: // Round Up (toward positive infinity) |
| return ceilf(x); |
| case RMM: // Round to nearest, ties to Max Magnitude |
| return roundf(x); |
| case DYN: // Dynamic rounding mode |
| UNIMPLEMENTED(); |
| default: |
| FATAL("Invalid rounding mode"); |
| } |
| } |
| |
| static double Round(double x, RoundingMode rounding) { |
| switch (rounding) { |
| case RNE: // Round to Nearest, ties to Even |
| return roundeven(x); |
| case RTZ: // Round towards Zero |
| return trunc(x); |
| case RDN: // Round Down (toward negative infinity) |
| return floor(x); |
| case RUP: // Round Up (toward positive infinity) |
| return ceil(x); |
| case RMM: // Round to nearest, ties to Max Magnitude |
| return round(x); |
| case DYN: // Dynamic rounding mode |
| UNIMPLEMENTED(); |
| default: |
| FATAL("Invalid rounding mode"); |
| } |
| } |
| |
| static int32_t fcvtws(float x, RoundingMode rounding) { |
| if (x < static_cast<float>(kMinInt32)) { |
| return kMinInt32; // Negative infinity. |
| } |
| if (x < static_cast<float>(kMaxInt32)) { |
| return static_cast<int32_t>(Round(x, rounding)); |
| } |
| return kMaxInt32; // Positive infinity, NaN. |
| } |
| |
| static uint32_t fcvtwus(float x, RoundingMode rounding) { |
| if (x < static_cast<float>(0)) { |
| return 0; // Negative infinity. |
| } |
| if (x < static_cast<float>(kMaxUint32)) { |
| return static_cast<uint32_t>(Round(x, rounding)); |
| } |
| return kMaxUint32; // Positive infinity, NaN. |
| } |
| |
| #if XLEN >= 64 |
| static int64_t fcvtls(float x, RoundingMode rounding) { |
| if (x < static_cast<float>(kMinInt64)) { |
| return kMinInt64; // Negative infinity. |
| } |
| if (x < static_cast<float>(kMaxInt64)) { |
| return static_cast<int64_t>(Round(x, rounding)); |
| } |
| return kMaxInt64; // Positive infinity, NaN. |
| } |
| |
| static uint64_t fcvtlus(float x, RoundingMode rounding) { |
| if (x < static_cast<float>(0.0)) { |
| return 0; // Negative infinity. |
| } |
| if (x < static_cast<float>(kMaxUint64)) { |
| return static_cast<uint64_t>(Round(x, rounding)); |
| } |
| return kMaxUint64; // Positive infinity, NaN. |
| } |
| #endif // XLEN >= 64 |
| |
| static int32_t fcvtwd(double x, RoundingMode rounding) { |
| if (x < static_cast<double>(kMinInt32)) { |
| return kMinInt32; // Negative infinity. |
| } |
| if (x < static_cast<double>(kMaxInt32)) { |
| return static_cast<int32_t>(Round(x, rounding)); |
| } |
| return kMaxInt32; // Positive infinity, NaN. |
| } |
| |
| static uint32_t fcvtwud(double x, RoundingMode rounding) { |
| if (x < static_cast<double>(0)) { |
| return 0; // Negative infinity. |
| } |
| if (x < static_cast<double>(kMaxUint32)) { |
| return static_cast<uint32_t>(Round(x, rounding)); |
| } |
| return kMaxUint32; // Positive infinity, NaN. |
| } |
| |
| #if XLEN >= 64 |
| static int64_t fcvtld(double x, RoundingMode rounding) { |
| if (x < static_cast<double>(kMinInt64)) { |
| return kMinInt64; // Negative infinity. |
| } |
| if (x < static_cast<double>(kMaxInt64)) { |
| return static_cast<int64_t>(Round(x, rounding)); |
| } |
| return kMaxInt64; // Positive infinity, NaN. |
| } |
| |
| static uint64_t fcvtlud(double x, RoundingMode rounding) { |
| if (x < static_cast<double>(0.0)) { |
| return 0; // Negative infinity. |
| } |
| if (x < static_cast<double>(kMaxUint64)) { |
| return static_cast<uint64_t>(Round(x, rounding)); |
| } |
| return kMaxUint64; // Positive infinity, NaN. |
| } |
| #endif // XLEN >= 64 |
| |
| void Simulator::InterpretOPFP(Instr instr) { |
| switch (instr.funct7()) { |
| case FADDS: { |
| float rs1 = get_fregs(instr.frs1()); |
| float rs2 = get_fregs(instr.frs2()); |
| set_fregs(instr.frd(), rs1 + rs2); |
| break; |
| } |
| case FSUBS: { |
| float rs1 = get_fregs(instr.frs1()); |
| float rs2 = get_fregs(instr.frs2()); |
| set_fregs(instr.frd(), rs1 - rs2); |
| break; |
| } |
| case FMULS: { |
| float rs1 = get_fregs(instr.frs1()); |
| float rs2 = get_fregs(instr.frs2()); |
| set_fregs(instr.frd(), rs1 * rs2); |
| break; |
| } |
| case FDIVS: { |
| float rs1 = get_fregs(instr.frs1()); |
| float rs2 = get_fregs(instr.frs2()); |
| set_fregs(instr.frd(), rs1 / rs2); |
| break; |
| } |
| case FSQRTS: { |
| float rs1 = get_fregs(instr.frs1()); |
| set_fregs(instr.frd(), sqrtf(rs1)); |
| break; |
| } |
| case FSGNJS: { |
| const uint32_t kSignMask = static_cast<uint32_t>(1) << 31; |
| uint32_t rs1 = bit_cast<uint32_t>(get_fregs(instr.frs1())); |
| uint32_t rs2 = bit_cast<uint32_t>(get_fregs(instr.frs2())); |
| uint32_t result; |
| switch (instr.funct3()) { |
| case J: |
| result = (rs1 & ~kSignMask) | (rs2 & kSignMask); |
| break; |
| case JN: |
| result = (rs1 & ~kSignMask) | (~rs2 & kSignMask); |
| break; |
| case JX: |
| result = (rs1 & ~kSignMask) | ((rs1 ^ rs2) & kSignMask); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| set_fregs(instr.frd(), bit_cast<float>(result)); |
| break; |
| } |
| case FMINMAXS: { |
| float rs1 = get_fregs(instr.frs1()); |
| float rs2 = get_fregs(instr.frs2()); |
| switch (instr.funct3()) { |
| case MIN: |
| set_fregs(instr.frd(), fminf(rs1, rs2)); |
| break; |
| case MAX: |
| set_fregs(instr.frd(), fmaxf(rs1, rs2)); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| } |
| case FCMPS: { |
| float rs1 = get_fregs(instr.frs1()); |
| float rs2 = get_fregs(instr.frs2()); |
| switch (instr.funct3()) { |
| case FEQ: |
| set_xreg(instr.rd(), rs1 == rs2 ? 1 : 0); |
| break; |
| case FLT: |
| set_xreg(instr.rd(), rs1 < rs2 ? 1 : 0); |
| break; |
| case FLE: |
| set_xreg(instr.rd(), rs1 <= rs2 ? 1 : 0); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| } |
| case FCLASSS: // = FMVXW |
| switch (instr.funct3()) { |
| case 1: |
| // fclass.s |
| set_xreg(instr.rd(), fclass(get_fregs(instr.frs1()))); |
| break; |
| case 0: |
| // fmv.x.s |
| set_xreg(instr.rd(), |
| sign_extend(bit_cast<int32_t>(get_fregs(instr.frs1())))); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| case FCVTintS: |
| switch (static_cast<FcvtRs2>(instr.rs2())) { |
| case W: |
| set_xreg(instr.rd(), sign_extend(fcvtws(get_fregs(instr.frs1()), |
| instr.rounding()))); |
| break; |
| case WU: |
| set_xreg(instr.rd(), sign_extend(fcvtwus(get_fregs(instr.frs1()), |
| instr.rounding()))); |
| break; |
| #if XLEN >= 64 |
| case L: |
| set_xreg(instr.rd(), sign_extend(fcvtls(get_fregs(instr.frs1()), |
| instr.rounding()))); |
| break; |
| case LU: |
| set_xreg(instr.rd(), sign_extend(fcvtlus(get_fregs(instr.frs1()), |
| instr.rounding()))); |
| break; |
| #endif // XLEN >= 64 |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| case FCVTSint: |
| switch (static_cast<FcvtRs2>(instr.rs2())) { |
| case W: |
| set_fregs( |
| instr.frd(), |
| static_cast<float>(static_cast<int32_t>(get_xreg(instr.rs1())))); |
| break; |
| case WU: |
| set_fregs( |
| instr.frd(), |
| static_cast<float>(static_cast<uint32_t>(get_xreg(instr.rs1())))); |
| break; |
| #if XLEN >= 64 |
| case L: |
| set_fregs( |
| instr.frd(), |
| static_cast<float>(static_cast<int64_t>(get_xreg(instr.rs1())))); |
| break; |
| case LU: |
| set_fregs( |
| instr.frd(), |
| static_cast<float>(static_cast<uint64_t>(get_xreg(instr.rs1())))); |
| break; |
| #endif // XLEN >= 64 |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| case FMVWX: |
| set_fregs(instr.frd(), |
| bit_cast<float>(static_cast<int32_t>(get_xreg(instr.rs1())))); |
| break; |
| case FADDD: { |
| double rs1 = get_fregd(instr.frs1()); |
| double rs2 = get_fregd(instr.frs2()); |
| set_fregd(instr.frd(), rs1 + rs2); |
| break; |
| } |
| case FSUBD: { |
| double rs1 = get_fregd(instr.frs1()); |
| double rs2 = get_fregd(instr.frs2()); |
| set_fregd(instr.frd(), rs1 - rs2); |
| break; |
| } |
| case FMULD: { |
| double rs1 = get_fregd(instr.frs1()); |
| double rs2 = get_fregd(instr.frs2()); |
| set_fregd(instr.frd(), rs1 * rs2); |
| break; |
| } |
| case FDIVD: { |
| double rs1 = get_fregd(instr.frs1()); |
| double rs2 = get_fregd(instr.frs2()); |
| set_fregd(instr.frd(), rs1 / rs2); |
| break; |
| } |
| case FSQRTD: { |
| double rs1 = get_fregd(instr.frs1()); |
| set_fregd(instr.frd(), sqrt(rs1)); |
| break; |
| } |
| case FSGNJD: { |
| const uint64_t kSignMask = static_cast<uint64_t>(1) << 63; |
| uint64_t rs1 = bit_cast<uint64_t>(get_fregd(instr.frs1())); |
| uint64_t rs2 = bit_cast<uint64_t>(get_fregd(instr.frs2())); |
| uint64_t result; |
| switch (instr.funct3()) { |
| case J: |
| result = (rs1 & ~kSignMask) | (rs2 & kSignMask); |
| break; |
| case JN: |
| result = (rs1 & ~kSignMask) | (~rs2 & kSignMask); |
| break; |
| case JX: |
| result = (rs1 & ~kSignMask) | ((rs1 ^ rs2) & kSignMask); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| set_fregd(instr.frd(), bit_cast<double>(result)); |
| break; |
| } |
| case FMINMAXD: { |
| double rs1 = get_fregd(instr.frs1()); |
| double rs2 = get_fregd(instr.frs2()); |
| switch (instr.funct3()) { |
| case MIN: |
| set_fregd(instr.frd(), fmin(rs1, rs2)); |
| break; |
| case MAX: |
| set_fregd(instr.frd(), fmax(rs1, rs2)); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| } |
| case FCVTS: { |
| switch (static_cast<FcvtRs2>(instr.rs2())) { |
| case 1: |
| set_fregs(instr.frd(), static_cast<float>(get_fregd(instr.frs1()))); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| } |
| case FCVTD: { |
| switch (static_cast<FcvtRs2>(instr.rs2())) { |
| case 0: |
| set_fregd(instr.frd(), static_cast<double>(get_fregs(instr.frs1()))); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| } |
| |
| case FCMPD: { |
| double rs1 = get_fregd(instr.frs1()); |
| double rs2 = get_fregd(instr.frs2()); |
| switch (instr.funct3()) { |
| case FEQ: |
| set_xreg(instr.rd(), rs1 == rs2 ? 1 : 0); |
| break; |
| case FLT: |
| set_xreg(instr.rd(), rs1 < rs2 ? 1 : 0); |
| break; |
| case FLE: |
| set_xreg(instr.rd(), rs1 <= rs2 ? 1 : 0); |
| break; |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| } |
| case FCLASSD: // = FMVXD |
| switch (instr.funct3()) { |
| case 1: |
| // fclass.d |
| set_xreg(instr.rd(), fclass(get_fregd(instr.frs1()))); |
| break; |
| #if XLEN >= 64 |
| case 0: |
| // fmv.x.d |
| set_xreg(instr.rd(), bit_cast<int64_t>(get_fregd(instr.frs1()))); |
| break; |
| #endif // XLEN >= 64 |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| case FCVTintD: |
| switch (static_cast<FcvtRs2>(instr.rs2())) { |
| case W: |
| set_xreg(instr.rd(), sign_extend(fcvtwd(get_fregd(instr.frs1()), |
| instr.rounding()))); |
| break; |
| case WU: |
| set_xreg(instr.rd(), sign_extend(fcvtwud(get_fregd(instr.frs1()), |
| instr.rounding()))); |
| break; |
| #if XLEN >= 64 |
| case L: |
| set_xreg(instr.rd(), sign_extend(fcvtld(get_fregd(instr.frs1()), |
| instr.rounding()))); |
| break; |
| case LU: |
| set_xreg(instr.rd(), sign_extend(fcvtlud(get_fregd(instr.frs1()), |
| instr.rounding()))); |
| break; |
| #endif // XLEN >= 64 |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| case FCVTDint: |
| switch (static_cast<FcvtRs2>(instr.rs2())) { |
| case W: |
| set_fregd( |
| instr.frd(), |
| static_cast<double>(static_cast<int32_t>(get_xreg(instr.rs1())))); |
| break; |
| case WU: |
| set_fregd(instr.frd(), static_cast<double>(static_cast<uint32_t>( |
| get_xreg(instr.rs1())))); |
| break; |
| #if XLEN >= 64 |
| case L: |
| set_fregd( |
| instr.frd(), |
| static_cast<double>(static_cast<int64_t>(get_xreg(instr.rs1())))); |
| break; |
| case LU: |
| set_fregd(instr.frd(), static_cast<double>(static_cast<uint64_t>( |
| get_xreg(instr.rs1())))); |
| break; |
| #endif // XLEN >= 64 |
| default: |
| IllegalInstruction(instr); |
| } |
| break; |
| #if XLEN >= 64 |
| case FMVDX: |
| set_fregd(instr.frd(), bit_cast<double>(get_xreg(instr.rs1()))); |
| break; |
| #endif // XLEN >= 64 |
| default: |
| IllegalInstruction(instr); |
| } |
| pc_ += instr.length(); |
| } |
| |
| void Simulator::InterpretEBREAK(Instr instr) { |
| PrintRegisters(); |
| PrintStack(); |
| FATAL("Encounted EBREAK"); |
| } |
| |
| void Simulator::InterpretEBREAK(CInstr instr) { |
| PrintRegisters(); |
| PrintStack(); |
| FATAL("Encounted EBREAK"); |
| } |
| |
| void Simulator::IllegalInstruction(Instr instr) { |
| PrintRegisters(); |
| PrintStack(); |
| FATAL("Illegal instruction: 0x%08x", instr.encoding()); |
| } |
| |
| void Simulator::IllegalInstruction(CInstr instr) { |
| PrintRegisters(); |
| PrintStack(); |
| FATAL("Illegal instruction: 0x%04x", instr.encoding()); |
| } |
| |
| template <typename type> |
| type Simulator::MemoryRead(uintx_t addr, Register base) { |
| #if defined(DEBUG) |
| if ((base == SP) || (base == FP)) { |
| if ((addr + sizeof(type) > stack_base()) || (addr < get_xreg(SP))) { |
| PrintRegisters(); |
| PrintStack(); |
| FATAL("Out-of-bounds stack access"); |
| } |
| } else { |
| const uintx_t kPageSize = 16 * KB; |
| if ((addr < kPageSize) || (addr + sizeof(type) >= ~kPageSize)) { |
| PrintRegisters(); |
| PrintStack(); |
| FATAL("Bad memory access"); |
| } |
| } |
| #endif |
| return *reinterpret_cast<type*>(addr); |
| } |
| |
| template <typename type> |
| void Simulator::MemoryWrite(uintx_t addr, type value, Register base) { |
| #if defined(DEBUG) |
| if ((base == SP) || (base == FP)) { |
| if ((addr + sizeof(type) > stack_base()) || (addr < get_xreg(SP))) { |
| PrintRegisters(); |
| PrintStack(); |
| FATAL("Out-of-bounds stack access"); |
| } |
| } else { |
| const uintx_t kPageSize = 16 * KB; |
| if ((addr < kPageSize) || (addr + sizeof(type) >= ~kPageSize)) { |
| PrintRegisters(); |
| PrintStack(); |
| FATAL("Bad memory access"); |
| } |
| } |
| #endif |
| *reinterpret_cast<type*>(addr) = value; |
| } |
| |
| enum ControlStatusRegister { |
| fflags = 0x001, |
| frm = 0x002, |
| fcsr = 0x003, |
| cycle = 0xC00, |
| time = 0xC01, |
| instret = 0xC02, |
| #if XLEN == 32 |
| cycleh = 0xC80, |
| timeh = 0xC81, |
| instreth = 0xC82, |
| #endif |
| }; |
| |
| intx_t Simulator::CSRRead(uint16_t csr) { |
| switch (csr) { |
| case fcsr: |
| return fcsr_; |
| case cycle: |
| return instret_ / 2; |
| case time: |
| return 0; |
| case instret: |
| return instret_; |
| #if XLEN == 32 |
| case cycleh: |
| return (instret_ / 2) >> 32; |
| case timeh: |
| return 0; |
| case instreth: |
| return instret_ >> 32; |
| #endif |
| default: |
| FATAL("Unknown CSR: %d", csr); |
| } |
| } |
| |
| void Simulator::CSRWrite(uint16_t csr, intx_t value) { |
| UNIMPLEMENTED(); |
| } |
| |
| void Simulator::CSRSet(uint16_t csr, intx_t mask) { |
| UNIMPLEMENTED(); |
| } |
| |
| void Simulator::CSRClear(uint16_t csr, intx_t mask) { |
| UNIMPLEMENTED(); |
| } |
| |
| } // namespace dart |
| |
| #endif // !defined(USING_SIMULATOR) |
| |
| #endif // defined TARGET_ARCH_RISCV |