| // 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 <bit> | |
| #include <cmath> | |
| #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(DART_INCLUDE_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."); | |
| DEFINE_FLAG(bool, sim_buffer_memory, false, "Simulate weak memory ordering."); | |
| // 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); | |
| DART_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_(nullptr) {} | |
| 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 thread. | |
| Simulator* Simulator::Current() { | |
| Thread* thread = Thread::Current(); | |
| Simulator* simulator = thread->simulator(); | |
| if (simulator == nullptr) { | |
| NoSafepointScope no_safepoint; | |
| simulator = new Simulator(); | |
| thread->set_simulator(simulator); | |
| } | |
| return simulator; | |
| } | |
| void Simulator::Init() {} | |
| Simulator::Simulator() : random_(), memory_(FLAG_sim_buffer_memory) { | |
| // 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)]; | |
| shadow_stack_ = new char[64 * KB]; | |
| // 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); | |
| } | |
| for (intptr_t i = 0; i < kNumberOfVectorRegisters; i++) { | |
| for (intptr_t j = 0; j < VLEN / 8; j++) { | |
| vregs_[i][j] = random_.NextUInt64(); | |
| } | |
| } | |
| // The sp is initialized to point to the bottom (high address) of the | |
| // allocated stack area. | |
| set_xreg(SP, stack_base()); | |
| ssp_ = reinterpret_cast<uintx_t>(shadow_stack_) + 64 * KB; | |
| ss_enabled_ = true; | |
| // 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_; | |
| delete[] shadow_stack_; | |
| } | |
| 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); | |
| } | |
| } | |
| for (intptr_t i = 0; i < kNumberOfVectorRegisters; i++) { | |
| for (intptr_t j = 0; j < VLEN / 8; j++) { | |
| vregs_[i][j] = random_.NextUInt64(); | |
| } | |
| } | |
| preserved->ssp = ssp_; | |
| #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); | |
| } | |
| } | |
| for (intptr_t i = 0; i < kNumberOfVectorRegisters; i++) { | |
| for (intptr_t j = 0; j < VLEN / 8; j++) { | |
| vregs_[i][j] = random_.NextUInt64(); | |
| } | |
| } | |
| #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]; | |
| } | |
| preserved->ssp = ssp_; | |
| #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]); | |
| } | |
| } | |
| } | |
| if (ssp_ != preserved->ssp) { | |
| FATAL("ssp was not preserved\n"); | |
| } | |
| #endif | |
| } | |
| void Simulator::RunCall(intx_t entry, PreservedRegisters* preserved) { | |
| pc_ = entry; | |
| set_xreg(RA, kEndSimulatingPC); | |
| Execute(); | |
| CheckPreservedRegisters(preserved); | |
| // We can't instrument the runtime. | |
| memory_.FlushAll(); | |
| } | |
| 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); | |
| } | |
| // We can't instrument the runtime. | |
| memory_.FlushAll(); | |
| return return_value; | |
| } | |
| void Simulator::Execute() { | |
| if (FLAG_trace_sim_after == ULLONG_MAX) [[likely]] { | |
| ExecuteNoTrace(); | |
| } else { | |
| ExecuteTrace(); | |
| } | |
| } | |
| void Simulator::ExecuteNoTrace() { | |
| while (pc_ != kEndSimulatingPC) { | |
| uint16_t parcel = *reinterpret_cast<uint16_t*>(pc_); | |
| if (IsCInstruction(parcel)) { | |
| CInstr instr(parcel); | |
| Interpret(instr); | |
| } else { | |
| Instr instr(LoadUnaligned(reinterpret_cast<uint32_t*>(pc_))); | |
| Interpret(instr); | |
| } | |
| instret_++; | |
| } | |
| } | |
| void Simulator::ExecuteTrace() { | |
| 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(LoadUnaligned(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() != nullptr && buf->link()->sp() <= sp) { | |
| buf = buf->link(); | |
| } | |
| ASSERT(buf != nullptr); | |
| // 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)); | |
| #if defined(DART_TARGET_OS_FUCHSIA) || defined(DART_TARGET_OS_ANDROID) | |
| set_xreg(GP, thread->saved_shadow_call_stack()); | |
| #endif | |
| // 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_STATE, | |
| thread->write_barrier_mask() ^ | |
| ((UntaggedObject::kGenerationalBarrierMask << 1) - 1)); | |
| 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("%5s: %8x %11d", cpu_reg_names[i], xregs_[i], xregs_[i]); | |
| #elif XLEN == 64 | |
| OS::Print("%5s: %16" Px64 " %20" Pd64, cpu_reg_names[i], xregs_[i], | |
| xregs_[i]); | |
| #endif | |
| OS::Print(" %5s: %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 | |
| for (intptr_t i = 0; i < kNumberOfVectorRegisters; i++) { | |
| OS::Print("%5s: ", vector_reg_names[i]); | |
| for (intptr_t j = VLEN / 8 - 1; j >= 0; j--) { | |
| OS::Print("%02x", (unsigned)vregs_[i][j]); | |
| } | |
| OS::Print("\n"); | |
| } | |
| #if XLEN == 32 | |
| OS::Print(" vl: %8x %11d\n", vl_, vl_); | |
| #elif XLEN == 64 | |
| OS::Print(" vl: %16" Px64 " %20" Pd64 "\n", vl_, vl_); | |
| #endif | |
| #if XLEN == 32 | |
| OS::Print("vtype: %8x ", vtype_); | |
| #elif XLEN == 64 | |
| OS::Print("vtype: %16" Px64 " ", vtype_); | |
| #endif | |
| switch (vsew()) { | |
| case e8: | |
| OS::Print("e8"); | |
| break; | |
| case e16: | |
| OS::Print("e16"); | |
| break; | |
| case e32: | |
| OS::Print("e32"); | |
| break; | |
| case e64: | |
| OS::Print("e64"); | |
| break; | |
| default: | |
| OS::Print("invalid sew"); | |
| break; | |
| } | |
| switch (vlmul()) { | |
| case mf8: | |
| OS::Print(", mf8"); | |
| break; | |
| case mf4: | |
| OS::Print(", mf4"); | |
| break; | |
| case mf2: | |
| OS::Print(", mf2"); | |
| break; | |
| case m1: | |
| OS::Print(", m1"); | |
| break; | |
| case m2: | |
| OS::Print(", m2"); | |
| break; | |
| case m4: | |
| OS::Print(", m4"); | |
| break; | |
| case m8: | |
| OS::Print(", m8"); | |
| break; | |
| default: | |
| OS::Print(", invalid lmul"); | |
| break; | |
| } | |
| if ((vtype_ & (1 << 6)) == 0) { | |
| OS::Print(", tu"); | |
| } else { | |
| OS::Print(", ta"); | |
| } | |
| if ((vtype_ & (1 << 7)) == 0) { | |
| OS::Print(", mu\n"); | |
| } else { | |
| OS::Print(", ma\n"); | |
| } | |
| } | |
| 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(); | |
| } | |
| } | |
| DART_FORCE_INLINE | |
| 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; | |
| case OPV: | |
| InterpretOPV(instr); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| } | |
| static intx_t mul(intx_t a, intx_t b) { | |
| return static_cast<uintx_t>(a) * static_cast<uintx_t>(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 | |
| static uintx_t clz(uintx_t a) { | |
| return std::countl_zero(a); | |
| } | |
| static uintx_t ctz(uintx_t a) { | |
| return std::countr_zero(a); | |
| } | |
| static uintx_t cpop(uintx_t a) { | |
| return std::popcount(a); | |
| } | |
| static uintx_t clzw(uint32_t a) { | |
| return std::countl_zero(a); | |
| } | |
| static uintx_t ctzw(uint32_t a) { | |
| return std::countr_zero(a); | |
| } | |
| static uintx_t cpopw(uint32_t a) { | |
| return std::popcount(a); | |
| } | |
| static intx_t max(intx_t a, intx_t b) { | |
| return a > b ? a : b; | |
| } | |
| static uintx_t maxu(uintx_t a, uintx_t b) { | |
| return a > b ? a : b; | |
| } | |
| static intx_t min(intx_t a, intx_t b) { | |
| return a < b ? a : b; | |
| } | |
| static uintx_t minu(uintx_t a, uintx_t b) { | |
| return a < b ? a : b; | |
| } | |
| static uintx_t clmul(uintx_t a, uintx_t b) { | |
| uintx_t result = 0; | |
| for (int bit = 0; bit < XLEN; bit++) { | |
| if (((b >> bit) & 1) != 0) { | |
| result ^= a << bit; | |
| } | |
| } | |
| return result; | |
| } | |
| static uintx_t clmulh(uintx_t a, uintx_t b) { | |
| uintx_t result = 0; | |
| for (int bit = 1; bit < XLEN; bit++) { | |
| if (((b >> bit) & 1) != 0) { | |
| result ^= a >> (XLEN - bit); | |
| } | |
| } | |
| return result; | |
| } | |
| static uintx_t clmulr(uintx_t a, uintx_t b) { | |
| uintx_t result = 0; | |
| for (int bit = 0; bit < XLEN; bit++) { | |
| if (((b >> bit) & 1) != 0) { | |
| result ^= a >> (XLEN - bit - 1); | |
| } | |
| } | |
| return result; | |
| } | |
| static uintx_t sextb(uintx_t a) { | |
| return static_cast<intx_t>(a << (XLEN - 8)) >> (XLEN - 8); | |
| } | |
| static uintx_t zextb(uintx_t a) { | |
| return a << (XLEN - 8) >> (XLEN - 8); | |
| } | |
| static uintx_t sexth(uintx_t a) { | |
| return static_cast<intx_t>(a << (XLEN - 16)) >> (XLEN - 16); | |
| } | |
| static uintx_t zexth(uintx_t a) { | |
| return a << (XLEN - 16) >> (XLEN - 16); | |
| } | |
| #if XLEN >= 64 | |
| static uintx_t zextw(uintx_t a) { | |
| return a << (XLEN - 32) >> (XLEN - 32); | |
| } | |
| #endif | |
| static uintx_t ror(uintx_t a, uintx_t b) { | |
| return std::rotr(a, b & (XLEN - 1)); | |
| } | |
| static uintx_t rol(uintx_t a, uintx_t b) { | |
| return std::rotl(a, b & (XLEN - 1)); | |
| } | |
| static uintx_t rorw(uintx_t a, uintx_t b) { | |
| return sign_extend(std::rotr(static_cast<uint32_t>(a), b & (XLEN - 1))); | |
| } | |
| static uintx_t rolw(uintx_t a, uintx_t b) { | |
| return sign_extend(std::rotl(static_cast<uint32_t>(a), b & (XLEN - 1))); | |
| } | |
| static uintx_t orcb(uintx_t a) { | |
| uintx_t result = 0; | |
| for (int shift = 0; shift < XLEN; shift += 8) { | |
| if (((a >> shift) & 0xFF) != 0) { | |
| result |= static_cast<uintx_t>(0xFF) << shift; | |
| } | |
| } | |
| return result; | |
| } | |
| static uintx_t rev8(uintx_t a) { | |
| uintx_t result = 0; | |
| for (int shift = 0; shift < XLEN; shift += 8) { | |
| result <<= 8; | |
| result |= (a >> shift) & 0xFF; | |
| } | |
| return result; | |
| } | |
| static uintx_t bclr(uintx_t a, uintx_t b) { | |
| return a & ~(static_cast<uintx_t>(1) << (b & (XLEN - 1))); | |
| } | |
| static uintx_t bext(uintx_t a, uintx_t b) { | |
| return (a >> (b & (XLEN - 1))) & 1; | |
| } | |
| static uintx_t binv(uintx_t a, uintx_t b) { | |
| return a ^ (static_cast<uintx_t>(1) << (b & (XLEN - 1))); | |
| } | |
| static uintx_t bset(uintx_t a, uintx_t b) { | |
| return a | (static_cast<uintx_t>(1) << (b & (XLEN - 1))); | |
| } | |
| DART_FORCE_INLINE | |
| 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)) != 0) { | |
| if ((instr.rs1() == ZR) && (instr.rs2() == ZR)) { | |
| Fault("EBREAK"); | |
| } else if (instr.rs2() == ZR) { | |
| // JALR | |
| uintx_t target = get_xreg(instr.rs1()); | |
| // Set RA first so that the profiler does not get confused by | |
| // observing the update to PC without the update to RA when we are | |
| // calling out of the entry stub, i.e., failing to indentify the entry | |
| // stub. On real hardware, the profiler's signal handler cannot | |
| // observe a partially execute JALR. | |
| set_xreg(RA, pc_ + instr.length()); | |
| pc_ = target; | |
| CheckLandingPad(instr.rs1()); | |
| 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()); | |
| CheckLandingPad(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)) { | |
| set_xreg(instr.rd(), sign_extend(instr.u_imm())); | |
| } else if (instr.encoding() == C_SSPUSH) { | |
| if (ss_enabled_) { | |
| uintx_t ra = get_xreg(Register(1)); | |
| ssp_ -= sizeof(uintx_t); | |
| // The shadow stack has special memory protections that only this | |
| // write can access. | |
| MemoryWrite<uintx_t>(ssp_, ra, ZR); | |
| } | |
| } else if (instr.encoding() == C_SSPOPCHK) { | |
| if (ss_enabled_) { | |
| uintx_t ra = MemoryRead<uintx_t>(ssp_, ZR); | |
| ssp_ += sizeof(uintx_t); | |
| if (ra != get_xreg(Register(5))) { | |
| Fault("Corrupt control flow"); | |
| } | |
| } | |
| } else if ((instr.encoding() & C_MOP_MASK) == C_MOP) { | |
| // May-be-op | |
| } else { | |
| IllegalInstruction(instr); | |
| } | |
| 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.rd() == ZR) { | |
| IllegalInstruction(instr); | |
| } else { | |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) << instr.shamt()); | |
| } | |
| 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.shamt() >= XLEN) { | |
| IllegalInstruction(instr); | |
| } else { | |
| set_xreg(instr.rs1p(), get_xreg(instr.rs1p()) >> instr.shamt()); | |
| } | |
| break; | |
| case C_SRAI: | |
| if (instr.shamt() >= XLEN) { | |
| IllegalInstruction(instr); | |
| } else { | |
| set_xreg( | |
| instr.rs1p(), | |
| static_cast<intx_t>(get_xreg(instr.rs1p())) >> instr.shamt()); | |
| } | |
| 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; | |
| } | |
| case C_MUL: | |
| set_xreg(instr.rs1p(), | |
| mul(get_xreg(instr.rs1p()), get_xreg(instr.rs2p()))); | |
| break; | |
| case C_EXT: | |
| switch (instr.encoding() & C_EXT_MASK) { | |
| case C_ZEXTB: | |
| set_xreg(instr.rs1p(), zextb(get_xreg(instr.rs1p()))); | |
| break; | |
| case C_SEXTB: | |
| set_xreg(instr.rs1p(), sextb(get_xreg(instr.rs1p()))); | |
| break; | |
| case C_ZEXTH: | |
| set_xreg(instr.rs1p(), zexth(get_xreg(instr.rs1p()))); | |
| break; | |
| case C_SEXTH: | |
| set_xreg(instr.rs1p(), sexth(get_xreg(instr.rs1p()))); | |
| break; | |
| #if XLEN >= 64 | |
| case C_ZEXTW: | |
| set_xreg(instr.rs1p(), zextw(get_xreg(instr.rs1p()))); | |
| break; | |
| #endif | |
| case C_NOT: | |
| set_xreg(instr.rs1p(), ~get_xreg(instr.rs1p())); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| break; | |
| case C_LBU: | |
| switch (instr.encoding() & 0b1111110000000011) { | |
| case C_LBU: { | |
| uintx_t addr = get_xreg(instr.rs1p()) + instr.mem1_imm(); | |
| set_xreg(instr.rdp(), MemoryRead<uint8_t>(addr, instr.rs1p())); | |
| break; | |
| } | |
| case C_LHU: { | |
| uintx_t addr = get_xreg(instr.rs1p()) + instr.mem2_imm(); | |
| if ((instr.encoding() & 0b1000000) == 0) { | |
| set_xreg(instr.rdp(), MemoryRead<uint16_t>(addr, instr.rs1p())); | |
| } else { | |
| set_xreg(instr.rdp(), MemoryRead<int16_t>(addr, instr.rs1p())); | |
| } | |
| break; | |
| } | |
| case C_SB: { | |
| uintx_t addr = get_xreg(instr.rs1p()) + instr.mem1_imm(); | |
| MemoryWrite<uint8_t>(addr, get_xreg(instr.rs2p()), instr.rs1p()); | |
| break; | |
| } | |
| case C_SH: { | |
| uintx_t addr = get_xreg(instr.rs1p()) + instr.mem2_imm(); | |
| MemoryWrite<uint16_t>(addr, get_xreg(instr.rs2p()), instr.rs1p()); | |
| break; | |
| } | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| DART_FORCE_INLINE | |
| void Simulator::InterpretLUI(Instr instr) { | |
| set_xreg(instr.rd(), sign_extend(instr.utype_imm())); | |
| pc_ += instr.length(); | |
| } | |
| DART_FORCE_INLINE | |
| void Simulator::InterpretAUIPC(Instr instr) { | |
| set_xreg(instr.rd(), pc_ + sign_extend(instr.utype_imm())); | |
| pc_ += instr.length(); | |
| } | |
| DART_FORCE_INLINE | |
| void Simulator::InterpretJAL(Instr instr) { | |
| set_xreg(instr.rd(), pc_ + instr.length()); | |
| pc_ += sign_extend(instr.jtype_imm()); | |
| } | |
| DART_FORCE_INLINE | |
| void Simulator::InterpretJALR(Instr instr) { | |
| uintx_t base = get_xreg(instr.rs1()); | |
| uintx_t offset = static_cast<uintx_t>(instr.itype_imm()); | |
| // Set RA first so that the profiler does not get confused by | |
| // observing the update to PC without the update to RA when we are | |
| // calling out of the entry stub, i.e., failing to indentify the entry | |
| // stub. On real hardware, the profiler's signal handler cannot | |
| // observe a partially execute JALR. | |
| set_xreg(instr.rd(), pc_ + instr.length()); | |
| pc_ = base + offset; | |
| CheckLandingPad(instr.rs1()); | |
| } | |
| DART_FORCE_INLINE | |
| 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); | |
| } | |
| } | |
| DART_FORCE_INLINE | |
| 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(); | |
| } | |
| DART_FORCE_INLINE | |
| 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; | |
| case E8: | |
| InterpretLOADV<uint8_t>(instr); | |
| break; | |
| case E16: | |
| InterpretLOADV<uint16_t>(instr); | |
| break; | |
| case E32: | |
| InterpretLOADV<uint32_t>(instr); | |
| break; | |
| case E64: | |
| InterpretLOADV<uint64_t>(instr); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| template <typename T> | |
| void Simulator::InterpretLOADV(Instr instr) { | |
| if ((instr.encoding() & 0xFFF00000) != 0x02000000) { | |
| UNIMPLEMENTED(); // Only unmasked unit-stride implemented. | |
| } | |
| uintx_t base = get_xreg(instr.rs1()); | |
| T* vd = ref_vreg<T>(instr.vd()); | |
| for (uintx_t i = 0; i < vl_; i++) { | |
| vd[i] = MemoryRead<T>(base + i * sizeof(T), instr.rs1()); | |
| } | |
| } | |
| DART_FORCE_INLINE | |
| 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(); | |
| } | |
| DART_FORCE_INLINE | |
| 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; | |
| case E8: | |
| InterpretSTOREV<uint8_t>(instr); | |
| break; | |
| case E16: | |
| InterpretSTOREV<uint16_t>(instr); | |
| break; | |
| case E32: | |
| InterpretSTOREV<uint32_t>(instr); | |
| break; | |
| case E64: | |
| InterpretSTOREV<uint64_t>(instr); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| template <typename T> | |
| void Simulator::InterpretSTOREV(Instr instr) { | |
| if ((instr.encoding() & 0xFFF00000) != 0x02000000) { | |
| UNIMPLEMENTED(); // Only unmasked unit-stride implemented. | |
| } | |
| uintx_t base = get_xreg(instr.rs1()); | |
| T* vs3 = ref_vreg<T>(instr.vs3()); | |
| for (uintx_t i = 0; i < vl_; i++) { | |
| MemoryWrite<T>(base + i * sizeof(T), vs3[i], instr.rs1()); | |
| } | |
| } | |
| DART_FORCE_INLINE | |
| 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: | |
| if (instr.funct7() == COUNT) { | |
| if (instr.shamt() == 0b00000) { | |
| set_xreg(instr.rd(), clz(get_xreg(instr.rs1()))); | |
| } else if (instr.shamt() == 0b00001) { | |
| set_xreg(instr.rd(), ctz(get_xreg(instr.rs1()))); | |
| } else if (instr.shamt() == 0b00010) { | |
| set_xreg(instr.rd(), cpop(get_xreg(instr.rs1()))); | |
| } else if (instr.shamt() == 0b00100) { | |
| set_xreg(instr.rd(), sextb(get_xreg(instr.rs1()))); | |
| } else if (instr.shamt() == 0b00101) { | |
| set_xreg(instr.rd(), sexth(get_xreg(instr.rs1()))); | |
| } else { | |
| IllegalInstruction(instr); | |
| } | |
| } else if ((instr.funct7() & 0b1111110) == BCLRBEXT) { | |
| set_xreg(instr.rd(), bclr(get_xreg(instr.rs1()), instr.shamt())); | |
| } else if ((instr.funct7() & 0b1111110) == BINV) { | |
| set_xreg(instr.rd(), binv(get_xreg(instr.rs1()), instr.shamt())); | |
| } else if ((instr.funct7() & 0b1111110) == BSET) { | |
| set_xreg(instr.rd(), bset(get_xreg(instr.rs1()), instr.shamt())); | |
| } else { | |
| 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 if ((instr.funct7() & 0b1111110) == ROTATE) { | |
| set_xreg(instr.rd(), ror(get_xreg(instr.rs1()), instr.shamt())); | |
| } else if (instr.funct7() == 0b0010100) { | |
| set_xreg(instr.rd(), orcb(get_xreg(instr.rs1()))); | |
| #if XLEN == 32 | |
| } else if (instr.funct7() == 0b0110100) { | |
| #else | |
| } else if (instr.funct7() == 0b0110101) { | |
| #endif | |
| set_xreg(instr.rd(), rev8(get_xreg(instr.rs1()))); | |
| } else if ((instr.funct7() & 0b1111110) == BCLRBEXT) { | |
| set_xreg(instr.rd(), bext(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(); | |
| } | |
| DART_FORCE_INLINE | |
| 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: { | |
| if (instr.funct7() == SLLIUW) { | |
| uintx_t a = static_cast<uint32_t>(get_xreg(instr.rs1())); | |
| uintx_t b = instr.shamt(); | |
| set_xreg(instr.rd(), a << b); | |
| } else if (instr.funct7() == COUNT) { | |
| if (instr.shamt() == 0b00000) { | |
| set_xreg(instr.rd(), clzw(get_xreg(instr.rs1()))); | |
| } else if (instr.shamt() == 0b00001) { | |
| set_xreg(instr.rd(), ctzw(get_xreg(instr.rs1()))); | |
| } else if (instr.shamt() == 0b00010) { | |
| set_xreg(instr.rd(), cpopw(get_xreg(instr.rs1()))); | |
| } else { | |
| IllegalInstruction(instr); | |
| } | |
| } else { | |
| 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 if (instr.funct7() == ROTATE) { | |
| set_xreg(instr.rd(), rorw(get_xreg(instr.rs1()), instr.shamt())); | |
| } 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(); | |
| } | |
| DART_FORCE_INLINE | |
| 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; | |
| case SHADD: | |
| InterpretOP_SHADD(instr); | |
| break; | |
| case MINMAXCLMUL: | |
| InterpretOP_MINMAXCLMUL(instr); | |
| break; | |
| case ROTATE: | |
| InterpretOP_ROTATE(instr); | |
| break; | |
| case BCLRBEXT: | |
| InterpretOP_BCLRBEXT(instr); | |
| break; | |
| case BINV: | |
| set_xreg(instr.rd(), binv(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| pc_ += instr.length(); | |
| break; | |
| case BSET: | |
| set_xreg(instr.rd(), bset(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| pc_ += instr.length(); | |
| break; | |
| #if XLEN == 32 | |
| case 0b0000100: | |
| set_xreg(instr.rd(), zexth(get_xreg(instr.rs1()))); | |
| pc_ += instr.length(); | |
| break; | |
| #endif | |
| case CZERO: | |
| InterpretOP_CZERO(instr); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| } | |
| DART_FORCE_INLINE | |
| 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(); | |
| } | |
| DART_FORCE_INLINE | |
| 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(); | |
| } | |
| DART_FORCE_INLINE | |
| 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; | |
| } | |
| case AND: | |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) & ~get_xreg(instr.rs2())); | |
| break; | |
| case OR: | |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) | ~get_xreg(instr.rs2())); | |
| break; | |
| case XOR: | |
| set_xreg(instr.rd(), get_xreg(instr.rs1()) ^ ~get_xreg(instr.rs2())); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| DART_FORCE_INLINE | |
| void Simulator::InterpretOP_SHADD(Instr instr) { | |
| switch (instr.funct3()) { | |
| case SH1ADD: | |
| set_xreg(instr.rd(), | |
| (get_xreg(instr.rs1()) << 1) + get_xreg(instr.rs2())); | |
| break; | |
| case SH2ADD: | |
| set_xreg(instr.rd(), | |
| (get_xreg(instr.rs1()) << 2) + get_xreg(instr.rs2())); | |
| break; | |
| case SH3ADD: | |
| set_xreg(instr.rd(), | |
| (get_xreg(instr.rs1()) << 3) + get_xreg(instr.rs2())); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| DART_FORCE_INLINE | |
| void Simulator::InterpretOP_MINMAXCLMUL(Instr instr) { | |
| switch (instr.funct3()) { | |
| case MAX: | |
| set_xreg(instr.rd(), max(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| break; | |
| case MAXU: | |
| set_xreg(instr.rd(), maxu(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| break; | |
| case MIN: | |
| set_xreg(instr.rd(), min(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| break; | |
| case MINU: | |
| set_xreg(instr.rd(), minu(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| break; | |
| case CLMUL: | |
| set_xreg(instr.rd(), clmul(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| break; | |
| case CLMULH: | |
| set_xreg(instr.rd(), | |
| clmulh(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| break; | |
| case CLMULR: | |
| set_xreg(instr.rd(), | |
| clmulr(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| DART_FORCE_INLINE | |
| void Simulator::InterpretOP_ROTATE(Instr instr) { | |
| switch (instr.funct3()) { | |
| case ROR: | |
| set_xreg(instr.rd(), ror(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| break; | |
| case ROL: | |
| set_xreg(instr.rd(), rol(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| DART_FORCE_INLINE | |
| void Simulator::InterpretOP_BCLRBEXT(Instr instr) { | |
| switch (instr.funct3()) { | |
| case BCLR: | |
| set_xreg(instr.rd(), bclr(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| break; | |
| case BEXT: | |
| set_xreg(instr.rd(), bext(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| DART_FORCE_INLINE | |
| void Simulator::InterpretOP_CZERO(Instr instr) { | |
| switch (instr.funct3()) { | |
| case CZEROEQZ: | |
| set_xreg(instr.rd(), | |
| get_xreg(instr.rs2()) == 0 ? 0 : get_xreg(instr.rs1())); | |
| break; | |
| case CZERONEZ: | |
| set_xreg(instr.rd(), | |
| get_xreg(instr.rs2()) != 0 ? 0 : get_xreg(instr.rs1())); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| DART_FORCE_INLINE | |
| 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; | |
| case SHADD: | |
| InterpretOP32_SHADD(instr); | |
| break; | |
| case ADDUW: | |
| InterpretOP32_ADDUW(instr); | |
| break; | |
| case ROTATE: | |
| InterpretOP32_ROTATE(instr); | |
| break; | |
| #endif // XLEN >= 64 | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| } | |
| DART_FORCE_INLINE | |
| 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(); | |
| } | |
| DART_FORCE_INLINE | |
| 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(); | |
| } | |
| DART_FORCE_INLINE | |
| 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(); | |
| } | |
| DART_FORCE_INLINE | |
| void Simulator::InterpretOP32_SHADD(Instr instr) { | |
| switch (instr.funct3()) { | |
| case SH1ADD: { | |
| uintx_t a = static_cast<uint32_t>(get_xreg(instr.rs1())); | |
| uintx_t b = get_xreg(instr.rs2()); | |
| set_xreg(instr.rd(), (a << 1) + b); | |
| break; | |
| } | |
| case SH2ADD: { | |
| uintx_t a = static_cast<uint32_t>(get_xreg(instr.rs1())); | |
| uintx_t b = get_xreg(instr.rs2()); | |
| set_xreg(instr.rd(), (a << 2) + b); | |
| break; | |
| } | |
| case SH3ADD: { | |
| uintx_t a = static_cast<uint32_t>(get_xreg(instr.rs1())); | |
| uintx_t b = get_xreg(instr.rs2()); | |
| set_xreg(instr.rd(), (a << 3) + b); | |
| break; | |
| } | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| DART_FORCE_INLINE | |
| void Simulator::InterpretOP32_ADDUW(Instr instr) { | |
| switch (instr.funct3()) { | |
| #if XLEN >= 64 | |
| case F3_0: { | |
| uintx_t a = static_cast<uint32_t>(get_xreg(instr.rs1())); | |
| uintx_t b = get_xreg(instr.rs2()); | |
| set_xreg(instr.rd(), a + b); | |
| break; | |
| } | |
| case ZEXT: | |
| set_xreg(instr.rd(), zexth(get_xreg(instr.rs1()))); | |
| break; | |
| #endif // XLEN >= 64 | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| DART_FORCE_INLINE | |
| void Simulator::InterpretOP32_ROTATE(Instr instr) { | |
| switch (instr.funct3()) { | |
| case ROR: | |
| set_xreg(instr.rd(), rorw(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| break; | |
| case ROL: | |
| set_xreg(instr.rd(), rolw(get_xreg(instr.rs1()), get_xreg(instr.rs2()))); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| void Simulator::InterpretMISCMEM(Instr instr) { | |
| switch (instr.funct3()) { | |
| case FENCE: | |
| memory_.FlushAll(); | |
| #if defined(__GNUC__) && !defined(__clang__) | |
| #pragma GCC diagnostic ignored "-Wtsan" | |
| #endif | |
| 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 PRIV: | |
| switch (instr.funct12()) { | |
| case ECALL: | |
| InterpretECALL(instr); | |
| return; | |
| case EBREAK: | |
| Fault("EBREAK"); | |
| break; | |
| case WRS_NTO: | |
| case WRS_STO: | |
| // Could issue mwait on host? | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| break; | |
| case MOP: { | |
| if ((instr.funct7() == SSPUSH) && (instr.rd() == ZR) && | |
| (instr.rs1() == ZR) && | |
| ((instr.rs2() == Register(1)) || (instr.rs2() == Register(5)))) { | |
| if (ss_enabled_) { | |
| uintx_t ra = get_xreg(instr.rs2()); | |
| ssp_ -= sizeof(uintx_t); | |
| // The shadow stack has special memory protections that only this | |
| // write can access. | |
| MemoryWrite<uintx_t>(ssp_, ra, ZR); | |
| } | |
| } else if ((instr.funct12() == SSPOPCHK) && (instr.rd() == ZR) && | |
| ((instr.rs1() == Register(1)) || | |
| (instr.rs1() == Register(5)))) { | |
| if (ss_enabled_) { | |
| uintx_t ra = MemoryRead<uintx_t>(ssp_, ZR); | |
| ssp_ += sizeof(uintx_t); | |
| if (ra != get_xreg(instr.rs1())) { | |
| Fault("Corrupt control flow"); | |
| } | |
| } | |
| } else if ((instr.funct12() == SSRDP) && (instr.rs1() == ZR)) { | |
| set_xreg(instr.rd(), ss_enabled_ ? ssp_ : 0); | |
| } else if ((instr.funct12() & MOP_R_MASK) == MOP_R) { | |
| set_xreg(instr.rd(), 0); | |
| } else if ((instr.funct7() & MOP_RR_MASK) == MOP_RR) { | |
| set_xreg(instr.rd(), 0); | |
| } else { | |
| 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()) { | |
| Object& obj = Object::Handle( | |
| static_cast<ObjectPtr>(static_cast<uword>(get_xreg(instr.rs1())))); | |
| THR_Print("%" Px ": %s = %s\n", static_cast<uword>(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)) { | |
| Fault("Stack misaligned at call to C function"); | |
| } | |
| // We can't instrument the runtime. | |
| memory_.FlushAll(); | |
| SimulatorSetjmpBuffer buffer(this); | |
| if (!DART_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%" Px "\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 WIDTH8: | |
| InterpretAMO8(instr); | |
| break; | |
| case WIDTH16: | |
| InterpretAMO16(instr); | |
| break; | |
| case WIDTH32: | |
| InterpretAMO32(instr); | |
| break; | |
| case WIDTH64: | |
| InterpretAMO64(instr); | |
| break; | |
| case WIDTH128: | |
| InterpretAMO128(instr); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| // 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) { | |
| Fault("Misaligned atomic memory operation"); | |
| } | |
| reserved_address_ = addr; | |
| reserved_value_ = memory_.Load<type>(addr, 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) { | |
| Fault("Misaligned atomic memory operation"); | |
| } | |
| if (addr != reserved_address_) { | |
| reserved_address_ = 0; | |
| set_xreg(instr.rd(), 1); | |
| return; | |
| } | |
| if ((random_.NextUInt32() % 16) == 0) { // Spurious failure. | |
| reserved_address_ = 0; | |
| set_xreg(instr.rd(), 1); | |
| return; | |
| } | |
| type expected = reserved_value_; | |
| type desired = get_xreg(instr.rs2()); | |
| bool success = | |
| memory_.CompareExchange(addr, expected, desired, instr.memory_order()); | |
| set_xreg(instr.rd(), success ? 0 : 1); | |
| reserved_address_ = 0; | |
| } | |
| template <typename type> | |
| void Simulator::InterpretAMOSWAP(Instr instr) { | |
| uintx_t addr = get_xreg(instr.rs1()); | |
| if ((addr & (sizeof(type) - 1)) != 0) { | |
| Fault("Misaligned atomic memory operation"); | |
| } | |
| memory_.FlushAddress(addr); | |
| std::atomic_ref<type> atomic(*reinterpret_cast<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) { | |
| Fault("Misaligned atomic memory operation"); | |
| } | |
| memory_.FlushAddress(addr); | |
| std::atomic_ref<type> atomic(*reinterpret_cast<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) { | |
| Fault("Misaligned atomic memory operation"); | |
| } | |
| memory_.FlushAddress(addr); | |
| std::atomic_ref<type> atomic(*reinterpret_cast<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) { | |
| Fault("Misaligned atomic memory operation"); | |
| } | |
| memory_.FlushAddress(addr); | |
| std::atomic_ref<type> atomic(*reinterpret_cast<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) { | |
| Fault("Misaligned atomic memory operation"); | |
| } | |
| memory_.FlushAddress(addr); | |
| std::atomic_ref<type> atomic(*reinterpret_cast<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) { | |
| Fault("Misaligned atomic memory operation"); | |
| } | |
| memory_.FlushAddress(addr); | |
| // TODO(c++26): fetch_max | |
| std::atomic_ref<type> atomic(*reinterpret_cast<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) { | |
| Fault("Misaligned atomic memory operation"); | |
| } | |
| memory_.FlushAddress(addr); | |
| // TODO(c++26): fetch_max | |
| std::atomic_ref<type> atomic(*reinterpret_cast<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::InterpretLOADORDERED(Instr instr) { | |
| uintx_t addr = get_xreg(instr.rs1()); | |
| if ((addr & (sizeof(type) - 1)) != 0) { | |
| Fault("Misaligned atomic memory operation"); | |
| } | |
| memory_.FlushAddress(addr); | |
| std::atomic_ref<type> atomic(*reinterpret_cast<type*>(addr)); | |
| type value = atomic.load(instr.memory_order()); | |
| set_xreg(instr.rd(), sign_extend(value)); | |
| } | |
| template <typename type> | |
| void Simulator::InterpretSTOREORDERED(Instr instr) { | |
| uintx_t addr = get_xreg(instr.rs1()); | |
| if ((addr & (sizeof(type) - 1)) != 0) { | |
| Fault("Misaligned atomic memory operation"); | |
| } | |
| memory_.FlushAddress(addr); | |
| type value = get_xreg(instr.rs2()); | |
| std::atomic_ref<type> atomic(*reinterpret_cast<type*>(addr)); | |
| atomic.store(value, instr.memory_order()); | |
| } | |
| template <typename type> | |
| void Simulator::InterpretAMOCAS(Instr instr) { | |
| uintx_t addr = get_xreg(instr.rs1()); | |
| if ((addr & (sizeof(type) - 1)) != 0) { | |
| Fault("Misaligned atomic memory operation"); | |
| } | |
| type expected = get_xreg(instr.rd()); | |
| type desired = get_xreg(instr.rs2()); | |
| std::atomic_ref<type> atomic(*reinterpret_cast<type*>(addr)); | |
| atomic.compare_exchange_weak(expected, desired, instr.memory_order()); | |
| set_xreg(instr.rd(), expected); | |
| } | |
| void Simulator::InterpretAMO8(Instr instr) { | |
| switch (instr.funct5()) { | |
| case AMOSWAP: | |
| InterpretAMOSWAP<int8_t>(instr); | |
| break; | |
| case AMOADD: | |
| InterpretAMOADD<int8_t>(instr); | |
| break; | |
| case AMOXOR: | |
| InterpretAMOXOR<int8_t>(instr); | |
| break; | |
| case AMOAND: | |
| InterpretAMOAND<int8_t>(instr); | |
| break; | |
| case AMOOR: | |
| InterpretAMOOR<int8_t>(instr); | |
| break; | |
| case AMOMIN: | |
| InterpretAMOMIN<int8_t>(instr); | |
| break; | |
| case AMOMAX: | |
| InterpretAMOMAX<int8_t>(instr); | |
| break; | |
| case AMOMINU: | |
| InterpretAMOMIN<uint8_t>(instr); | |
| break; | |
| case AMOMAXU: | |
| InterpretAMOMAX<uint8_t>(instr); | |
| break; | |
| case LOADORDERED: | |
| InterpretLOADORDERED<int8_t>(instr); | |
| break; | |
| case STOREORDERED: | |
| InterpretSTOREORDERED<int8_t>(instr); | |
| break; | |
| case AMOCAS: | |
| InterpretAMOCAS<int8_t>(instr); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| } | |
| void Simulator::InterpretAMO16(Instr instr) { | |
| switch (instr.funct5()) { | |
| case AMOSWAP: | |
| InterpretAMOSWAP<int16_t>(instr); | |
| break; | |
| case AMOADD: | |
| InterpretAMOADD<int16_t>(instr); | |
| break; | |
| case AMOXOR: | |
| InterpretAMOXOR<int16_t>(instr); | |
| break; | |
| case AMOAND: | |
| InterpretAMOAND<int16_t>(instr); | |
| break; | |
| case AMOOR: | |
| InterpretAMOOR<int16_t>(instr); | |
| break; | |
| case AMOMIN: | |
| InterpretAMOMIN<int16_t>(instr); | |
| break; | |
| case AMOMAX: | |
| InterpretAMOMAX<int16_t>(instr); | |
| break; | |
| case AMOMINU: | |
| InterpretAMOMIN<uint16_t>(instr); | |
| break; | |
| case AMOMAXU: | |
| InterpretAMOMAX<uint16_t>(instr); | |
| break; | |
| case LOADORDERED: | |
| InterpretLOADORDERED<int16_t>(instr); | |
| break; | |
| case STOREORDERED: | |
| InterpretSTOREORDERED<int16_t>(instr); | |
| break; | |
| case AMOCAS: | |
| InterpretAMOCAS<int16_t>(instr); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| } | |
| 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; | |
| case LOADORDERED: | |
| InterpretLOADORDERED<int32_t>(instr); | |
| break; | |
| case STOREORDERED: | |
| InterpretSTOREORDERED<int32_t>(instr); | |
| break; | |
| case AMOCAS: | |
| InterpretAMOCAS<int32_t>(instr); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| } | |
| 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; | |
| case LOADORDERED: | |
| InterpretLOADORDERED<int64_t>(instr); | |
| break; | |
| case STOREORDERED: | |
| InterpretSTOREORDERED<int64_t>(instr); | |
| break; | |
| case AMOCAS: | |
| InterpretAMOCAS<int64_t>(instr); | |
| break; | |
| #endif // XLEN >= 64 | |
| #if XLEN == 32 | |
| case AMOCAS: | |
| UNIMPLEMENTED(); // Need double-wide CAS from host. | |
| break; | |
| #endif | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| } | |
| void Simulator::InterpretAMO128(Instr instr) { | |
| switch (instr.funct5()) { | |
| #if XLEN >= 64 | |
| case AMOCAS: | |
| UNIMPLEMENTED(); // Need double-wide CAS from host. | |
| break; | |
| #endif // XLEN >= 64 | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| } | |
| 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(), std::fma(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(), std::fma(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(), std::fma(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(), std::fma(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(), -std::fma(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(), -std::fma(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(), -std::fma(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(), -std::fma(rs1, rs2, rs3)); | |
| break; | |
| } | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| // "For the purposes of these instructions only, the value −0.0 is considered to | |
| // be less than the value +0.0. If both inputs are NaNs, the result is the | |
| // canonical NaN. If only one operand is a NaN, the result is the non-NaN | |
| // operand." | |
| static double rv_fmin(double x, double y) { | |
| if (isnan(x) && isnan(y)) return std::numeric_limits<double>::quiet_NaN(); | |
| if (isnan(x)) return y; | |
| if (isnan(y)) return x; | |
| if (x == y) return signbit(x) ? x : y; | |
| return fmin(x, y); | |
| } | |
| static double rv_fmax(double x, double y) { | |
| if (isnan(x) && isnan(y)) return std::numeric_limits<double>::quiet_NaN(); | |
| if (isnan(x)) return y; | |
| if (isnan(y)) return x; | |
| if (x == y) return signbit(x) ? y : x; | |
| return fmax(x, y); | |
| } | |
| static float rv_fminf(float x, float y) { | |
| if (isnan(x) && isnan(y)) return std::numeric_limits<float>::quiet_NaN(); | |
| if (isnan(x)) return y; | |
| if (isnan(y)) return x; | |
| if (x == y) return signbit(x) ? x : y; | |
| return fminf(x, y); | |
| } | |
| static float rv_fmaxf(float x, float y) { | |
| if (isnan(x) && isnan(y)) return std::numeric_limits<float>::quiet_NaN(); | |
| if (isnan(x)) return y; | |
| if (isnan(y)) return x; | |
| if (x == y) return signbit(x) ? y : x; | |
| return fmaxf(x, y); | |
| } | |
| // "The FMINM.S and FMAXM.S instructions are defined like the FMIN.S and FMAX.S | |
| // instructions, except that if either input is NaN, the result is the | |
| // canonical NaN." | |
| static double rv_fminm(double x, double y) { | |
| if (isnan(x) || isnan(y)) return std::numeric_limits<double>::quiet_NaN(); | |
| if (x == y) return signbit(x) ? x : y; | |
| return fmin(x, y); | |
| } | |
| static double rv_fmaxm(double x, double y) { | |
| if (isnan(x) || isnan(y)) return std::numeric_limits<double>::quiet_NaN(); | |
| if (x == y) return signbit(x) ? y : x; | |
| return fmax(x, y); | |
| } | |
| static float rv_fminmf(float x, float y) { | |
| if (isnan(x) || isnan(y)) return std::numeric_limits<float>::quiet_NaN(); | |
| if (x == y) return signbit(x) ? x : y; | |
| return fminf(x, y); | |
| } | |
| static float rv_fmaxmf(float x, float y) { | |
| if (isnan(x) || isnan(y)) return std::numeric_limits<float>::quiet_NaN(); | |
| if (x == y) return signbit(x) ? y : x; | |
| return fmaxf(x, y); | |
| } | |
| 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 (fpclassify(x)) { | |
| case FP_INFINITE: | |
| case FP_ZERO: | |
| return x; | |
| case FP_NAN: | |
| return std::numeric_limits<float>::quiet_NaN(); | |
| } | |
| 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 (fpclassify(x)) { | |
| case FP_INFINITE: | |
| case FP_ZERO: | |
| return x; | |
| case FP_NAN: | |
| return std::numeric_limits<double>::quiet_NaN(); | |
| } | |
| 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 int32_t fcvtmodwd(double x, RoundingMode rounding) { | |
| ASSERT(rounding == RTZ); | |
| switch (fpclassify(x)) { | |
| case FP_INFINITE: | |
| case FP_NAN: | |
| case FP_ZERO: | |
| case FP_SUBNORMAL: | |
| return 0; | |
| } | |
| int biased_exp = (bit_cast<uint64_t>(x) & 0x7FF0000000000000) >> 52; | |
| int exponent = biased_exp - 0x3FF - 52; | |
| int64_t significand = bit_cast<uint64_t>(x) & 0x000FFFFFFFFFFFFF; | |
| significand += 0x0010000000000000; | |
| if (x < 0) { | |
| significand = -significand; | |
| } | |
| if (exponent >= 0) { | |
| if (exponent >= 64) { | |
| return 0; | |
| } | |
| return significand << exponent; | |
| } | |
| if (exponent <= -64) { | |
| return significand >> 63; | |
| } | |
| return significand >> -exponent; | |
| } | |
| 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 FMIN: | |
| set_fregs(instr.frd(), rv_fminf(rs1, rs2)); | |
| break; | |
| case FMAX: | |
| set_fregs(instr.frd(), rv_fmaxf(rs1, rs2)); | |
| break; | |
| case FMINM: | |
| set_fregs(instr.frd(), rv_fminmf(rs1, rs2)); | |
| break; | |
| case FMAXM: | |
| set_fregs(instr.frd(), rv_fmaxmf(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: | |
| case FLTQ: | |
| set_xreg(instr.rd(), rs1 < rs2 ? 1 : 0); | |
| break; | |
| case FLE: | |
| case FLEQ: | |
| 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_raw(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: | |
| switch (static_cast<int>(instr.frs2())) { | |
| case 0: | |
| set_fregs( | |
| instr.frd(), | |
| bit_cast<float>(static_cast<int32_t>(get_xreg(instr.rs1())))); | |
| break; | |
| case 1: | |
| set_fregs(instr.frd(), bit_cast<float>(kFlisConstants[instr.rs1()])); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| 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 FMIN: | |
| set_fregd(instr.frd(), rv_fmin(rs1, rs2)); | |
| break; | |
| case FMAX: | |
| set_fregd(instr.frd(), rv_fmax(rs1, rs2)); | |
| break; | |
| case FMINM: | |
| set_fregd(instr.frd(), rv_fminm(rs1, rs2)); | |
| break; | |
| case FMAXM: | |
| set_fregd(instr.frd(), rv_fmaxm(rs1, rs2)); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| break; | |
| } | |
| case FCVTS: { | |
| switch (static_cast<int>(instr.rs2())) { | |
| case 1: | |
| set_fregs(instr.frd(), static_cast<float>(get_fregd(instr.frs1()))); | |
| break; | |
| case 4: | |
| case 5: | |
| set_fregs(instr.frd(), | |
| Round(get_fregs(instr.frs1()), instr.rounding())); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| break; | |
| } | |
| case FCVTD: { | |
| switch (static_cast<int>(instr.rs2())) { | |
| case 0: | |
| set_fregd(instr.frd(), static_cast<double>(get_fregs(instr.frs1()))); | |
| break; | |
| case 4: | |
| case 5: | |
| set_fregd(instr.frd(), | |
| Round(get_fregd(instr.frs1()), instr.rounding())); | |
| 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: | |
| case FLTQ: | |
| set_xreg(instr.rd(), rs1 < rs2 ? 1 : 0); | |
| break; | |
| case FLE: | |
| case FLEQ: | |
| 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; | |
| case 0: | |
| switch (static_cast<int>(instr.rs2())) { | |
| #if XLEN >= 64 | |
| case 0: | |
| // fmv.x.d | |
| set_xreg(instr.rd(), bit_cast<int64_t>(get_fregd(instr.frs1()))); | |
| break; | |
| #endif // XLEN >= 64 | |
| #if XLEN == 32 | |
| case 1: | |
| // fmvh.x.d | |
| set_xreg(instr.rd(), | |
| bit_cast<uint64_t>(get_fregd(instr.frs1())) >> 32); | |
| break; | |
| #endif // XLEN == 32 | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| break; | |
| case FCVTintD: | |
| switch (static_cast<int>(instr.rs2())) { | |
| case W: | |
| set_xreg(instr.rd(), sign_extend(fcvtwd(get_fregd(instr.frs1()), | |
| instr.rounding()))); | |
| break; | |
| case 8: | |
| set_xreg(instr.rd(), sign_extend(fcvtmodwd(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; | |
| case FMVDX: | |
| switch (static_cast<int>(instr.frs2())) { | |
| #if XLEN >= 64 | |
| case 0: | |
| set_fregd(instr.frd(), bit_cast<double>(get_xreg(instr.rs1()))); | |
| break; | |
| #endif // XLEN >= 64 | |
| case 1: | |
| set_fregd(instr.frd(), bit_cast<double>(kFlidConstants[instr.rs1()])); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| break; | |
| #if XLEN == 32 | |
| case FMVPDX: { | |
| uint64_t hi = static_cast<uint32_t>(get_xreg(instr.rs2())); | |
| uint64_t lo = static_cast<uint32_t>(get_xreg(instr.rs1())); | |
| set_fregd(instr.frd(), bit_cast<double>((hi << 32) | lo)); | |
| break; | |
| } | |
| #endif | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| void Simulator::InterpretOPV(Instr instr) { | |
| switch (instr.funct3()) { | |
| case OPIVV: | |
| InterpretOPV_IVV(instr); | |
| break; | |
| case OPFVV: | |
| InterpretOPV_FVV(instr); | |
| break; | |
| case OPMVV: | |
| InterpretOPV_MVV(instr); | |
| break; | |
| case OPIVI: | |
| InterpretOPV_IVI(instr); | |
| break; | |
| case OPIVX: | |
| InterpretOPV_IVX(instr); | |
| break; | |
| case OPFVF: | |
| InterpretOPV_FVF(instr); | |
| break; | |
| case OPMVX: | |
| InterpretOPV_MVX(instr); | |
| break; | |
| case OPCFG: | |
| InterpretOPV_CFG(instr); | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| pc_ += instr.length(); | |
| } | |
| void Simulator::InterpretOPV_IVV(Instr instr) { | |
| IllegalInstruction(instr); | |
| } | |
| void Simulator::InterpretOPV_FVV(Instr instr) { | |
| IllegalInstruction(instr); | |
| } | |
| void Simulator::InterpretOPV_MVV(Instr instr) { | |
| IllegalInstruction(instr); | |
| } | |
| void Simulator::InterpretOPV_IVI(Instr instr) { | |
| IllegalInstruction(instr); | |
| } | |
| void Simulator::InterpretOPV_IVX(Instr instr) { | |
| switch (vsew()) { | |
| case e8: | |
| InterpretOPV_IVX<uint8_t>(instr); | |
| break; | |
| case e16: | |
| InterpretOPV_IVX<uint16_t>(instr); | |
| break; | |
| case e32: | |
| InterpretOPV_IVX<uint32_t>(instr); | |
| break; | |
| case e64: | |
| InterpretOPV_IVX<uint64_t>(instr); | |
| break; | |
| default: | |
| Fault("Invalid SEW"); | |
| } | |
| } | |
| template <typename sew_t> | |
| void Simulator::InterpretOPV_IVX(Instr instr) { | |
| if (instr.vm()) UNIMPLEMENTED(); | |
| sew_t rs1 = get_xreg(instr.rs1()); | |
| sew_t* vs2 = ref_vreg<sew_t>(instr.vs2()); | |
| sew_t* vd = ref_vreg<sew_t>(instr.vd()); | |
| switch (instr.funct6()) { | |
| case VADD: | |
| for (uintx_t i = 0, n = vl_; i < n; i++) { | |
| vd[i] = rs1 + vs2[i]; | |
| } | |
| break; | |
| case VMV: | |
| for (uintx_t i = 0, n = vl_; i < n; i++) { | |
| vd[i] = rs1; | |
| } | |
| break; | |
| default: | |
| IllegalInstruction(instr); | |
| } | |
| } | |
| void Simulator::InterpretOPV_FVF(Instr instr) { | |
| IllegalInstruction(instr); | |
| } | |
| void Simulator::InterpretOPV_MVX(Instr instr) { | |
| IllegalInstruction(instr); | |
| } | |
| void Simulator::InterpretOPV_CFG(Instr instr) { | |
| if ((instr.encoding() & 0x80000000) == 0) { | |
| uintx_t avl = get_xreg(instr.rs1()); // In elements. | |
| intx_t vtype = instr.itype_imm(); | |
| uintx_t sew; | |
| switch ((vtype >> 3) & 0b111) { | |
| case e8: | |
| sew = 8; | |
| break; | |
| case e16: | |
| sew = 16; | |
| break; | |
| case e32: | |
| sew = 32; | |
| break; | |
| case e64: | |
| sew = 64; | |
| break; | |
| default: | |
| Fault("Invalid SEW"); | |
| } | |
| intx_t lmul; | |
| switch ((vtype >> 0) & 0b111) { | |
| case mf8: | |
| lmul = -8; | |
| break; | |
| case mf4: | |
| lmul = -4; | |
| break; | |
| case mf2: | |
| lmul = -2; | |
| break; | |
| case m1: | |
| lmul = 1; | |
| break; | |
| case m2: | |
| lmul = 2; | |
| break; | |
| case m4: | |
| lmul = 4; | |
| break; | |
| case m8: | |
| lmul = 8; | |
| break; | |
| default: | |
| Fault("Invalid LMUL"); | |
| } | |
| uintx_t vlmax; | |
| if (lmul < 0) { | |
| vlmax = VLEN / sew / -lmul; | |
| } else { | |
| vlmax = VLEN / sew * lmul; | |
| } | |
| if (instr.rs1() == ZR && instr.rd() != ZR) { | |
| vl_ = vlmax; | |
| } else if (instr.rs1() == ZR) { | |
| // Keep existing vl. | |
| } else if (avl < vlmax) { | |
| vl_ = avl; | |
| } else { | |
| vl_ = vlmax; | |
| } | |
| vtype_ = vtype; | |
| set_xreg(instr.rd(), vl_); | |
| } else { | |
| IllegalInstruction(instr); | |
| } | |
| } | |
| 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()); | |
| } | |
| void Simulator::Fault(const char* message) { | |
| PrintRegisters(); | |
| PrintStack(); | |
| FATAL("%s", message); | |
| } | |
| void Simulator::CheckLandingPad(Register rs1) { | |
| if (!lp_enabled_) return; | |
| if ((rs1 == Register(1)) || (rs1 == Register(5)) || (rs1 == Register(7))) { | |
| return; | |
| } | |
| if ((pc_ & 3) != 0) { | |
| Fault("Landing pad misaligned"); | |
| } | |
| Instr instr(*reinterpret_cast<uint32_t*>(pc_)); | |
| if ((instr.opcode() != AUIPC) || (instr.rd() != ZR)) { | |
| Fault("Landing pad expected"); | |
| } | |
| uintx_t label = instr.utype_imm(); | |
| if ((label != 0) && ((label ^ get_xreg(Register(7))) & 0xFFFFF000) != 0) { | |
| Fault("Landing pad mismatch"); | |
| } | |
| } | |
| 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))) { | |
| Fault("Out-of-bounds stack access"); | |
| } | |
| } else { | |
| const uintx_t kPageSize = 16 * KB; | |
| if ((addr < kPageSize) || (addr + sizeof(type) >= ~kPageSize)) { | |
| Fault("Bad memory access"); | |
| } | |
| } | |
| #endif | |
| return memory_.Load<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))) { | |
| Fault("Out-of-bounds stack access"); | |
| } | |
| } else { | |
| const uintx_t kPageSize = 16 * KB; | |
| if ((addr < kPageSize) || (addr + sizeof(type) >= ~kPageSize)) { | |
| PrintRegisters(); | |
| PrintStack(); | |
| Fault("Bad memory access"); | |
| } | |
| } | |
| #endif | |
| memory_.Store<type>(addr, value); | |
| } | |
| enum ControlStatusRegister { | |
| // URW | |
| fflags = 0x001, | |
| frm = 0x002, | |
| fcsr = 0x003, | |
| vstart = 0x008, | |
| vxsat = 0x009, | |
| vxrm = 0x00A, | |
| vcsr = 0x00F, | |
| ssp = 0x011, | |
| // URO | |
| cycle = 0xC00, | |
| time = 0xC01, | |
| instret = 0xC02, | |
| vl = 0xC20, | |
| vtype = 0xC21, | |
| vlenb = 0xC22, | |
| #if XLEN == 32 | |
| cycleh = 0xC80, | |
| timeh = 0xC81, | |
| instreth = 0xC82, | |
| #endif | |
| }; | |
| intx_t Simulator::CSRRead(uint16_t csr) { | |
| switch (csr) { | |
| case fcsr: | |
| return fcsr_; | |
| case ssp: | |
| return ssp_; | |
| case cycle: | |
| return instret_ / 2; | |
| case time: | |
| return 0; | |
| case instret: | |
| return instret_; | |
| case vl: | |
| return vl_; | |
| case vtype: | |
| return vtype_; | |
| case vlenb: | |
| return VLEN / 8; | |
| #if XLEN == 32 | |
| case cycleh: | |
| return (instret_ / 2) >> 32; | |
| case timeh: | |
| return 0; | |
| case instreth: | |
| return instret_ >> 32; | |
| #endif | |
| default: | |
| Fault("Unknown CSR"); | |
| } | |
| } | |
| void Simulator::CSRWrite(uint16_t csr, intx_t value) { | |
| switch (csr) { | |
| case ssp: | |
| ssp_ = value; | |
| break; | |
| default: | |
| 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(DART_INCLUDE_SIMULATOR) | |
| #endif // defined TARGET_ARCH_RISCV |