// Copyright (c) 2014, 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 // NOLINT #include #include "vm/globals.h" #if defined(TARGET_ARCH_ARM64) // Only build the simulator if not compiling for real ARM hardware. #if defined(USING_SIMULATOR) #include "vm/simulator.h" #include "vm/compiler/assembler/disassembler.h" #include "vm/constants.h" #include "vm/image_snapshot.h" #include "vm/native_arguments.h" #include "vm/os_thread.h" #include "vm/stack_frame.h" namespace dart { // constants_arm64.h does not define LR constant to prevent accidental direct // use of it during code generation. However using LR directly is okay in this // file because it is a simulator. constexpr Register LR = LR_DO_NOT_USE_DIRECTLY; 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_allow_unaligned_accesses, true, "Allow unaligned accesses to Normal memory."); // This macro provides a platform independent use of sscanf. The reason for // SScanF not being implemented in a platform independent way through // OS in the same way as SNPrint is that the Windows C Run-Time // Library does not provide vsscanf. #define SScanF sscanf // NOLINT // SimulatorSetjmpBuffer are linked together, and the last created one // is referenced by the Simulator. When an exception is thrown, the exception // runtime looks at where to jump and finds the corresponding // SimulatorSetjmpBuffer based on the stack pointer of the exception handler. // The runtime then does a Longjmp on that buffer to return to the simulator. class SimulatorSetjmpBuffer { public: void Longjmp() { // "This" is now the last setjmp buffer. simulator_->set_last_setjmp_buffer(this); longjmp(buffer_, 1); } explicit SimulatorSetjmpBuffer(Simulator* sim) { simulator_ = sim; link_ = sim->last_setjmp_buffer(); sim->set_last_setjmp_buffer(this); sp_ = static_cast(sim->get_register(R31, R31IsSP)); } ~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; }; // The SimulatorDebugger class is used by the simulator while debugging // simulated ARM64 code. class SimulatorDebugger { public: explicit SimulatorDebugger(Simulator* sim); ~SimulatorDebugger(); void Stop(Instr* instr, const char* message); void Debug(); char* ReadLine(const char* prompt); private: Simulator* sim_; bool GetValue(char* desc, uint64_t* value); bool GetSValue(char* desc, uint32_t* value); bool GetDValue(char* desc, uint64_t* value); bool GetQValue(char* desc, simd_value_t* value); static TokenPosition GetApproximateTokenIndex(const Code& code, uword pc); static void PrintDartFrame(uword vm_instructions, uword isolate_instructions, uword pc, uword fp, uword sp, const Function& function, TokenPosition token_pos, bool is_optimized, bool is_inlined); void PrintBacktrace(); // Set or delete a breakpoint. Returns true if successful. bool SetBreakpoint(Instr* breakpc); bool DeleteBreakpoint(Instr* breakpc); // Undo and redo all breakpoints. This is needed to bracket disassembly and // execution to skip past breakpoints when run from the debugger. void UndoBreakpoints(); void RedoBreakpoints(); }; SimulatorDebugger::SimulatorDebugger(Simulator* sim) { sim_ = sim; } SimulatorDebugger::~SimulatorDebugger() {} void SimulatorDebugger::Stop(Instr* instr, const char* message) { OS::PrintErr("Simulator hit %s\n", message); Debug(); } static Register LookupCpuRegisterByName(const char* name) { static const char* kNames[] = { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", "r30", "ip0", "ip1", "pp", "fp", "lr", "sp", "zr", }; static const Register kRegisters[] = { R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15, R16, R17, R18, R19, R20, R21, R22, R23, R24, R25, R26, R27, R28, R29, R30, IP0, IP1, PP, FP, LR, R31, ZR, }; ASSERT(ARRAY_SIZE(kNames) == ARRAY_SIZE(kRegisters)); for (unsigned i = 0; i < ARRAY_SIZE(kNames); i++) { if (strcmp(kNames[i], name) == 0) { return kRegisters[i]; } } return kNoRegister; } static VRegister LookupVRegisterByName(const char* name) { int reg_nr = -1; bool ok = SScanF(name, "v%d", ®_nr); if (ok && (0 <= reg_nr) && (reg_nr < kNumberOfVRegisters)) { return static_cast(reg_nr); } return kNoVRegister; } bool SimulatorDebugger::GetValue(char* desc, uint64_t* value) { Register reg = LookupCpuRegisterByName(desc); if (reg != kNoRegister) { if (reg == ZR) { *value = 0; return true; } *value = sim_->get_register(reg); return true; } if (desc[0] == '*') { uint64_t addr; if (GetValue(desc + 1, &addr)) { if (Simulator::IsIllegalAddress(addr)) { return false; } *value = *(reinterpret_cast(addr)); return true; } } if (strcmp("pc", desc) == 0) { *value = sim_->get_pc(); return true; } bool retval = SScanF(desc, "0x%" Px64, value) == 1; if (!retval) { retval = SScanF(desc, "%" Px64, value) == 1; } return retval; } bool SimulatorDebugger::GetSValue(char* desc, uint32_t* value) { VRegister vreg = LookupVRegisterByName(desc); if (vreg != kNoVRegister) { *value = sim_->get_vregisters(vreg, 0); return true; } if (desc[0] == '*') { uint64_t addr; if (GetValue(desc + 1, &addr)) { if (Simulator::IsIllegalAddress(addr)) { return false; } *value = *(reinterpret_cast(addr)); return true; } } return false; } bool SimulatorDebugger::GetDValue(char* desc, uint64_t* value) { VRegister vreg = LookupVRegisterByName(desc); if (vreg != kNoVRegister) { *value = sim_->get_vregisterd(vreg, 0); return true; } if (desc[0] == '*') { uint64_t addr; if (GetValue(desc + 1, &addr)) { if (Simulator::IsIllegalAddress(addr)) { return false; } *value = *(reinterpret_cast(addr)); return true; } } return false; } bool SimulatorDebugger::GetQValue(char* desc, simd_value_t* value) { VRegister vreg = LookupVRegisterByName(desc); if (vreg != kNoVRegister) { sim_->get_vregister(vreg, value); return true; } if (desc[0] == '*') { uint64_t addr; if (GetValue(desc + 1, &addr)) { if (Simulator::IsIllegalAddress(addr)) { return false; } *value = *(reinterpret_cast(addr)); return true; } } return false; } TokenPosition SimulatorDebugger::GetApproximateTokenIndex(const Code& code, uword pc) { TokenPosition token_pos = TokenPosition::kNoSource; uword pc_offset = pc - code.PayloadStart(); const PcDescriptors& descriptors = PcDescriptors::Handle(code.pc_descriptors()); PcDescriptors::Iterator iter(descriptors, UntaggedPcDescriptors::kAnyKind); while (iter.MoveNext()) { if (iter.PcOffset() == pc_offset) { return iter.TokenPos(); } else if (!token_pos.IsReal() && (iter.PcOffset() > pc_offset)) { token_pos = iter.TokenPos(); } } return token_pos; } #if defined(DART_PRECOMPILED_RUNTIME) static const char* ImageName(uword vm_instructions, uword isolate_instructions, uword pc, intptr_t* offset) { const Image vm_image(vm_instructions); const Image isolate_image(isolate_instructions); if (vm_image.contains(pc)) { *offset = pc - vm_instructions; return kVmSnapshotInstructionsAsmSymbol; } else if (isolate_image.contains(pc)) { *offset = pc - isolate_instructions; return kIsolateSnapshotInstructionsAsmSymbol; } else { *offset = 0; return ""; } } #endif void SimulatorDebugger::PrintDartFrame(uword vm_instructions, uword isolate_instructions, uword pc, uword fp, uword sp, const Function& function, TokenPosition token_pos, bool is_optimized, bool is_inlined) { const Script& script = Script::Handle(function.script()); const String& func_name = String::Handle(function.QualifiedScrubbedName()); const String& url = String::Handle(script.url()); intptr_t line, column; if (script.GetTokenLocation(token_pos, &line, &column)) { OS::PrintErr( "pc=0x%" Px " fp=0x%" Px " sp=0x%" Px " %s%s (%s:%" Pd ":%" Pd ")", pc, fp, sp, is_optimized ? (is_inlined ? "inlined " : "optimized ") : "", func_name.ToCString(), url.ToCString(), line, column); } else { OS::PrintErr("pc=0x%" Px " fp=0x%" Px " sp=0x%" Px " %s%s (%s)", pc, fp, sp, is_optimized ? (is_inlined ? "inlined " : "optimized ") : "", func_name.ToCString(), url.ToCString()); } #if defined(DART_PRECOMPILED_RUNTIME) intptr_t offset; auto const symbol_name = ImageName(vm_instructions, isolate_instructions, pc, &offset); OS::PrintErr(" %s+0x%" Px "", symbol_name, offset); #endif OS::PrintErr("\n"); } void SimulatorDebugger::PrintBacktrace() { auto const T = Thread::Current(); auto const Z = T->zone(); #if defined(DART_PRECOMPILED_RUNTIME) auto const vm_instructions = reinterpret_cast( Dart::vm_isolate_group()->source()->snapshot_instructions); auto const isolate_instructions = reinterpret_cast( T->isolate_group()->source()->snapshot_instructions); OS::PrintErr("vm_instructions=0x%" Px ", isolate_instructions=0x%" Px "\n", vm_instructions, isolate_instructions); #else const uword vm_instructions = 0; const uword isolate_instructions = 0; #endif StackFrameIterator frames(sim_->get_register(FP), sim_->get_register(SP), sim_->get_pc(), ValidationPolicy::kDontValidateFrames, T, StackFrameIterator::kNoCrossThreadIteration); StackFrame* frame = frames.NextFrame(); ASSERT(frame != NULL); Function& function = Function::Handle(Z); Function& inlined_function = Function::Handle(Z); Code& code = Code::Handle(Z); Code& unoptimized_code = Code::Handle(Z); while (frame != NULL) { if (frame->IsDartFrame()) { code = frame->LookupDartCode(); function = code.function(); if (code.is_optimized()) { // For optimized frames, extract all the inlined functions if any // into the stack trace. InlinedFunctionsIterator it(code, frame->pc()); while (!it.Done()) { // Print each inlined frame with its pc in the corresponding // unoptimized frame. inlined_function = it.function(); unoptimized_code = it.code(); uword unoptimized_pc = it.pc(); it.Advance(); if (!it.Done()) { PrintDartFrame( vm_instructions, isolate_instructions, unoptimized_pc, frame->fp(), frame->sp(), inlined_function, GetApproximateTokenIndex(unoptimized_code, unoptimized_pc), true, true); } } // Print the optimized inlining frame below. } PrintDartFrame(vm_instructions, isolate_instructions, frame->pc(), frame->fp(), frame->sp(), function, GetApproximateTokenIndex(code, frame->pc()), code.is_optimized(), false); } else { OS::PrintErr("pc=0x%" Px " fp=0x%" Px " sp=0x%" Px " %s frame", frame->pc(), frame->fp(), frame->sp(), frame->IsEntryFrame() ? "entry" : frame->IsExitFrame() ? "exit" : frame->IsStubFrame() ? "stub" : "invalid"); #if defined(DART_PRECOMPILED_RUNTIME) intptr_t offset; auto const symbol_name = ImageName(vm_instructions, isolate_instructions, frame->pc(), &offset); OS::PrintErr(" %s+0x%" Px "", symbol_name, offset); #endif OS::PrintErr("\n"); } frame = frames.NextFrame(); } } bool SimulatorDebugger::SetBreakpoint(Instr* breakpc) { // Check if a breakpoint can be set. If not return without any side-effects. if (sim_->break_pc_ != NULL) { return false; } // Set the breakpoint. sim_->break_pc_ = breakpc; sim_->break_instr_ = breakpc->InstructionBits(); // Not setting the breakpoint instruction in the code itself. It will be set // when the debugger shell continues. return true; } bool SimulatorDebugger::DeleteBreakpoint(Instr* breakpc) { if (sim_->break_pc_ != NULL) { sim_->break_pc_->SetInstructionBits(sim_->break_instr_); } sim_->break_pc_ = NULL; sim_->break_instr_ = 0; return true; } void SimulatorDebugger::UndoBreakpoints() { if (sim_->break_pc_ != NULL) { sim_->break_pc_->SetInstructionBits(sim_->break_instr_); } } void SimulatorDebugger::RedoBreakpoints() { if (sim_->break_pc_ != NULL) { sim_->break_pc_->SetInstructionBits(Instr::kSimulatorBreakpointInstruction); } } void SimulatorDebugger::Debug() { uintptr_t last_pc = -1; bool done = false; #define COMMAND_SIZE 63 #define ARG_SIZE 255 #define STR(a) #a #define XSTR(a) STR(a) char cmd[COMMAND_SIZE + 1]; char arg1[ARG_SIZE + 1]; char arg2[ARG_SIZE + 1]; // make sure to have a proper terminating character if reaching the limit cmd[COMMAND_SIZE] = 0; arg1[ARG_SIZE] = 0; arg2[ARG_SIZE] = 0; // Undo all set breakpoints while running in the debugger shell. This will // make them invisible to all commands. UndoBreakpoints(); while (!done) { if (last_pc != sim_->get_pc()) { last_pc = sim_->get_pc(); if (Simulator::IsIllegalAddress(last_pc)) { OS::PrintErr("pc is out of bounds: 0x%" Px "\n", last_pc); } else { if (FLAG_support_disassembler) { Disassembler::Disassemble(last_pc, last_pc + Instr::kInstrSize); } else { OS::PrintErr("Disassembler not supported in this mode.\n"); } } } char* line = ReadLine("sim> "); if (line == NULL) { FATAL("ReadLine failed"); } else { // Use sscanf to parse the individual parts of the command line. At the // moment no command expects more than two parameters. int args = SScanF(line, "%" XSTR(COMMAND_SIZE) "s " "%" XSTR(ARG_SIZE) "s " "%" XSTR(ARG_SIZE) "s", cmd, arg1, arg2); if ((strcmp(cmd, "h") == 0) || (strcmp(cmd, "help") == 0)) { OS::PrintErr( "c/cont -- continue execution\n" "disasm -- disassemble instrs at current pc location\n" " other variants are:\n" " disasm
\n" " disasm
\n" " by default 10 instrs are disassembled\n" "del -- delete breakpoints\n" "flags -- print flag values\n" "gdb -- transfer control to gdb\n" "h/help -- print this help string\n" "break
-- set break point at specified address\n" "p/print -- print integer\n" "pf/printfloat --print float value\n" "pd/printdouble -- print double value\n" "pq/printquad -- print vector register\n" "po/printobject <*reg or *addr> -- print object\n" "si/stepi -- single step an instruction\n" "trace -- toggle execution tracing mode\n" "bt -- print backtrace\n" "unstop -- if current pc is a stop instr make it a nop\n" "q/quit -- Quit the debugger and exit the program\n"); } else if ((strcmp(cmd, "quit") == 0) || (strcmp(cmd, "q") == 0)) { OS::PrintErr("Quitting\n"); OS::Exit(0); } else if ((strcmp(cmd, "si") == 0) || (strcmp(cmd, "stepi") == 0)) { sim_->InstructionDecode(reinterpret_cast(sim_->get_pc())); } else if ((strcmp(cmd, "c") == 0) || (strcmp(cmd, "cont") == 0)) { // Execute the one instruction we broke at with breakpoints disabled. sim_->InstructionDecode(reinterpret_cast(sim_->get_pc())); // Leave the debugger shell. done = true; } else if ((strcmp(cmd, "p") == 0) || (strcmp(cmd, "print") == 0)) { if (args == 2) { uint64_t value; if (strcmp(arg1, "icount") == 0) { value = sim_->get_icount(); OS::PrintErr("icount: %" Pu64 " 0x%" Px64 "\n", value, value); } else if (GetValue(arg1, &value)) { OS::PrintErr("%s: %" Pu64 " 0x%" Px64 "\n", arg1, value, value); } else { OS::PrintErr("%s unrecognized\n", arg1); } } else { OS::PrintErr("print \n"); } } else if ((strcmp(cmd, "pf") == 0) || (strcmp(cmd, "printfloat") == 0)) { if (args == 2) { uint32_t value; if (GetSValue(arg1, &value)) { float svalue = bit_cast(value); OS::PrintErr("%s: %d 0x%x %.8g\n", arg1, value, value, svalue); } else { OS::PrintErr("%s unrecognized\n", arg1); } } else { OS::PrintErr("printfloat \n"); } } else if ((strcmp(cmd, "pd") == 0) || (strcmp(cmd, "printdouble") == 0)) { if (args == 2) { uint64_t long_value; if (GetDValue(arg1, &long_value)) { double dvalue = bit_cast(long_value); OS::PrintErr("%s: %" Pu64 " 0x%" Px64 " %.8g\n", arg1, long_value, long_value, dvalue); } else { OS::PrintErr("%s unrecognized\n", arg1); } } else { OS::PrintErr("printdouble \n"); } } else if ((strcmp(cmd, "pq") == 0) || (strcmp(cmd, "printquad") == 0)) { if (args == 2) { simd_value_t quad_value; if (GetQValue(arg1, &quad_value)) { const int64_t d0 = quad_value.bits.i64[0]; const int64_t d1 = quad_value.bits.i64[1]; const double dval0 = bit_cast(d0); const double dval1 = bit_cast(d1); const int32_t s0 = quad_value.bits.i32[0]; const int32_t s1 = quad_value.bits.i32[1]; const int32_t s2 = quad_value.bits.i32[2]; const int32_t s3 = quad_value.bits.i32[3]; const float sval0 = bit_cast(s0); const float sval1 = bit_cast(s1); const float sval2 = bit_cast(s2); const float sval3 = bit_cast(s3); OS::PrintErr("%s: %" Pu64 " 0x%" Px64 " %.8g\n", arg1, d0, d0, dval0); OS::PrintErr("%s: %" Pu64 " 0x%" Px64 " %.8g\n", arg1, d1, d1, dval1); OS::PrintErr("%s: %d 0x%x %.8g\n", arg1, s0, s0, sval0); OS::PrintErr("%s: %d 0x%x %.8g\n", arg1, s1, s1, sval1); OS::PrintErr("%s: %d 0x%x %.8g\n", arg1, s2, s2, sval2); OS::PrintErr("%s: %d 0x%x %.8g\n", arg1, s3, s3, sval3); } else { OS::PrintErr("%s unrecognized\n", arg1); } } else { OS::PrintErr("printquad \n"); } } else if ((strcmp(cmd, "po") == 0) || (strcmp(cmd, "printobject") == 0)) { if (args == 2) { uint64_t value; // Make the dereferencing '*' optional. if (((arg1[0] == '*') && GetValue(arg1 + 1, &value)) || GetValue(arg1, &value)) { if (IsolateGroup::Current()->heap()->Contains(value)) { OS::PrintErr("%s: \n", arg1); #if defined(DEBUG) const Object& obj = Object::Handle( static_cast(static_cast(value))); obj.Print(); #endif // defined(DEBUG) } else { OS::PrintErr("0x%" Px64 " is not an object reference\n", value); } } else { OS::PrintErr("%s unrecognized\n", arg1); } } else { OS::PrintErr("printobject <*reg or *addr>\n"); } } else if (strcmp(cmd, "disasm") == 0) { uint64_t start = 0; uint64_t end = 0; if (args == 1) { start = sim_->get_pc(); end = start + (10 * Instr::kInstrSize); } else if (args == 2) { if (GetValue(arg1, &start)) { // No length parameter passed, assume 10 instructions. if (Simulator::IsIllegalAddress(start)) { // If start isn't a valid address, warn and use PC instead. OS::PrintErr("First argument yields invalid address: 0x%" Px64 "\n", start); OS::PrintErr("Using PC instead\n"); start = sim_->get_pc(); } end = start + (10 * Instr::kInstrSize); } } else { uint64_t length; if (GetValue(arg1, &start) && GetValue(arg2, &length)) { if (Simulator::IsIllegalAddress(start)) { // If start isn't a valid address, warn and use PC instead. OS::PrintErr("First argument yields invalid address: 0x%" Px64 "\n", start); OS::PrintErr("Using PC instead\n"); start = sim_->get_pc(); } end = start + (length * Instr::kInstrSize); } } if ((start > 0) && (end > start)) { if (FLAG_support_disassembler) { Disassembler::Disassemble(start, end); } else { OS::PrintErr("Disassembler not supported in this mode.\n"); } } else { OS::PrintErr("disasm [
[]]\n"); } } else if (strcmp(cmd, "gdb") == 0) { OS::PrintErr("relinquishing control to gdb\n"); OS::DebugBreak(); OS::PrintErr("regaining control from gdb\n"); } else if (strcmp(cmd, "break") == 0) { if (args == 2) { uint64_t addr; if (GetValue(arg1, &addr)) { if (!SetBreakpoint(reinterpret_cast(addr))) { OS::PrintErr("setting breakpoint failed\n"); } } else { OS::PrintErr("%s unrecognized\n", arg1); } } else { OS::PrintErr("break \n"); } } else if (strcmp(cmd, "del") == 0) { if (!DeleteBreakpoint(NULL)) { OS::PrintErr("deleting breakpoint failed\n"); } } else if (strcmp(cmd, "flags") == 0) { OS::PrintErr("APSR: "); OS::PrintErr("N flag: %d; ", sim_->n_flag_); OS::PrintErr("Z flag: %d; ", sim_->z_flag_); OS::PrintErr("C flag: %d; ", sim_->c_flag_); OS::PrintErr("V flag: %d\n", sim_->v_flag_); } else if (strcmp(cmd, "unstop") == 0) { intptr_t stop_pc = sim_->get_pc() - Instr::kInstrSize; Instr* stop_instr = reinterpret_cast(stop_pc); if (stop_instr->IsExceptionGenOp()) { stop_instr->SetInstructionBits(Instr::kNopInstruction); } else { OS::PrintErr("Not at debugger stop.\n"); } } else if (strcmp(cmd, "trace") == 0) { if (FLAG_trace_sim_after == ULLONG_MAX) { FLAG_trace_sim_after = sim_->get_icount(); OS::PrintErr("execution tracing on\n"); } else { FLAG_trace_sim_after = ULLONG_MAX; OS::PrintErr("execution tracing off\n"); } } else if (strcmp(cmd, "bt") == 0) { Thread* thread = reinterpret_cast(sim_->get_register(THR)); thread->set_execution_state(Thread::kThreadInVM); PrintBacktrace(); thread->set_execution_state(Thread::kThreadInGenerated); } else { OS::PrintErr("Unknown command: %s\n", cmd); } } delete[] line; } // Add all the breakpoints back to stop execution and enter the debugger // shell when hit. RedoBreakpoints(); #undef COMMAND_SIZE #undef ARG_SIZE #undef STR #undef XSTR } char* SimulatorDebugger::ReadLine(const char* prompt) { char* result = NULL; char line_buf[256]; intptr_t offset = 0; bool keep_going = true; OS::PrintErr("%s", prompt); while (keep_going) { if (fgets(line_buf, sizeof(line_buf), stdin) == NULL) { // fgets got an error. Just give up. if (result != NULL) { delete[] result; } return NULL; } intptr_t len = strlen(line_buf); if (len > 1 && line_buf[len - 2] == '\\' && line_buf[len - 1] == '\n') { // When we read a line that ends with a "\" we remove the escape and // append the remainder. line_buf[len - 2] = '\n'; line_buf[len - 1] = 0; len -= 1; } else if ((len > 0) && (line_buf[len - 1] == '\n')) { // Since we read a new line we are done reading the line. This // will exit the loop after copying this buffer into the result. keep_going = false; } if (result == NULL) { // Allocate the initial result and make room for the terminating '\0' result = new char[len + 1]; if (result == NULL) { // OOM, so cannot readline anymore. return NULL; } } else { // Allocate a new result with enough room for the new addition. intptr_t new_len = offset + len + 1; char* new_result = new char[new_len]; if (new_result == NULL) { // OOM, free the buffer allocated so far and return NULL. delete[] result; return NULL; } else { // Copy the existing input into the new array and set the new // array as the result. memmove(new_result, result, offset); delete[] result; result = new_result; } } // Copy the newly read line into the result. memmove(result + offset, line_buf, len); offset += len; } ASSERT(result != NULL); result[offset] = '\0'; return result; } void Simulator::Init() {} Simulator::Simulator() : exclusive_access_addr_(0), exclusive_access_value_(0) { // Setup simulator support first. Some of this information is needed to // setup the architecture state. // We allocate the stack here, the size is computed as the sum of // the size specified by the user and the buffer space needed for // handling stack overflow exceptions. To be safe in potential // stack underflows we also add some underflow buffer space. stack_ = new char[(OSThread::GetSpecifiedStackSize() + OSThread::kStackSizeBufferMax + kSimulatorStackUnderflowSize)]; // Low address. stack_limit_ = reinterpret_cast(stack_); // Limit for StackOverflowError. overflow_stack_limit_ = stack_limit_ + OSThread::kStackSizeBufferMax; // High address. stack_base_ = overflow_stack_limit_ + OSThread::GetSpecifiedStackSize(); pc_modified_ = false; icount_ = 0; break_pc_ = NULL; break_instr_ = 0; last_setjmp_buffer_ = NULL; // Setup architecture state. // All registers are initialized to zero to start with. for (int i = 0; i < kNumberOfCpuRegisters; i++) { registers_[i] = 0; } n_flag_ = false; z_flag_ = false; c_flag_ = false; v_flag_ = false; for (int i = 0; i < kNumberOfVRegisters; i++) { vregisters_[i].bits.i64[0] = 0; vregisters_[i].bits.i64[1] = 0; } // The sp is initialized to point to the bottom (high address) of the // allocated stack area. registers_[R31] = stack_base(); // The lr and pc are initialized to a known bad value that will cause an // access violation if the simulator ever tries to execute it. registers_[LR] = kBadLR; pc_ = kBadLR; } Simulator::~Simulator() { delete[] stack_; Isolate* isolate = Isolate::Current(); if (isolate != NULL) { isolate->set_simulator(NULL); } } // 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_hlt_instruction() { return reinterpret_cast(&hlt_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* FromHltInstruction(Instr* hlt_instruction) { char* addr_of_hlt = reinterpret_cast(hlt_instruction); char* addr_of_redirection = addr_of_hlt - OFFSET_OF(Redirection, hlt_instruction_); return reinterpret_cast(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_hlt) { for (Redirection* current = list_.load(std::memory_order_acquire); current != nullptr; current = current->next_) { if (current->address_of_hlt_instruction() == address_of_hlt) { 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), hlt_instruction_(Instr::kSimulatorRedirectInstruction), next_(NULL) {} uword external_function_; Simulator::CallKind call_kind_; int argument_count_; uint32_t hlt_instruction_; Redirection* next_; static std::atomic list_; static Mutex* mutex_; }; std::atomic 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_hlt_instruction(); } uword Simulator::FunctionForRedirect(uword redirect) { return Redirection::FunctionForRedirect(redirect); } // Get the active Simulator for the current isolate. Simulator* Simulator::Current() { Isolate* isolate = Isolate::Current(); Simulator* simulator = isolate->simulator(); if (simulator == NULL) { NoSafepointScope no_safepoint; simulator = new Simulator(); isolate->set_simulator(simulator); } return simulator; } // Sets the register in the architecture state. void Simulator::set_register(Instr* instr, Register reg, int64_t value, R31Type r31t) { // Register is in range. ASSERT((reg >= 0) && (reg < kNumberOfCpuRegisters)); #if !defined(TARGET_OS_FUCHSIA) ASSERT(instr == NULL || reg != R18); // R18 is globally reserved on iOS. #endif if ((reg != R31) || (r31t != R31IsZR)) { registers_[reg] = value; // If we're setting CSP, make sure it is 16-byte aligned. In truth, CSP // can store addresses that are not 16-byte aligned, but loads and stores // are not allowed through CSP when it is not aligned. Thus, this check is // more conservative that necessary. However, it will likely be more // useful to find the program locations where CSP is set to a bad value, // than to find only the resulting loads/stores that would cause a fault on // hardware. if ((instr != NULL) && (reg == R31) && !Utils::IsAligned(value, 16)) { UnalignedAccess("CSP set", value, instr); } } } // Get the register from the architecture state. int64_t Simulator::get_register(Register reg, R31Type r31t) const { ASSERT((reg >= 0) && (reg < kNumberOfCpuRegisters)); if ((reg == R31) && (r31t == R31IsZR)) { return 0; } else { return registers_[reg]; } } void Simulator::set_wregister(Register reg, int32_t value, R31Type r31t) { ASSERT((reg >= 0) && (reg < kNumberOfCpuRegisters)); // When setting in W mode, clear the high bits. if ((reg != R31) || (r31t != R31IsZR)) { registers_[reg] = Utils::LowHighTo64Bits(static_cast(value), 0); } } // Get the register from the architecture state. int32_t Simulator::get_wregister(Register reg, R31Type r31t) const { ASSERT((reg >= 0) && (reg < kNumberOfCpuRegisters)); if ((reg == R31) && (r31t == R31IsZR)) { return 0; } else { return static_cast(registers_[reg]); } } int32_t Simulator::get_vregisters(VRegister reg, int idx) const { ASSERT((reg >= 0) && (reg < kNumberOfVRegisters)); ASSERT((idx >= 0) && (idx <= 3)); return vregisters_[reg].bits.i32[idx]; } void Simulator::set_vregisters(VRegister reg, int idx, int32_t value) { ASSERT((reg >= 0) && (reg < kNumberOfVRegisters)); ASSERT((idx >= 0) && (idx <= 3)); vregisters_[reg].bits.i32[idx] = value; } int64_t Simulator::get_vregisterd(VRegister reg, int idx) const { ASSERT((reg >= 0) && (reg < kNumberOfVRegisters)); ASSERT((idx == 0) || (idx == 1)); return vregisters_[reg].bits.i64[idx]; } void Simulator::set_vregisterd(VRegister reg, int idx, int64_t value) { ASSERT((reg >= 0) && (reg < kNumberOfVRegisters)); ASSERT((idx == 0) || (idx == 1)); vregisters_[reg].bits.i64[idx] = value; } void Simulator::get_vregister(VRegister reg, simd_value_t* value) const { ASSERT((reg >= 0) && (reg < kNumberOfVRegisters)); value->bits.i64[0] = vregisters_[reg].bits.i64[0]; value->bits.i64[1] = vregisters_[reg].bits.i64[1]; } void Simulator::set_vregister(VRegister reg, const simd_value_t& value) { ASSERT((reg >= 0) && (reg < kNumberOfVRegisters)); vregisters_[reg].bits.i64[0] = value.bits.i64[0]; vregisters_[reg].bits.i64[1] = value.bits.i64[1]; } // Raw access to the PC register. void Simulator::set_pc(uint64_t value) { pc_modified_ = true; last_pc_ = pc_; pc_ = value; } // Raw access to the pc. uint64_t Simulator::get_pc() const { return pc_; } uint64_t Simulator::get_last_pc() const { return last_pc_; } void Simulator::HandleIllegalAccess(uword addr, Instr* instr) { uword fault_pc = get_pc(); uword last_pc = get_last_pc(); char buffer[128]; snprintf(buffer, sizeof(buffer), "illegal memory access at 0x%" Px ", pc=0x%" Px ", last_pc=0x%" Px "\n", addr, fault_pc, last_pc); SimulatorDebugger dbg(this); dbg.Stop(instr, buffer); // The debugger will return control in non-interactive mode. FATAL("Cannot continue execution after illegal memory access."); } // ARMv8 supports unaligned memory accesses to normal memory without trapping // for all instructions except Load-Exclusive/Store-Exclusive and // Load-Acquire/Store-Release. // See B2.4.2 "Alignment of data accesses" for more information. void Simulator::UnalignedAccess(const char* msg, uword addr, Instr* instr) { char buffer[128]; snprintf(buffer, sizeof(buffer), "unaligned %s at 0x%" Px ", pc=%p\n", msg, addr, instr); SimulatorDebugger dbg(this); dbg.Stop(instr, buffer); // The debugger will not be able to single step past this instruction, but // it will be possible to disassemble the code and inspect registers. FATAL("Cannot continue execution after unaligned access."); } void Simulator::UnimplementedInstruction(Instr* instr) { char buffer[128]; snprintf(buffer, sizeof(buffer), "Unimplemented instruction: at %p, last_pc=0x%" Px64 "\n", instr, get_last_pc()); SimulatorDebugger dbg(this); dbg.Stop(instr, buffer); FATAL("Cannot continue execution after unimplemented instruction."); } bool Simulator::IsTracingExecution() const { return icount_ > FLAG_trace_sim_after; } intptr_t Simulator::ReadX(uword addr, Instr* instr, bool must_be_aligned /* = false */) { const bool allow_unaligned_access = FLAG_sim_allow_unaligned_accesses && !must_be_aligned; if (allow_unaligned_access || (addr & 7) == 0) { intptr_t* ptr = reinterpret_cast(addr); return *ptr; } UnalignedAccess("read", addr, instr); return 0; } void Simulator::WriteX(uword addr, intptr_t value, Instr* instr) { if (FLAG_sim_allow_unaligned_accesses || (addr & 7) == 0) { intptr_t* ptr = reinterpret_cast(addr); *ptr = value; return; } UnalignedAccess("write", addr, instr); } uint32_t Simulator::ReadWU(uword addr, Instr* instr, bool must_be_aligned /* = false */) { const bool allow_unaligned_access = FLAG_sim_allow_unaligned_accesses && !must_be_aligned; if (allow_unaligned_access || (addr & 3) == 0) { uint32_t* ptr = reinterpret_cast(addr); return *ptr; } UnalignedAccess("read unsigned single word", addr, instr); return 0; } int32_t Simulator::ReadW(uword addr, Instr* instr) { if (FLAG_sim_allow_unaligned_accesses || (addr & 3) == 0) { int32_t* ptr = reinterpret_cast(addr); return *ptr; } UnalignedAccess("read single word", addr, instr); return 0; } void Simulator::WriteW(uword addr, uint32_t value, Instr* instr) { if (FLAG_sim_allow_unaligned_accesses || (addr & 3) == 0) { uint32_t* ptr = reinterpret_cast(addr); *ptr = value; return; } UnalignedAccess("write single word", addr, instr); } uint16_t Simulator::ReadHU(uword addr, Instr* instr) { if (FLAG_sim_allow_unaligned_accesses || (addr & 1) == 0) { uint16_t* ptr = reinterpret_cast(addr); return *ptr; } UnalignedAccess("unsigned halfword read", addr, instr); return 0; } int16_t Simulator::ReadH(uword addr, Instr* instr) { if (FLAG_sim_allow_unaligned_accesses || (addr & 1) == 0) { int16_t* ptr = reinterpret_cast(addr); return *ptr; } UnalignedAccess("signed halfword read", addr, instr); return 0; } void Simulator::WriteH(uword addr, uint16_t value, Instr* instr) { if (FLAG_sim_allow_unaligned_accesses || (addr & 1) == 0) { uint16_t* ptr = reinterpret_cast(addr); *ptr = value; return; } UnalignedAccess("halfword write", addr, instr); } uint8_t Simulator::ReadBU(uword addr) { uint8_t* ptr = reinterpret_cast(addr); return *ptr; } int8_t Simulator::ReadB(uword addr) { int8_t* ptr = reinterpret_cast(addr); return *ptr; } void Simulator::WriteB(uword addr, uint8_t value) { uint8_t* ptr = reinterpret_cast(addr); *ptr = value; } void Simulator::ClearExclusive() { exclusive_access_addr_ = 0; exclusive_access_value_ = 0; } intptr_t Simulator::ReadExclusiveX(uword addr, Instr* instr) { exclusive_access_addr_ = addr; exclusive_access_value_ = ReadX(addr, instr, /*must_be_aligned=*/true); return exclusive_access_value_; } intptr_t Simulator::ReadExclusiveW(uword addr, Instr* instr) { exclusive_access_addr_ = addr; exclusive_access_value_ = ReadWU(addr, instr, /*must_be_aligned=*/true); return exclusive_access_value_; } intptr_t Simulator::WriteExclusiveX(uword addr, intptr_t value, Instr* instr) { // In a well-formed code store-exclusive instruction should always follow // a corresponding load-exclusive instruction with the same address. ASSERT((exclusive_access_addr_ == 0) || (exclusive_access_addr_ == addr)); if (exclusive_access_addr_ != addr) { return 1; // Failure. } int64_t old_value = exclusive_access_value_; ClearExclusive(); auto atomic_addr = reinterpret_cast*>(addr); if (atomic_addr->compare_exchange_weak(old_value, value)) { return 0; // Success. } return 1; // Failure. } intptr_t Simulator::WriteExclusiveW(uword addr, intptr_t value, Instr* instr) { // In a well-formed code store-exclusive instruction should always follow // a corresponding load-exclusive instruction with the same address. ASSERT((exclusive_access_addr_ == 0) || (exclusive_access_addr_ == addr)); if (exclusive_access_addr_ != addr) { return 1; // Failure. } int32_t old_value = static_cast(exclusive_access_value_); ClearExclusive(); auto atomic_addr = reinterpret_cast*>(addr); if (atomic_addr->compare_exchange_weak(old_value, value)) { return 0; // Success. } return 1; // Failure. } intptr_t Simulator::ReadAcquire(uword addr, Instr* instr) { // TODO(42074): Once we switch to C++20 we should change this to use use // `std::atomic_ref` which supports performing atomic operations on // non-atomic data. COMPILE_ASSERT(sizeof(std::atomic) == sizeof(intptr_t)); return reinterpret_cast*>(addr)->load( std::memory_order_acquire); } uint32_t Simulator::ReadAcquireW(uword addr, Instr* instr) { // TODO(42074): Once we switch to C++20 we should change this to use use // `std::atomic_ref` which supports performing atomic operations on // non-atomic data. COMPILE_ASSERT(sizeof(std::atomic) == sizeof(intptr_t)); return reinterpret_cast*>(addr)->load( std::memory_order_acquire); } void Simulator::WriteRelease(uword addr, intptr_t value, Instr* instr) { // TODO(42074): Once we switch to C++20 we should change this to use use // `std::atomic_ref` which supports performing atomic operations on // non-atomic data. COMPILE_ASSERT(sizeof(std::atomic) == sizeof(intptr_t)); reinterpret_cast*>(addr)->store( value, std::memory_order_release); } void Simulator::WriteReleaseW(uword addr, uint32_t value, Instr* instr) { // TODO(42074): Once we switch to C++20 we should change this to use use // `std::atomic_ref` which supports performing atomic operations on // non-atomic data. COMPILE_ASSERT(sizeof(std::atomic) == sizeof(intptr_t)); reinterpret_cast*>(addr)->store( value, std::memory_order_release); } // Unsupported instructions use Format to print an error and stop execution. void Simulator::Format(Instr* instr, const char* format) { OS::PrintErr("Simulator found unsupported instruction:\n 0x%p: %s\n", instr, format); UNIMPLEMENTED(); } // Calculate and set the Negative and Zero flags. void Simulator::SetNZFlagsW(int32_t val) { n_flag_ = (val < 0); z_flag_ = (val == 0); } // Calculate C flag value for additions (and subtractions with adjusted args). bool Simulator::CarryFromW(int32_t left, int32_t right, int32_t carry) { uint64_t uleft = static_cast(left); uint64_t uright = static_cast(right); uint64_t ucarry = static_cast(carry); return ((uleft + uright + ucarry) >> 32) != 0; } // Calculate V flag value for additions (and subtractions with adjusted args). bool Simulator::OverflowFromW(int32_t left, int32_t right, int32_t carry) { int64_t result = static_cast(left) + right + carry; return (result >> 31) != (result >> 32); } // Calculate and set the Negative and Zero flags. void Simulator::SetNZFlagsX(int64_t val) { n_flag_ = (val < 0); z_flag_ = (val == 0); } // Calculate C flag value for additions and subtractions. bool Simulator::CarryFromX(int64_t alu_out, int64_t left, int64_t right, bool addition) { if (addition) { return (((left & right) | ((left | right) & ~alu_out)) >> 63) != 0; } else { return (((~left & right) | ((~left | right) & alu_out)) >> 63) == 0; } } // Calculate V flag value for additions and subtractions. bool Simulator::OverflowFromX(int64_t alu_out, int64_t left, int64_t right, bool addition) { if (addition) { return (((alu_out ^ left) & (alu_out ^ right)) >> 63) != 0; } else { return (((left ^ right) & (alu_out ^ left)) >> 63) != 0; } } // Set the Carry flag. void Simulator::SetCFlag(bool val) { c_flag_ = val; } // Set the oVerflow flag. void Simulator::SetVFlag(bool val) { v_flag_ = val; } void Simulator::DecodeMoveWide(Instr* instr) { const Register rd = instr->RdField(); const int hw = instr->HWField(); const int64_t shift = hw << 4; const int64_t shifted_imm = static_cast(instr->Imm16Field()) << shift; if (instr->SFField()) { if (instr->Bits(29, 2) == 0) { // Format(instr, "movn'sf 'rd, 'imm16 'hw"); set_register(instr, rd, ~shifted_imm, instr->RdMode()); } else if (instr->Bits(29, 2) == 2) { // Format(instr, "movz'sf 'rd, 'imm16 'hw"); set_register(instr, rd, shifted_imm, instr->RdMode()); } else if (instr->Bits(29, 2) == 3) { // Format(instr, "movk'sf 'rd, 'imm16 'hw"); const int64_t rd_val = get_register(rd, instr->RdMode()); const int64_t result = (rd_val & ~(0xffffL << shift)) | shifted_imm; set_register(instr, rd, result, instr->RdMode()); } else { UnimplementedInstruction(instr); } } else if ((hw & 0x2) == 0) { if (instr->Bits(29, 2) == 0) { // Format(instr, "movn'sf 'rd, 'imm16 'hw"); set_wregister(rd, ~shifted_imm & kWRegMask, instr->RdMode()); } else if (instr->Bits(29, 2) == 2) { // Format(instr, "movz'sf 'rd, 'imm16 'hw"); set_wregister(rd, shifted_imm & kWRegMask, instr->RdMode()); } else if (instr->Bits(29, 2) == 3) { // Format(instr, "movk'sf 'rd, 'imm16 'hw"); const int32_t rd_val = get_wregister(rd, instr->RdMode()); const int32_t result = (rd_val & ~(0xffffL << shift)) | shifted_imm; set_wregister(rd, result, instr->RdMode()); } else { UnimplementedInstruction(instr); } } else { // Dest is 32 bits, but shift is more than 32. UnimplementedInstruction(instr); } } void Simulator::DecodeAddSubImm(Instr* instr) { const bool addition = (instr->Bit(30) == 0); // Format(instr, "addi'sf's 'rd, 'rn, 'imm12s"); // Format(instr, "subi'sf's 'rd, 'rn, 'imm12s"); const Register rd = instr->RdField(); const Register rn = instr->RnField(); uint32_t imm = (instr->Bit(22) == 1) ? (instr->Imm12Field() << 12) : (instr->Imm12Field()); if (instr->SFField()) { // 64-bit add. const uint64_t rn_val = get_register(rn, instr->RnMode()); const uint64_t alu_out = addition ? (rn_val + imm) : (rn_val - imm); set_register(instr, rd, alu_out, instr->RdMode()); if (instr->HasS()) { SetNZFlagsX(alu_out); SetCFlag(CarryFromX(alu_out, rn_val, imm, addition)); SetVFlag(OverflowFromX(alu_out, rn_val, imm, addition)); } } else { // 32-bit add. const uint32_t rn_val = get_wregister(rn, instr->RnMode()); uint32_t carry_in = 0; if (!addition) { carry_in = 1; imm = ~imm; } const uint32_t alu_out = rn_val + imm + carry_in; set_wregister(rd, alu_out, instr->RdMode()); if (instr->HasS()) { SetNZFlagsW(alu_out); SetCFlag(CarryFromW(rn_val, imm, carry_in)); SetVFlag(OverflowFromW(rn_val, imm, carry_in)); } } } void Simulator::DecodeBitfield(Instr* instr) { int bitwidth = instr->SFField() == 0 ? 32 : 64; unsigned op = instr->Bits(29, 2); ASSERT(op <= 2); bool sign_extend = op == 0; bool zero_extend = op == 2; ASSERT(instr->NField() == instr->SFField()); const Register rn = instr->RnField(); const Register rd = instr->RdField(); int64_t result = get_register(rn, instr->RnMode()); int r_bit = instr->ImmRField(); int s_bit = instr->ImmSField(); result &= Utils::NBitMask(bitwidth); ASSERT(s_bit < bitwidth && r_bit < bitwidth); // See ARM v8 Instruction set overview 5.4.5. // If s >= r then Rd[s-r:0] := Rn[s:r], else Rd[bitwidth+s-r:bitwidth-r] := // Rn[s:0]. uword mask = Utils::NBitMask(s_bit + 1); if (s_bit >= r_bit) { mask >>= r_bit; result >>= r_bit; } else { result = static_cast(result) << (bitwidth - r_bit); mask <<= bitwidth - r_bit; } result &= mask; if (sign_extend) { int highest_bit = (s_bit - r_bit) & (bitwidth - 1); int shift = 64 - highest_bit - 1; result <<= shift; result = static_cast(result) >> shift; } else if (!zero_extend) { const int64_t rd_val = get_register(rd, instr->RnMode()); result |= rd_val & ~mask; } if (bitwidth == 64) { set_register(instr, rd, result, instr->RdMode()); } else { set_wregister(rd, result, instr->RdMode()); } } void Simulator::DecodeLogicalImm(Instr* instr) { const int op = instr->Bits(29, 2); const bool set_flags = op == 3; const int out_size = ((instr->SFField() == 0) && (instr->NField() == 0)) ? kWRegSizeInBits : kXRegSizeInBits; const Register rn = instr->RnField(); const Register rd = instr->RdField(); const int64_t rn_val = get_register(rn, instr->RnMode()); const uint64_t imm = instr->ImmLogical(); if (imm == 0) { UnimplementedInstruction(instr); } int64_t alu_out = 0; switch (op) { case 0: alu_out = rn_val & imm; break; case 1: alu_out = rn_val | imm; break; case 2: alu_out = rn_val ^ imm; break; case 3: alu_out = rn_val & imm; break; default: UNREACHABLE(); break; } if (set_flags) { if (out_size == kXRegSizeInBits) { SetNZFlagsX(alu_out); } else { SetNZFlagsW(alu_out); } SetCFlag(false); SetVFlag(false); } if (out_size == kXRegSizeInBits) { set_register(instr, rd, alu_out, instr->RdMode()); } else { set_wregister(rd, alu_out, instr->RdMode()); } } void Simulator::DecodePCRel(Instr* instr) { const int op = instr->Bit(31); if (op == 0) { // Format(instr, "adr 'rd, 'pcrel") const Register rd = instr->RdField(); const uint64_t immhi = instr->SImm19Field(); const uint64_t immlo = instr->Bits(29, 2); const uint64_t off = (immhi << 2) | immlo; const uint64_t dest = get_pc() + off; set_register(instr, rd, dest, instr->RdMode()); } else { UnimplementedInstruction(instr); } } void Simulator::DecodeDPImmediate(Instr* instr) { if (instr->IsMoveWideOp()) { DecodeMoveWide(instr); } else if (instr->IsAddSubImmOp()) { DecodeAddSubImm(instr); } else if (instr->IsBitfieldOp()) { DecodeBitfield(instr); } else if (instr->IsLogicalImmOp()) { DecodeLogicalImm(instr); } else if (instr->IsPCRelOp()) { DecodePCRel(instr); } else { UnimplementedInstruction(instr); } } void Simulator::DecodeCompareAndBranch(Instr* instr) { const int op = instr->Bit(24); const Register rt = instr->RtField(); const uint64_t imm19 = instr->SImm19Field(); const uint64_t dest = get_pc() + (imm19 << 2); const uint64_t mask = instr->SFField() == 1 ? kXRegMask : kWRegMask; const uint64_t rt_val = get_register(rt, R31IsZR) & mask; if (op == 0) { // Format(instr, "cbz'sf 'rt, 'dest19"); if (rt_val == 0) { set_pc(dest); } } else { // Format(instr, "cbnz'sf 'rt, 'dest19"); if (rt_val != 0) { set_pc(dest); } } } bool Simulator::ConditionallyExecute(Instr* instr) { Condition cond; if (instr->IsConditionalSelectOp()) { cond = instr->SelectConditionField(); } else { cond = instr->ConditionField(); } switch (cond) { case EQ: return z_flag_; case NE: return !z_flag_; case CS: return c_flag_; case CC: return !c_flag_; case MI: return n_flag_; case PL: return !n_flag_; case VS: return v_flag_; case VC: return !v_flag_; case HI: return c_flag_ && !z_flag_; case LS: return !c_flag_ || z_flag_; case GE: return n_flag_ == v_flag_; case LT: return n_flag_ != v_flag_; case GT: return !z_flag_ && (n_flag_ == v_flag_); case LE: return z_flag_ || (n_flag_ != v_flag_); case AL: return true; default: UNREACHABLE(); } return false; } void Simulator::DecodeConditionalBranch(Instr* instr) { // Format(instr, "b'cond 'dest19"); if ((instr->Bit(24) != 0) || (instr->Bit(4) != 0)) { UnimplementedInstruction(instr); } const uint64_t imm19 = instr->SImm19Field(); const uint64_t dest = get_pc() + (imm19 << 2); if (ConditionallyExecute(instr)) { set_pc(dest); } } // 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 int64_t (*SimulatorLeafRuntimeCall)(int64_t r0, int64_t r1, int64_t r2, int64_t r3, int64_t r4, int64_t r5, int64_t r6, int64_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 int64_t InvokeLeafRuntime(SimulatorLeafRuntimeCall target, int64_t r0, int64_t r1, int64_t r2, int64_t r3, int64_t r4, int64_t r5, int64_t r6, int64_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::DoRedirectedCall(Instr* instr) { SimulatorSetjmpBuffer buffer(this); if (!setjmp(buffer.buffer_)) { int64_t saved_lr = get_register(LR); Redirection* redirection = Redirection::FromHltInstruction(instr); uword external = redirection->external_function(); if (IsTracingExecution()) { THR_Print("Call to host function at 0x%" Pd "\n", external); } if (redirection->call_kind() == kRuntimeCall) { NativeArguments* arguments = reinterpret_cast(get_register(R0)); SimulatorRuntimeCall target = reinterpret_cast(external); target(*arguments); // Zap result register from void function. set_register(instr, R0, icount_); set_register(instr, R1, icount_); } else if (redirection->call_kind() == kLeafRuntimeCall) { ASSERT((0 <= redirection->argument_count()) && (redirection->argument_count() <= 8)); SimulatorLeafRuntimeCall target = reinterpret_cast(external); const int64_t r0 = get_register(R0); const int64_t r1 = get_register(R1); const int64_t r2 = get_register(R2); const int64_t r3 = get_register(R3); const int64_t r4 = get_register(R4); const int64_t r5 = get_register(R5); const int64_t r6 = get_register(R6); const int64_t r7 = get_register(R7); const int64_t res = InvokeLeafRuntime(target, r0, r1, r2, r3, r4, r5, r6, r7); set_register(instr, R0, res); // Set returned result from function. set_register(instr, R1, icount_); // Zap unused result register. } else if (redirection->call_kind() == kLeafFloatRuntimeCall) { ASSERT((0 <= redirection->argument_count()) && (redirection->argument_count() <= 8)); SimulatorLeafFloatRuntimeCall target = reinterpret_cast(external); const double d0 = bit_cast(get_vregisterd(V0, 0)); const double d1 = bit_cast(get_vregisterd(V1, 0)); const double d2 = bit_cast(get_vregisterd(V2, 0)); const double d3 = bit_cast(get_vregisterd(V3, 0)); const double d4 = bit_cast(get_vregisterd(V4, 0)); const double d5 = bit_cast(get_vregisterd(V5, 0)); const double d6 = bit_cast(get_vregisterd(V6, 0)); const double d7 = bit_cast(get_vregisterd(V7, 0)); const double res = InvokeFloatLeafRuntime(target, d0, d1, d2, d3, d4, d5, d6, d7); set_vregisterd(V0, 0, bit_cast(res)); set_vregisterd(V0, 1, 0); } else { ASSERT(redirection->call_kind() == kNativeCallWrapper); SimulatorNativeCallWrapper wrapper = reinterpret_cast(external); Dart_NativeArguments arguments = reinterpret_cast(get_register(R0)); Dart_NativeFunction target = reinterpret_cast(get_register(R1)); wrapper(arguments, target); // Zap result register from void function. set_register(instr, R0, icount_); set_register(instr, R1, icount_); } // Zap caller-saved registers, since the actual runtime call could have // used them. set_register(NULL, R2, icount_); set_register(NULL, R3, icount_); set_register(NULL, R4, icount_); set_register(NULL, R5, icount_); set_register(NULL, R6, icount_); set_register(NULL, R7, icount_); set_register(NULL, R8, icount_); set_register(NULL, R9, icount_); set_register(NULL, R10, icount_); set_register(NULL, R11, icount_); set_register(NULL, R12, icount_); set_register(NULL, R13, icount_); set_register(NULL, R14, icount_); set_register(NULL, R15, icount_); set_register(NULL, IP0, icount_); set_register(NULL, IP1, icount_); set_register(NULL, R18, icount_); set_register(NULL, LR, icount_); // TODO(zra): Zap caller-saved fpu registers. // Return. set_pc(saved_lr); } else { // Coming via long jump from a throw. Continue to exception handler. } } void Simulator::DecodeExceptionGen(Instr* instr) { if ((instr->Bits(0, 2) == 1) && (instr->Bits(2, 3) == 0) && (instr->Bits(21, 3) == 0)) { // Format(instr, "svc 'imm16"); UnimplementedInstruction(instr); } else if ((instr->Bits(0, 2) == 0) && (instr->Bits(2, 3) == 0) && (instr->Bits(21, 3) == 1)) { // Format(instr, "brk 'imm16"); SimulatorDebugger dbg(this); int32_t imm = instr->Imm16Field(); char buffer[32]; snprintf(buffer, sizeof(buffer), "brk #0x%x", imm); set_pc(get_pc() + Instr::kInstrSize); dbg.Stop(instr, buffer); } else if ((instr->Bits(0, 2) == 0) && (instr->Bits(2, 3) == 0) && (instr->Bits(21, 3) == 2)) { // Format(instr, "hlt 'imm16"); uint16_t imm = static_cast(instr->Imm16Field()); if (imm == Instr::kSimulatorBreakCode) { SimulatorDebugger dbg(this); dbg.Stop(instr, "breakpoint"); } else if (imm == Instr::kSimulatorRedirectCode) { DoRedirectedCall(instr); } else { UnimplementedInstruction(instr); } } else { UnimplementedInstruction(instr); } } void Simulator::DecodeSystem(Instr* instr) { if (instr->InstructionBits() == CLREX) { // Format(instr, "clrex"); ClearExclusive(); return; } if ((instr->Bits(0, 8) == 0x1f) && (instr->Bits(12, 4) == 2) && (instr->Bits(16, 3) == 3) && (instr->Bits(19, 2) == 0) && (instr->Bit(21) == 0)) { if (instr->Bits(8, 4) == 0) { // Format(instr, "nop"); } else { UnimplementedInstruction(instr); } } else { UnimplementedInstruction(instr); } } void Simulator::DecodeTestAndBranch(Instr* instr) { const int op = instr->Bit(24); const int bitpos = instr->Bits(19, 5) | (instr->Bit(31) << 5); const uint64_t imm14 = instr->SImm14Field(); const uint64_t dest = get_pc() + (imm14 << 2); const Register rt = instr->RtField(); const uint64_t rt_val = get_register(rt, R31IsZR); if (op == 0) { // Format(instr, "tbz'sf 'rt, 'bitpos, 'dest14"); if ((rt_val & (1ull << bitpos)) == 0) { set_pc(dest); } } else { // Format(instr, "tbnz'sf 'rt, 'bitpos, 'dest14"); if ((rt_val & (1ull << bitpos)) != 0) { set_pc(dest); } } } void Simulator::DecodeUnconditionalBranch(Instr* instr) { const bool link = instr->Bit(31) == 1; const uint64_t imm26 = instr->SImm26Field(); const uint64_t dest = get_pc() + (imm26 << 2); const uint64_t ret = get_pc() + Instr::kInstrSize; set_pc(dest); if (link) { set_register(instr, LR, ret); } } void Simulator::DecodeUnconditionalBranchReg(Instr* instr) { if ((instr->Bits(0, 5) == 0) && (instr->Bits(10, 6) == 0) && (instr->Bits(16, 5) == 0x1f)) { switch (instr->Bits(21, 4)) { case 0: { // Format(instr, "br 'rn"); const Register rn = instr->RnField(); const int64_t dest = get_register(rn, instr->RnMode()); set_pc(dest); break; } case 1: { // Format(instr, "blr 'rn"); const Register rn = instr->RnField(); const int64_t dest = get_register(rn, instr->RnMode()); const int64_t ret = get_pc() + Instr::kInstrSize; set_pc(dest); set_register(instr, LR, ret); break; } case 2: { // Format(instr, "ret 'rn"); const Register rn = instr->RnField(); const int64_t rn_val = get_register(rn, instr->RnMode()); set_pc(rn_val); break; } default: UnimplementedInstruction(instr); break; } } else { UnimplementedInstruction(instr); } } void Simulator::DecodeCompareBranch(Instr* instr) { if (instr->IsCompareAndBranchOp()) { DecodeCompareAndBranch(instr); } else if (instr->IsConditionalBranchOp()) { DecodeConditionalBranch(instr); } else if (instr->IsExceptionGenOp()) { DecodeExceptionGen(instr); } else if (instr->IsSystemOp()) { DecodeSystem(instr); } else if (instr->IsTestAndBranchOp()) { DecodeTestAndBranch(instr); } else if (instr->IsUnconditionalBranchOp()) { DecodeUnconditionalBranch(instr); } else if (instr->IsUnconditionalBranchRegOp()) { DecodeUnconditionalBranchReg(instr); } else { UnimplementedInstruction(instr); } } void Simulator::DecodeLoadStoreReg(Instr* instr) { // Calculate the address. const Register rn = instr->RnField(); const Register rt = instr->RtField(); const VRegister vt = instr->VtField(); const int64_t rn_val = get_register(rn, R31IsSP); const uint32_t size = (instr->Bit(26) == 1) ? ((instr->Bit(23) << 2) | instr->SzField()) : instr->SzField(); uword address = 0; uword wb_address = 0; bool wb = false; if (instr->Bit(24) == 1) { // addr = rn + scaled unsigned 12-bit immediate offset. const uint32_t imm12 = static_cast(instr->Imm12Field()); const uint32_t offset = imm12 << size; address = rn_val + offset; } else if (instr->Bits(10, 2) == 0) { // addr = rn + signed 9-bit immediate offset. wb = false; const int64_t offset = static_cast(instr->SImm9Field()); address = rn_val + offset; wb_address = rn_val; } else if (instr->Bit(10) == 1) { // addr = rn + signed 9-bit immediate offset. wb = true; const int64_t offset = static_cast(instr->SImm9Field()); if (instr->Bit(11) == 1) { // Pre-index. address = rn_val + offset; wb_address = address; } else { // Post-index. address = rn_val; wb_address = rn_val + offset; } } else if (instr->Bits(10, 2) == 2) { // addr = rn + (rm EXT optionally scaled by operand instruction size). const Register rm = instr->RmField(); const Extend ext = instr->ExtendTypeField(); const uint8_t scale = (ext == UXTX) && (instr->Bit(12) == 1) ? size : 0; const int64_t rm_val = get_register(rm, R31IsZR); const int64_t offset = ExtendOperand(kXRegSizeInBits, rm_val, ext, scale); address = rn_val + offset; } else { UnimplementedInstruction(instr); return; } // Check the address. if (IsIllegalAddress(address)) { HandleIllegalAccess(address, instr); return; } // Do access. if (instr->Bit(26) == 1) { if (instr->Bit(22) == 0) { // Format(instr, "fstr'fsz 'vt, 'memop"); const int64_t vt_val = get_vregisterd(vt, 0); switch (size) { case 2: WriteW(address, vt_val & kWRegMask, instr); break; case 3: WriteX(address, vt_val, instr); break; case 4: { simd_value_t val; get_vregister(vt, &val); WriteX(address, val.bits.i64[0], instr); WriteX(address + kWordSize, val.bits.i64[1], instr); break; } default: UnimplementedInstruction(instr); return; } } else { // Format(instr, "fldr'fsz 'vt, 'memop"); switch (size) { case 2: set_vregisterd(vt, 0, static_cast(ReadWU(address, instr))); set_vregisterd(vt, 1, 0); break; case 3: set_vregisterd(vt, 0, ReadX(address, instr)); set_vregisterd(vt, 1, 0); break; case 4: { simd_value_t val; val.bits.i64[0] = ReadX(address, instr); val.bits.i64[1] = ReadX(address + kWordSize, instr); set_vregister(vt, val); break; } default: UnimplementedInstruction(instr); return; } } } else { if (instr->Bits(22, 2) == 0) { // Format(instr, "str'sz 'rt, 'memop"); const int32_t rt_val32 = get_wregister(rt, R31IsZR); switch (size) { case 0: { const uint8_t val = static_cast(rt_val32); WriteB(address, val); break; } case 1: { const uint16_t val = static_cast(rt_val32); WriteH(address, val, instr); break; } case 2: { const uint32_t val = static_cast(rt_val32); WriteW(address, val, instr); break; } case 3: { const int64_t val = get_register(rt, R31IsZR); WriteX(address, val, instr); break; } default: UNREACHABLE(); break; } } else { // Format(instr, "ldr'sz 'rt, 'memop"); // Undefined case. if ((size == 3) && (instr->Bits(22, 2) == 3)) { UnimplementedInstruction(instr); return; } // Read the value. const bool signd = instr->Bit(23) == 1; // Write the W register for signed values when size < 2. // Write the W register for unsigned values when size == 2. const bool use_w = (signd && (instr->Bit(22) == 1)) || (!signd && (size == 2)); int64_t val = 0; // Sign extend into an int64_t. switch (size) { case 0: { if (signd) { val = static_cast(ReadB(address)); } else { val = static_cast(ReadBU(address)); } break; } case 1: { if (signd) { val = static_cast(ReadH(address, instr)); } else { val = static_cast(ReadHU(address, instr)); } break; } case 2: { if (signd) { val = static_cast(ReadW(address, instr)); } else { val = static_cast(ReadWU(address, instr)); } break; } case 3: val = ReadX(address, instr); break; default: UNREACHABLE(); break; } // Write to register. if (use_w) { set_wregister(rt, static_cast(val), R31IsZR); } else { set_register(instr, rt, val, R31IsZR); } } } // Do writeback. if (wb) { set_register(instr, rn, wb_address, R31IsSP); } } void Simulator::DecodeLoadStoreRegPair(Instr* instr) { const int32_t opc = instr->Bits(23, 3); const Register rn = instr->RnField(); const Register rt = instr->RtField(); const Register rt2 = instr->Rt2Field(); const int64_t rn_val = get_register(rn, R31IsSP); const intptr_t shift = 2 + instr->SFField(); const intptr_t size = 1 << shift; const int32_t offset = (static_cast(instr->SImm7Field()) << shift); uword address = 0; uword wb_address = 0; bool wb = false; if ((instr->Bits(30, 2) == 3) || (instr->Bit(26) != 0)) { UnimplementedInstruction(instr); return; } // Calculate address. switch (opc) { case 1: address = rn_val; wb_address = rn_val + offset; wb = true; break; case 2: address = rn_val + offset; break; case 3: address = rn_val + offset; wb_address = address; wb = true; break; default: UnimplementedInstruction(instr); return; } // Check the address. if (IsIllegalAddress(address)) { HandleIllegalAccess(address, instr); return; } // Do access. if (instr->Bit(22)) { // Format(instr, "ldp'sf 'rt, 'ra, 'memop"); const bool signd = instr->Bit(30) == 1; int64_t val1 = 0; // Sign extend into an int64_t. int64_t val2 = 0; if (instr->Bit(31) == 1) { // 64-bit read. val1 = ReadX(address, instr); val2 = ReadX(address + size, instr); } else { if (signd) { val1 = static_cast(ReadW(address, instr)); val2 = static_cast(ReadW(address + size, instr)); } else { val1 = static_cast(ReadWU(address, instr)); val2 = static_cast(ReadWU(address + size, instr)); } } // Write to register. if (instr->Bit(31) == 1) { set_register(instr, rt, val1, R31IsZR); set_register(instr, rt2, val2, R31IsZR); } else { set_wregister(rt, static_cast(val1), R31IsZR); set_wregister(rt2, static_cast(val2), R31IsZR); } } else { // Format(instr, "stp'sf 'rt, 'ra, 'memop"); if (instr->Bit(31) == 1) { const int64_t val1 = get_register(rt, R31IsZR); const int64_t val2 = get_register(rt2, R31IsZR); WriteX(address, val1, instr); WriteX(address + size, val2, instr); } else { const int32_t val1 = get_wregister(rt, R31IsZR); const int32_t val2 = get_wregister(rt2, R31IsZR); WriteW(address, val1, instr); WriteW(address + size, val2, instr); } } // Do writeback. if (wb) { set_register(instr, rn, wb_address, R31IsSP); } } void Simulator::DecodeLoadRegLiteral(Instr* instr) { if ((instr->Bit(31) != 0) || (instr->Bit(29) != 0) || (instr->Bits(24, 3) != 0)) { UnimplementedInstruction(instr); } const Register rt = instr->RtField(); const int64_t off = instr->SImm19Field() << 2; const int64_t pc = reinterpret_cast(instr); const int64_t address = pc + off; const int64_t val = ReadX(address, instr); if (instr->Bit(30)) { // Format(instr, "ldrx 'rt, 'pcldr"); set_register(instr, rt, val, R31IsZR); } else { // Format(instr, "ldrw 'rt, 'pcldr"); set_wregister(rt, static_cast(val), R31IsZR); } } void Simulator::DecodeLoadStoreExclusive(Instr* instr) { if (instr->Bit(21) != 0 || instr->Bit(23) != instr->Bit(15)) { UNIMPLEMENTED(); } const int32_t size = instr->Bits(30, 2); if (size != 3 && size != 2) { UNIMPLEMENTED(); } const Register rs = instr->RsField(); const Register rn = instr->RnField(); const Register rt = instr->RtField(); ASSERT(instr->Rt2Field() == R31); // Should-Be-One const bool is_load = instr->Bit(22) == 1; const bool is_exclusive = instr->Bit(23) == 0; const bool is_ordered = instr->Bit(15) == 1; if (is_load) { const bool is_load_acquire = !is_exclusive && is_ordered; if (is_load_acquire) { ASSERT(rs == R31); // Should-Be-One // Format(instr, "ldar 'rt, 'rn"); const int64_t addr = get_register(rn, R31IsSP); const intptr_t value = (size == 3) ? ReadAcquire(addr, instr) : ReadAcquireW(addr, instr); set_register(instr, rt, value, R31IsSP); } else { ASSERT(rs == R31); // Should-Be-One // Format(instr, "ldxr 'rt, 'rn"); const int64_t addr = get_register(rn, R31IsSP); const intptr_t value = (size == 3) ? ReadExclusiveX(addr, instr) : ReadExclusiveW(addr, instr); set_register(instr, rt, value, R31IsSP); } } else { const bool is_store_release = !is_exclusive && is_ordered; if (is_store_release) { ASSERT(rs == R31); // Should-Be-One // Format(instr, "stlr 'rt, 'rn"); const uword value = get_register(rt, R31IsSP); const uword addr = get_register(rn, R31IsSP); if (size == 3) { WriteRelease(addr, value, instr); } else { WriteReleaseW(addr, static_cast(value), instr); } } else { // Format(instr, "stxr 'rs, 'rt, 'rn"); const uword value = get_register(rt, R31IsSP); const uword addr = get_register(rn, R31IsSP); const intptr_t status = (size == 3) ? WriteExclusiveX(addr, value, instr) : WriteExclusiveW(addr, static_cast(value), instr); set_register(instr, rs, status, R31IsSP); } } } void Simulator::DecodeLoadStore(Instr* instr) { if (instr->IsLoadStoreRegOp()) { DecodeLoadStoreReg(instr); } else if (instr->IsLoadStoreRegPairOp()) { DecodeLoadStoreRegPair(instr); } else if (instr->IsLoadRegLiteralOp()) { DecodeLoadRegLiteral(instr); } else if (instr->IsLoadStoreExclusiveOp()) { DecodeLoadStoreExclusive(instr); } else { UnimplementedInstruction(instr); } } int64_t Simulator::ShiftOperand(uint8_t reg_size, int64_t value, Shift shift_type, uint8_t amount) { if (amount == 0) { return value; } int64_t mask = reg_size == kXRegSizeInBits ? kXRegMask : kWRegMask; switch (shift_type) { case LSL: return (static_cast(value) << amount) & mask; case LSR: return static_cast(value) >> amount; case ASR: { // Shift used to restore the sign. uint8_t s_shift = kXRegSizeInBits - reg_size; // Value with its sign restored. int64_t s_value = (value << s_shift) >> s_shift; return (s_value >> amount) & mask; } case ROR: { if (reg_size == kWRegSizeInBits) { value &= kWRegMask; } return (static_cast(value) >> amount) | ((static_cast(value) & ((1ULL << amount) - 1ULL)) << (reg_size - amount)); } default: UNIMPLEMENTED(); return 0; } } int64_t Simulator::ExtendOperand(uint8_t reg_size, int64_t value, Extend extend_type, uint8_t amount) { switch (extend_type) { case UXTB: value &= 0xff; break; case UXTH: value &= 0xffff; break; case UXTW: value &= 0xffffffff; break; case SXTB: value = static_cast(static_cast(value) << 56) >> 56; break; case SXTH: value = static_cast(static_cast(value) << 48) >> 48; break; case SXTW: value = static_cast(static_cast(value) << 32) >> 32; break; case UXTX: case SXTX: break; default: UNREACHABLE(); break; } int64_t mask = (reg_size == kXRegSizeInBits) ? kXRegMask : kWRegMask; return (static_cast(value) << amount) & mask; } int64_t Simulator::DecodeShiftExtendOperand(Instr* instr) { const Register rm = instr->RmField(); const int64_t rm_val = get_register(rm, R31IsZR); const uint8_t size = instr->SFField() ? kXRegSizeInBits : kWRegSizeInBits; if (instr->IsShift()) { const Shift shift_type = instr->ShiftTypeField(); const uint8_t shift_amount = instr->Imm6Field(); return ShiftOperand(size, rm_val, shift_type, shift_amount); } else { ASSERT(instr->IsExtend()); const Extend extend_type = instr->ExtendTypeField(); const uint8_t shift_amount = instr->Imm3Field(); return ExtendOperand(size, rm_val, extend_type, shift_amount); } UNREACHABLE(); return -1; } void Simulator::DecodeAddSubShiftExt(Instr* instr) { // Format(instr, "add'sf's 'rd, 'rn, 'shift_op"); // also, sub, cmp, etc. const bool addition = (instr->Bit(30) == 0); const Register rd = instr->RdField(); const Register rn = instr->RnField(); const uint64_t rm_val = DecodeShiftExtendOperand(instr); if (instr->SFField()) { // 64-bit add. const uint64_t rn_val = get_register(rn, instr->RnMode()); const uint64_t alu_out = rn_val + (addition ? rm_val : -rm_val); set_register(instr, rd, alu_out, instr->RdMode()); if (instr->HasS()) { SetNZFlagsX(alu_out); SetCFlag(CarryFromX(alu_out, rn_val, rm_val, addition)); SetVFlag(OverflowFromX(alu_out, rn_val, rm_val, addition)); } } else { // 32-bit add. const uint32_t rn_val = get_wregister(rn, instr->RnMode()); uint32_t rm_val32 = static_cast(rm_val & kWRegMask); uint32_t carry_in = 0; if (!addition) { carry_in = 1; rm_val32 = ~rm_val32; } const uint32_t alu_out = rn_val + rm_val32 + carry_in; set_wregister(rd, alu_out, instr->RdMode()); if (instr->HasS()) { SetNZFlagsW(alu_out); SetCFlag(CarryFromW(rn_val, rm_val32, carry_in)); SetVFlag(OverflowFromW(rn_val, rm_val32, carry_in)); } } } void Simulator::DecodeAddSubWithCarry(Instr* instr) { // Format(instr, "adc'sf's 'rd, 'rn, 'rm"); // Format(instr, "sbc'sf's 'rd, 'rn, 'rm"); const bool addition = (instr->Bit(30) == 0); const Register rd = instr->RdField(); const Register rn = instr->RnField(); const Register rm = instr->RmField(); const uint64_t rn_val64 = get_register(rn, R31IsZR); const uint32_t rn_val32 = get_wregister(rn, R31IsZR); const uint64_t rm_val64 = get_register(rm, R31IsZR); uint32_t rm_val32 = get_wregister(rm, R31IsZR); const uint32_t carry_in = c_flag_ ? 1 : 0; if (instr->SFField()) { // 64-bit add. const uint64_t alu_out = rn_val64 + (addition ? rm_val64 : ~rm_val64) + carry_in; set_register(instr, rd, alu_out, R31IsZR); if (instr->HasS()) { SetNZFlagsX(alu_out); SetCFlag(CarryFromX(alu_out, rn_val64, rm_val64, addition)); SetVFlag(OverflowFromX(alu_out, rn_val64, rm_val64, addition)); } } else { // 32-bit add. if (!addition) { rm_val32 = ~rm_val32; } const uint32_t alu_out = rn_val32 + rm_val32 + carry_in; set_wregister(rd, alu_out, R31IsZR); if (instr->HasS()) { SetNZFlagsW(alu_out); SetCFlag(CarryFromW(rn_val32, rm_val32, carry_in)); SetVFlag(OverflowFromW(rn_val32, rm_val32, carry_in)); } } } void Simulator::DecodeLogicalShift(Instr* instr) { const int op = (instr->Bits(29, 2) << 1) | instr->Bit(21); const Register rd = instr->RdField(); const Register rn = instr->RnField(); const int64_t rn_val = get_register(rn, instr->RnMode()); const int64_t rm_val = DecodeShiftExtendOperand(instr); int64_t alu_out = 0; switch (op) { case 0: // Format(instr, "and'sf 'rd, 'rn, 'shift_op"); alu_out = rn_val & rm_val; break; case 1: // Format(instr, "bic'sf 'rd, 'rn, 'shift_op"); alu_out = rn_val & (~rm_val); break; case 2: // Format(instr, "orr'sf 'rd, 'rn, 'shift_op"); alu_out = rn_val | rm_val; break; case 3: // Format(instr, "orn'sf 'rd, 'rn, 'shift_op"); alu_out = rn_val | (~rm_val); break; case 4: // Format(instr, "eor'sf 'rd, 'rn, 'shift_op"); alu_out = rn_val ^ rm_val; break; case 5: // Format(instr, "eon'sf 'rd, 'rn, 'shift_op"); alu_out = rn_val ^ (~rm_val); break; case 6: // Format(instr, "and'sfs 'rd, 'rn, 'shift_op"); alu_out = rn_val & rm_val; break; case 7: // Format(instr, "bic'sfs 'rd, 'rn, 'shift_op"); alu_out = rn_val & (~rm_val); break; default: UNREACHABLE(); break; } // Set flags if ands or bics. if ((op == 6) || (op == 7)) { if (instr->SFField() == 1) { SetNZFlagsX(alu_out); } else { SetNZFlagsW(alu_out); } SetCFlag(false); SetVFlag(false); } if (instr->SFField() == 1) { set_register(instr, rd, alu_out, instr->RdMode()); } else { set_wregister(rd, alu_out & kWRegMask, instr->RdMode()); } } static int64_t divide64(int64_t top, int64_t bottom, bool signd) { // ARM64 does not trap on integer division by zero. The destination register // is instead set to 0. if (bottom == 0) { return 0; } if (signd) { // INT_MIN / -1 = INT_MIN. if ((top == static_cast(0x8000000000000000LL)) && (bottom == static_cast(0xffffffffffffffffLL))) { return static_cast(0x8000000000000000LL); } else { return top / bottom; } } else { const uint64_t utop = static_cast(top); const uint64_t ubottom = static_cast(bottom); return static_cast(utop / ubottom); } } static int32_t divide32(int32_t top, int32_t bottom, bool signd) { // ARM64 does not trap on integer division by zero. The destination register // is instead set to 0. if (bottom == 0) { return 0; } if (signd) { // INT_MIN / -1 = INT_MIN. if ((top == static_cast(0x80000000)) && (bottom == static_cast(0xffffffff))) { return static_cast(0x80000000); } else { return top / bottom; } } else { const uint32_t utop = static_cast(top); const uint32_t ubottom = static_cast(bottom); return static_cast(utop / ubottom); } } void Simulator::DecodeMiscDP1Source(Instr* instr) { if (instr->Bit(29) != 0) { UnimplementedInstruction(instr); } const Register rd = instr->RdField(); const Register rn = instr->RnField(); const int op = instr->Bits(10, 10); const int64_t rn_val64 = get_register(rn, R31IsZR); const int32_t rn_val32 = get_wregister(rn, R31IsZR); switch (op) { case 4: { // Format(instr, "clz'sf 'rd, 'rn"); if (instr->SFField() == 1) { const uint64_t rd_val = Utils::CountLeadingZeros64(rn_val64); set_register(instr, rd, rd_val, R31IsZR); } else { const uint32_t rd_val = Utils::CountLeadingZeros32(rn_val32); set_wregister(rd, rd_val, R31IsZR); } break; } case 0: { // Format(instr, "rbit'sf 'rd, 'rn"); if (instr->SFField() == 1) { const uint64_t rd_val = Utils::ReverseBits64(rn_val64); set_register(instr, rd, rd_val, R31IsZR); } else { const uint32_t rd_val = Utils::ReverseBits32(rn_val32); set_wregister(rd, rd_val, R31IsZR); } break; } default: UnimplementedInstruction(instr); break; } } void Simulator::DecodeMiscDP2Source(Instr* instr) { if (instr->Bit(29) != 0) { UnimplementedInstruction(instr); } const Register rd = instr->RdField(); const Register rn = instr->RnField(); const Register rm = instr->RmField(); const int op = instr->Bits(10, 5); const int64_t rn_val64 = get_register(rn, R31IsZR); const int64_t rm_val64 = get_register(rm, R31IsZR); const int32_t rn_val32 = get_wregister(rn, R31IsZR); const int32_t rm_val32 = get_wregister(rm, R31IsZR); switch (op) { case 2: case 3: { // Format(instr, "udiv'sf 'rd, 'rn, 'rm"); // Format(instr, "sdiv'sf 'rd, 'rn, 'rm"); const bool signd = instr->Bit(10) == 1; if (instr->SFField() == 1) { set_register(instr, rd, divide64(rn_val64, rm_val64, signd), R31IsZR); } else { set_wregister(rd, divide32(rn_val32, rm_val32, signd), R31IsZR); } break; } case 8: { // Format(instr, "lsl'sf 'rd, 'rn, 'rm"); if (instr->SFField() == 1) { const uint64_t rn_u64 = static_cast(rn_val64); const int64_t alu_out = rn_u64 << (rm_val64 & (kXRegSizeInBits - 1)); set_register(instr, rd, alu_out, R31IsZR); } else { const uint32_t rn_u32 = static_cast(rn_val32); const int32_t alu_out = rn_u32 << (rm_val32 & (kXRegSizeInBits - 1)); set_wregister(rd, alu_out, R31IsZR); } break; } case 9: { // Format(instr, "lsr'sf 'rd, 'rn, 'rm"); if (instr->SFField() == 1) { const uint64_t rn_u64 = static_cast(rn_val64); const int64_t alu_out = rn_u64 >> (rm_val64 & (kXRegSizeInBits - 1)); set_register(instr, rd, alu_out, R31IsZR); } else { const uint32_t rn_u32 = static_cast(rn_val32); const int32_t alu_out = rn_u32 >> (rm_val32 & (kXRegSizeInBits - 1)); set_wregister(rd, alu_out, R31IsZR); } break; } case 10: { // Format(instr, "asr'sf 'rd, 'rn, 'rm"); if (instr->SFField() == 1) { const int64_t alu_out = rn_val64 >> (rm_val64 & (kXRegSizeInBits - 1)); set_register(instr, rd, alu_out, R31IsZR); } else { const int32_t alu_out = rn_val32 >> (rm_val32 & (kXRegSizeInBits - 1)); set_wregister(rd, alu_out, R31IsZR); } break; } default: UnimplementedInstruction(instr); break; } } void Simulator::DecodeMiscDP3Source(Instr* instr) { const Register rd = instr->RdField(); const Register rn = instr->RnField(); const Register rm = instr->RmField(); const Register ra = instr->RaField(); if ((instr->Bits(29, 2) == 0) && (instr->Bits(21, 3) == 0) && (instr->Bit(15) == 0)) { // Format(instr, "madd'sf 'rd, 'rn, 'rm, 'ra"); if (instr->SFField() == 1) { const uint64_t rn_val = get_register(rn, R31IsZR); const uint64_t rm_val = get_register(rm, R31IsZR); const uint64_t ra_val = get_register(ra, R31IsZR); const uint64_t alu_out = ra_val + (rn_val * rm_val); set_register(instr, rd, alu_out, R31IsZR); } else { const uint32_t rn_val = get_wregister(rn, R31IsZR); const uint32_t rm_val = get_wregister(rm, R31IsZR); const uint32_t ra_val = get_wregister(ra, R31IsZR); const uint32_t alu_out = ra_val + (rn_val * rm_val); set_wregister(rd, alu_out, R31IsZR); } } else if ((instr->Bits(29, 2) == 0) && (instr->Bits(21, 3) == 0) && (instr->Bit(15) == 1)) { // Format(instr, "msub'sf 'rd, 'rn, 'rm, 'ra"); if (instr->SFField() == 1) { const uint64_t rn_val = get_register(rn, R31IsZR); const uint64_t rm_val = get_register(rm, R31IsZR); const uint64_t ra_val = get_register(ra, R31IsZR); const uint64_t alu_out = ra_val - (rn_val * rm_val); set_register(instr, rd, alu_out, R31IsZR); } else { const uint32_t rn_val = get_wregister(rn, R31IsZR); const uint32_t rm_val = get_wregister(rm, R31IsZR); const uint32_t ra_val = get_wregister(ra, R31IsZR); const uint32_t alu_out = ra_val - (rn_val * rm_val); set_wregister(rd, alu_out, R31IsZR); } } else if ((instr->Bits(29, 3) == 4) && (instr->Bits(21, 3) == 2) && (instr->Bit(15) == 0)) { ASSERT(ra == R31); // Should-Be-One // Format(instr, "smulh 'rd, 'rn, 'rm"); const int64_t rn_val = get_register(rn, R31IsZR); const int64_t rm_val = get_register(rm, R31IsZR); #if defined(HOST_OS_WINDOWS) // Visual Studio does not support __int128. int64_t alu_out; Multiply128(rn_val, rm_val, &alu_out); #else const __int128 res = static_cast<__int128>(rn_val) * static_cast<__int128>(rm_val); const int64_t alu_out = static_cast(res >> 64); #endif // HOST_OS_WINDOWS set_register(instr, rd, alu_out, R31IsZR); } else if ((instr->Bits(29, 3) == 4) && (instr->Bits(21, 3) == 6) && (instr->Bit(15) == 0)) { ASSERT(ra == R31); // Should-Be-One // Format(instr, "umulh 'rd, 'rn, 'rm"); const uint64_t rn_val = get_register(rn, R31IsZR); const uint64_t rm_val = get_register(rm, R31IsZR); #if defined(HOST_OS_WINDOWS) // Visual Studio does not support __int128. uint64_t alu_out; UnsignedMultiply128(rn_val, rm_val, &alu_out); #else const unsigned __int128 res = static_cast(rn_val) * static_cast(rm_val); const uint64_t alu_out = static_cast(res >> 64); #endif // HOST_OS_WINDOWS set_register(instr, rd, alu_out, R31IsZR); } else if ((instr->Bits(29, 3) == 4) && (instr->Bit(15) == 0)) { if (instr->Bits(21, 3) == 5) { // Format(instr, "umaddl 'rd, 'rn, 'rm, 'ra"); const uint64_t rn_val = static_cast(get_wregister(rn, R31IsZR)); const uint64_t rm_val = static_cast(get_wregister(rm, R31IsZR)); const uint64_t ra_val = get_register(ra, R31IsZR); const uint64_t alu_out = ra_val + (rn_val * rm_val); set_register(instr, rd, alu_out, R31IsZR); } else { // Format(instr, "smaddl 'rd, 'rn, 'rm, 'ra"); const int64_t rn_val = static_cast(get_wregister(rn, R31IsZR)); const int64_t rm_val = static_cast(get_wregister(rm, R31IsZR)); const int64_t ra_val = get_register(ra, R31IsZR); const int64_t alu_out = ra_val + (rn_val * rm_val); set_register(instr, rd, alu_out, R31IsZR); } } else { UnimplementedInstruction(instr); } } void Simulator::DecodeConditionalSelect(Instr* instr) { const Register rd = instr->RdField(); const Register rn = instr->RnField(); const Register rm = instr->RmField(); const int64_t rm_val64 = get_register(rm, R31IsZR); const int32_t rm_val32 = get_wregister(rm, R31IsZR); const int64_t rn_val64 = get_register(rn, instr->RnMode()); const int32_t rn_val32 = get_wregister(rn, instr->RnMode()); int64_t result64 = 0; int32_t result32 = 0; if ((instr->Bits(29, 2) == 0) && (instr->Bits(10, 2) == 0)) { // Format(instr, "mov'sf'cond 'rd, 'rn, 'rm"); result64 = rm_val64; result32 = rm_val32; if (ConditionallyExecute(instr)) { result64 = rn_val64; result32 = rn_val32; } } else if ((instr->Bits(29, 2) == 0) && (instr->Bits(10, 2) == 1)) { // Format(instr, "csinc'sf'cond 'rd, 'rn, 'rm"); result64 = rm_val64 + 1; result32 = rm_val32 + 1; if (ConditionallyExecute(instr)) { result64 = rn_val64; result32 = rn_val32; } } else if ((instr->Bits(29, 2) == 2) && (instr->Bits(10, 2) == 0)) { // Format(instr, "csinv'sf'cond 'rd, 'rn, 'rm"); result64 = ~rm_val64; result32 = ~rm_val32; if (ConditionallyExecute(instr)) { result64 = rn_val64; result32 = rn_val32; } } else if ((instr->Bits(29, 2) == 2) && (instr->Bits(10, 2) == 1)) { // Format(instr, "csneg'sf'cond 'rd, 'rn, 'rm"); result64 = -rm_val64; result32 = -rm_val32; if (ConditionallyExecute(instr)) { result64 = rn_val64; result32 = rn_val32; } } else { UnimplementedInstruction(instr); return; } if (instr->SFField() == 1) { set_register(instr, rd, result64, instr->RdMode()); } else { set_wregister(rd, result32, instr->RdMode()); } } void Simulator::DecodeDPRegister(Instr* instr) { if (instr->IsAddSubShiftExtOp()) { DecodeAddSubShiftExt(instr); } else if (instr->IsAddSubWithCarryOp()) { DecodeAddSubWithCarry(instr); } else if (instr->IsLogicalShiftOp()) { DecodeLogicalShift(instr); } else if (instr->IsMiscDP1SourceOp()) { DecodeMiscDP1Source(instr); } else if (instr->IsMiscDP2SourceOp()) { DecodeMiscDP2Source(instr); } else if (instr->IsMiscDP3SourceOp()) { DecodeMiscDP3Source(instr); } else if (instr->IsConditionalSelectOp()) { DecodeConditionalSelect(instr); } else { UnimplementedInstruction(instr); } } void Simulator::DecodeSIMDCopy(Instr* instr) { const int32_t Q = instr->Bit(30); const int32_t op = instr->Bit(29); const int32_t imm4 = instr->Bits(11, 4); const int32_t imm5 = instr->Bits(16, 5); int32_t idx4 = -1; int32_t idx5 = -1; int32_t element_bytes; if (imm5 & 0x1) { idx4 = imm4; idx5 = imm5 >> 1; element_bytes = 1; } else if (imm5 & 0x2) { idx4 = imm4 >> 1; idx5 = imm5 >> 2; element_bytes = 2; } else if (imm5 & 0x4) { idx4 = imm4 >> 2; idx5 = imm5 >> 3; element_bytes = 4; } else if (imm5 & 0x8) { idx4 = imm4 >> 3; idx5 = imm5 >> 4; element_bytes = 8; } else { UnimplementedInstruction(instr); return; } ASSERT((idx4 != -1) && (idx5 != -1)); const VRegister vd = instr->VdField(); const VRegister vn = instr->VnField(); const Register rn = instr->RnField(); const Register rd = instr->RdField(); if ((op == 0) && (imm4 == 7)) { if (Q == 0) { // Format(instr, "vmovrs 'rd, 'vn'idx5"); set_wregister(rd, get_vregisters(vn, idx5), R31IsZR); } else { // Format(instr, "vmovrd 'rd, 'vn'idx5"); set_register(instr, rd, get_vregisterd(vn, idx5), R31IsZR); } } else if ((Q == 1) && (op == 0) && (imm4 == 0)) { // Format(instr, "vdup'csz 'vd, 'vn'idx5"); if (element_bytes == 4) { for (int i = 0; i < 4; i++) { set_vregisters(vd, i, get_vregisters(vn, idx5)); } } else if (element_bytes == 8) { for (int i = 0; i < 2; i++) { set_vregisterd(vd, i, get_vregisterd(vn, idx5)); } } else { UnimplementedInstruction(instr); return; } } else if ((Q == 1) && (op == 0) && (imm4 == 3)) { // Format(instr, "vins'csz 'vd'idx5, 'rn"); if (element_bytes == 4) { set_vregisters(vd, idx5, get_wregister(rn, R31IsZR)); } else if (element_bytes == 8) { set_vregisterd(vd, idx5, get_register(rn, R31IsZR)); } else { UnimplementedInstruction(instr); } } else if ((Q == 1) && (op == 0) && (imm4 == 1)) { // Format(instr, "vdup'csz 'vd, 'rn"); if (element_bytes == 4) { for (int i = 0; i < 4; i++) { set_vregisters(vd, i, get_wregister(rn, R31IsZR)); } } else if (element_bytes == 8) { for (int i = 0; i < 2; i++) { set_vregisterd(vd, i, get_register(rn, R31IsZR)); } } else { UnimplementedInstruction(instr); return; } } else if ((Q == 1) && (op == 1)) { // Format(instr, "vins'csz 'vd'idx5, 'vn'idx4"); if (element_bytes == 4) { set_vregisters(vd, idx5, get_vregisters(vn, idx4)); } else if (element_bytes == 8) { set_vregisterd(vd, idx5, get_vregisterd(vn, idx4)); } else { UnimplementedInstruction(instr); } } else { UnimplementedInstruction(instr); } } void Simulator::DecodeSIMDThreeSame(Instr* instr) { const int Q = instr->Bit(30); const int U = instr->Bit(29); const int opcode = instr->Bits(11, 5); if (Q == 0) { UnimplementedInstruction(instr); return; } const VRegister vd = instr->VdField(); const VRegister vn = instr->VnField(); const VRegister vm = instr->VmField(); if (instr->Bit(22) == 0) { // f32 case. for (int idx = 0; idx < 4; idx++) { const int32_t vn_val = get_vregisters(vn, idx); const int32_t vm_val = get_vregisters(vm, idx); const float vn_flt = bit_cast(vn_val); const float vm_flt = bit_cast(vm_val); int32_t res = 0.0; if ((U == 0) && (opcode == 0x3)) { if (instr->Bit(23) == 0) { // Format(instr, "vand 'vd, 'vn, 'vm"); res = vn_val & vm_val; } else { // Format(instr, "vorr 'vd, 'vn, 'vm"); res = vn_val | vm_val; } } else if ((U == 1) && (opcode == 0x3)) { // Format(instr, "veor 'vd, 'vn, 'vm"); res = vn_val ^ vm_val; } else if ((U == 0) && (opcode == 0x10)) { // Format(instr, "vadd'vsz 'vd, 'vn, 'vm"); res = vn_val + vm_val; } else if ((U == 1) && (opcode == 0x10)) { // Format(instr, "vsub'vsz 'vd, 'vn, 'vm"); res = vn_val - vm_val; } else if ((U == 0) && (opcode == 0x1a)) { if (instr->Bit(23) == 0) { // Format(instr, "vadd'vsz 'vd, 'vn, 'vm"); res = bit_cast(vn_flt + vm_flt); } else { // Format(instr, "vsub'vsz 'vd, 'vn, 'vm"); res = bit_cast(vn_flt - vm_flt); } } else if ((U == 1) && (opcode == 0x1b)) { // Format(instr, "vmul'vsz 'vd, 'vn, 'vm"); res = bit_cast(vn_flt * vm_flt); } else if ((U == 1) && (opcode == 0x1f)) { // Format(instr, "vdiv'vsz 'vd, 'vn, 'vm"); res = bit_cast(vn_flt / vm_flt); } else if ((U == 0) && (opcode == 0x1c)) { // Format(instr, "vceq'vsz 'vd, 'vn, 'vm"); res = (vn_flt == vm_flt) ? 0xffffffff : 0; } else if ((U == 1) && (opcode == 0x1c)) { if (instr->Bit(23) == 1) { // Format(instr, "vcgt'vsz 'vd, 'vn, 'vm"); res = (vn_flt > vm_flt) ? 0xffffffff : 0; } else { // Format(instr, "vcge'vsz 'vd, 'vn, 'vm"); res = (vn_flt >= vm_flt) ? 0xffffffff : 0; } } else if ((U == 0) && (opcode == 0x1e)) { if (instr->Bit(23) == 1) { // Format(instr, "vmin'vsz 'vd, 'vn, 'vm"); const float m = fminf(vn_flt, vm_flt); res = bit_cast(m); } else { // Format(instr, "vmax'vsz 'vd, 'vn, 'vm"); const float m = fmaxf(vn_flt, vm_flt); res = bit_cast(m); } } else if ((U == 0) && (opcode == 0x1f)) { if (instr->Bit(23) == 0) { // Format(instr, "vrecps'vsz 'vd, 'vn, 'vm"); res = bit_cast(2.0 - (vn_flt * vm_flt)); } else { // Format(instr, "vrsqrt'vsz 'vd, 'vn, 'vm"); res = bit_cast((3.0 - vn_flt * vm_flt) / 2.0); } } else { UnimplementedInstruction(instr); return; } set_vregisters(vd, idx, res); } } else { // f64 case. for (int idx = 0; idx < 2; idx++) { const int64_t vn_val = get_vregisterd(vn, idx); const int64_t vm_val = get_vregisterd(vm, idx); const double vn_dbl = bit_cast(vn_val); const double vm_dbl = bit_cast(vm_val); int64_t res = 0.0; if ((U == 0) && (opcode == 0x3)) { if (instr->Bit(23) == 0) { // Format(instr, "vand 'vd, 'vn, 'vm"); res = vn_val & vm_val; } else { // Format(instr, "vorr 'vd, 'vn, 'vm"); res = vn_val | vm_val; } } else if ((U == 1) && (opcode == 0x3)) { // Format(instr, "veor 'vd, 'vn, 'vm"); res = vn_val ^ vm_val; } else if ((U == 0) && (opcode == 0x10)) { // Format(instr, "vadd'vsz 'vd, 'vn, 'vm"); res = vn_val + vm_val; } else if ((U == 1) && (opcode == 0x10)) { // Format(instr, "vsub'vsz 'vd, 'vn, 'vm"); res = vn_val - vm_val; } else if ((U == 0) && (opcode == 0x1a)) { if (instr->Bit(23) == 0) { // Format(instr, "vadd'vsz 'vd, 'vn, 'vm"); res = bit_cast(vn_dbl + vm_dbl); } else { // Format(instr, "vsub'vsz 'vd, 'vn, 'vm"); res = bit_cast(vn_dbl - vm_dbl); } } else if ((U == 1) && (opcode == 0x1b)) { // Format(instr, "vmul'vsz 'vd, 'vn, 'vm"); res = bit_cast(vn_dbl * vm_dbl); } else if ((U == 1) && (opcode == 0x1f)) { // Format(instr, "vdiv'vsz 'vd, 'vn, 'vm"); res = bit_cast(vn_dbl / vm_dbl); } else if ((U == 0) && (opcode == 0x1c)) { // Format(instr, "vceq'vsz 'vd, 'vn, 'vm"); res = (vn_dbl == vm_dbl) ? 0xffffffffffffffffLL : 0; } else if ((U == 1) && (opcode == 0x1c)) { if (instr->Bit(23) == 1) { // Format(instr, "vcgt'vsz 'vd, 'vn, 'vm"); res = (vn_dbl > vm_dbl) ? 0xffffffffffffffffLL : 0; } else { // Format(instr, "vcge'vsz 'vd, 'vn, 'vm"); res = (vn_dbl >= vm_dbl) ? 0xffffffffffffffffLL : 0; } } else if ((U == 0) && (opcode == 0x1e)) { if (instr->Bit(23) == 1) { // Format(instr, "vmin'vsz 'vd, 'vn, 'vm"); const double m = fmin(vn_dbl, vm_dbl); res = bit_cast(m); } else { // Format(instr, "vmax'vsz 'vd, 'vn, 'vm"); const double m = fmax(vn_dbl, vm_dbl); res = bit_cast(m); } } else { UnimplementedInstruction(instr); return; } set_vregisterd(vd, idx, res); } } } static float arm_reciprocal_sqrt_estimate(float a) { // From the ARM Architecture Reference Manual A2-87. if (isinf(a) || (fabs(a) >= exp2f(126))) return 0.0; else if (a == 0.0) return kPosInfinity; else if (isnan(a)) return a; uint32_t a_bits = bit_cast(a); uint64_t scaled; if (((a_bits >> 23) & 1) != 0) { // scaled = '0 01111111101' : operand<22:0> : Zeros(29) scaled = (static_cast(0x3fd) << 52) | ((static_cast(a_bits) & 0x7fffff) << 29); } else { // scaled = '0 01111111110' : operand<22:0> : Zeros(29) scaled = (static_cast(0x3fe) << 52) | ((static_cast(a_bits) & 0x7fffff) << 29); } // result_exp = (380 - UInt(operand<30:23>) DIV 2; int32_t result_exp = (380 - ((a_bits >> 23) & 0xff)) / 2; double scaled_d = bit_cast(scaled); ASSERT((scaled_d >= 0.25) && (scaled_d < 1.0)); double r; if (scaled_d < 0.5) { // range 0.25 <= a < 0.5 // a in units of 1/512 rounded down. int32_t q0 = static_cast(scaled_d * 512.0); // reciprocal root r. r = 1.0 / sqrt((static_cast(q0) + 0.5) / 512.0); } else { // range 0.5 <= a < 1.0 // a in units of 1/256 rounded down. int32_t q1 = static_cast(scaled_d * 256.0); // reciprocal root r. r = 1.0 / sqrt((static_cast(q1) + 0.5) / 256.0); } // r in units of 1/256 rounded to nearest. int32_t s = static_cast(256.0 * r + 0.5); double estimate = static_cast(s) / 256.0; ASSERT((estimate >= 1.0) && (estimate <= (511.0 / 256.0))); // result = 0 : result_exp<7:0> : estimate<51:29> int32_t result_bits = ((result_exp & 0xff) << 23) | ((bit_cast(estimate) >> 29) & 0x7fffff); return bit_cast(result_bits); } static float arm_recip_estimate(float a) { // From the ARM Architecture Reference Manual A2-85. if (isinf(a) || (fabs(a) >= exp2f(126))) return 0.0; else if (a == 0.0) return kPosInfinity; else if (isnan(a)) return a; uint32_t a_bits = bit_cast(a); // scaled = '0011 1111 1110' : a<22:0> : Zeros(29) uint64_t scaled = (static_cast(0x3fe) << 52) | ((static_cast(a_bits) & 0x7fffff) << 29); // result_exp = 253 - UInt(a<30:23>) int32_t result_exp = 253 - ((a_bits >> 23) & 0xff); ASSERT((result_exp >= 1) && (result_exp <= 252)); double scaled_d = bit_cast(scaled); ASSERT((scaled_d >= 0.5) && (scaled_d < 1.0)); // a in units of 1/512 rounded down. int32_t q = static_cast(scaled_d * 512.0); // reciprocal r. double r = 1.0 / ((static_cast(q) + 0.5) / 512.0); // r in units of 1/256 rounded to nearest. int32_t s = static_cast(256.0 * r + 0.5); double estimate = static_cast(s) / 256.0; ASSERT((estimate >= 1.0) && (estimate <= (511.0 / 256.0))); // result = sign : result_exp<7:0> : estimate<51:29> int32_t result_bits = (a_bits & 0x80000000) | ((result_exp & 0xff) << 23) | ((bit_cast(estimate) >> 29) & 0x7fffff); return bit_cast(result_bits); } void Simulator::DecodeSIMDTwoReg(Instr* instr) { const int32_t Q = instr->Bit(30); const int32_t U = instr->Bit(29); const int32_t op = instr->Bits(12, 5); const int32_t sz = instr->Bits(22, 2); const VRegister vd = instr->VdField(); const VRegister vn = instr->VnField(); if (Q != 1) { UnimplementedInstruction(instr); return; } if ((U == 1) && (op == 5)) { // Format(instr, "vnot 'vd, 'vn"); for (int i = 0; i < 2; i++) { set_vregisterd(vd, i, ~get_vregisterd(vn, i)); } } else if ((U == 0) && (op == 0xf)) { if (sz == 2) { // Format(instr, "vabss 'vd, 'vn"); for (int i = 0; i < 4; i++) { const int32_t vn_val = get_vregisters(vn, i); const float vn_flt = bit_cast(vn_val); set_vregisters(vd, i, bit_cast(fabsf(vn_flt))); } } else if (sz == 3) { // Format(instr, "vabsd 'vd, 'vn"); for (int i = 0; i < 2; i++) { const int64_t vn_val = get_vregisterd(vn, i); const double vn_dbl = bit_cast(vn_val); set_vregisterd(vd, i, bit_cast(fabs(vn_dbl))); } } else { UnimplementedInstruction(instr); } } else if ((U == 1) && (op == 0xf)) { if (sz == 2) { // Format(instr, "vnegs 'vd, 'vn"); for (int i = 0; i < 4; i++) { const int32_t vn_val = get_vregisters(vn, i); const float vn_flt = bit_cast(vn_val); set_vregisters(vd, i, bit_cast(-vn_flt)); } } else if (sz == 3) { // Format(instr, "vnegd 'vd, 'vn"); for (int i = 0; i < 2; i++) { const int64_t vn_val = get_vregisterd(vn, i); const double vn_dbl = bit_cast(vn_val); set_vregisterd(vd, i, bit_cast(-vn_dbl)); } } else { UnimplementedInstruction(instr); } } else if ((U == 1) && (op == 0x1f)) { if (sz == 2) { // Format(instr, "vsqrts 'vd, 'vn"); for (int i = 0; i < 4; i++) { const int32_t vn_val = get_vregisters(vn, i); const float vn_flt = bit_cast(vn_val); set_vregisters(vd, i, bit_cast(sqrtf(vn_flt))); } } else if (sz == 3) { // Format(instr, "vsqrtd 'vd, 'vn"); for (int i = 0; i < 2; i++) { const int64_t vn_val = get_vregisterd(vn, i); const double vn_dbl = bit_cast(vn_val); set_vregisterd(vd, i, bit_cast(sqrt(vn_dbl))); } } else { UnimplementedInstruction(instr); } } else if ((U == 0) && (op == 0x1d)) { if (sz != 2) { UnimplementedInstruction(instr); return; } // Format(instr, "vrecpes 'vd, 'vn"); for (int i = 0; i < 4; i++) { const int32_t vn_val = get_vregisters(vn, i); const float vn_flt = bit_cast(vn_val); const float re = arm_recip_estimate(vn_flt); set_vregisters(vd, i, bit_cast(re)); } } else if ((U == 1) && (op == 0x1d)) { if (sz != 2) { UnimplementedInstruction(instr); return; } // Format(instr, "vrsqrtes 'vd, 'vn"); for (int i = 0; i < 4; i++) { const int32_t vn_val = get_vregisters(vn, i); const float vn_flt = bit_cast(vn_val); const float re = arm_reciprocal_sqrt_estimate(vn_flt); set_vregisters(vd, i, bit_cast(re)); } } else { UnimplementedInstruction(instr); } } void Simulator::DecodeDPSimd1(Instr* instr) { if (instr->IsSIMDCopyOp()) { DecodeSIMDCopy(instr); } else if (instr->IsSIMDThreeSameOp()) { DecodeSIMDThreeSame(instr); } else if (instr->IsSIMDTwoRegOp()) { DecodeSIMDTwoReg(instr); } else { UnimplementedInstruction(instr); } } void Simulator::DecodeFPImm(Instr* instr) { if ((instr->Bit(31) != 0) || (instr->Bit(29) != 0) || (instr->Bit(23) != 0) || (instr->Bits(5, 5) != 0)) { UnimplementedInstruction(instr); return; } if (instr->Bit(22) == 1) { // Double. // Format(instr, "fmovd 'vd, #'immd"); const VRegister vd = instr->VdField(); const int64_t immd = Instr::VFPExpandImm(instr->Imm8Field()); set_vregisterd(vd, 0, immd); set_vregisterd(vd, 1, 0); } else { // Single. UnimplementedInstruction(instr); } } void Simulator::DecodeFPIntCvt(Instr* instr) { const VRegister vd = instr->VdField(); const VRegister vn = instr->VnField(); const Register rd = instr->RdField(); const Register rn = instr->RnField(); if (instr->Bit(29) != 0) { UnimplementedInstruction(instr); return; } if ((instr->SFField() == 0) && (instr->Bits(22, 2) == 0)) { if (instr->Bits(16, 5) == 6) { // Format(instr, "fmovrs'sf 'rd, 'vn"); const int32_t vn_val = get_vregisters(vn, 0); set_wregister(rd, vn_val, R31IsZR); } else if (instr->Bits(16, 5) == 7) { // Format(instr, "fmovsr'sf 'vd, 'rn"); const int32_t rn_val = get_wregister(rn, R31IsZR); set_vregisters(vd, 0, rn_val); set_vregisters(vd, 1, 0); set_vregisters(vd, 2, 0); set_vregisters(vd, 3, 0); } else { UnimplementedInstruction(instr); } } else if (instr->Bits(22, 2) == 1) { if (instr->Bits(16, 5) == 2) { // Format(instr, "scvtfd'sf 'vd, 'rn"); const int64_t rn_val64 = get_register(rn, instr->RnMode()); const int32_t rn_val32 = get_wregister(rn, instr->RnMode()); const double vn_dbl = (instr->SFField() == 1) ? static_cast(rn_val64) : static_cast(rn_val32); set_vregisterd(vd, 0, bit_cast(vn_dbl)); set_vregisterd(vd, 1, 0); } else if (instr->Bits(16, 5) == 6) { // Format(instr, "fmovrd'sf 'rd, 'vn"); const int64_t vn_val = get_vregisterd(vn, 0); set_register(instr, rd, vn_val, R31IsZR); } else if (instr->Bits(16, 5) == 7) { // Format(instr, "fmovdr'sf 'vd, 'rn"); const int64_t rn_val = get_register(rn, R31IsZR); set_vregisterd(vd, 0, rn_val); set_vregisterd(vd, 1, 0); } else if (instr->Bits(16, 5) == 24) { // Format(instr, "fcvtzds'sf 'rd, 'vn"); const intptr_t max = instr->Bit(31) == 1 ? INT64_MAX : INT32_MAX; const intptr_t min = instr->Bit(31) == 1 ? INT64_MIN : INT32_MIN; const double vn_val = bit_cast(get_vregisterd(vn, 0)); int64_t result; if (vn_val >= static_cast(max)) { result = max; } else if (vn_val <= static_cast(min)) { result = min; } else { result = static_cast(vn_val); } if (instr->Bit(31) == 1) { set_register(instr, rd, result, instr->RdMode()); } else { set_register(instr, rd, result & 0xffffffffll, instr->RdMode()); } } else { UnimplementedInstruction(instr); } } else { UnimplementedInstruction(instr); } } void Simulator::DecodeFPOneSource(Instr* instr) { const int opc = instr->Bits(15, 6); const VRegister vd = instr->VdField(); const VRegister vn = instr->VnField(); const int64_t vn_val = get_vregisterd(vn, 0); const int32_t vn_val32 = vn_val & kWRegMask; const double vn_dbl = bit_cast(vn_val); const float vn_flt = bit_cast(vn_val32); if ((opc != 5) && (instr->Bit(22) != 1)) { // Source is interpreted as single-precision only if we're doing a // conversion from single -> double. UnimplementedInstruction(instr); return; } int64_t res_val = 0; switch (opc) { case 0: // Format("fmovdd 'vd, 'vn"); res_val = get_vregisterd(vn, 0); break; case 1: // Format("fabsd 'vd, 'vn"); res_val = bit_cast(fabs(vn_dbl)); break; case 2: // Format("fnegd 'vd, 'vn"); res_val = bit_cast(-vn_dbl); break; case 3: // Format("fsqrtd 'vd, 'vn"); res_val = bit_cast(sqrt(vn_dbl)); break; case 4: { // Format(instr, "fcvtsd 'vd, 'vn"); const uint32_t val = bit_cast(static_cast(vn_dbl)); res_val = static_cast(val); break; } case 5: // Format(instr, "fcvtds 'vd, 'vn"); res_val = bit_cast(static_cast(vn_flt)); break; default: UnimplementedInstruction(instr); break; } set_vregisterd(vd, 0, res_val); set_vregisterd(vd, 1, 0); } void Simulator::DecodeFPTwoSource(Instr* instr) { if (instr->Bits(22, 2) != 1) { UnimplementedInstruction(instr); return; } const VRegister vd = instr->VdField(); const VRegister vn = instr->VnField(); const VRegister vm = instr->VmField(); const double vn_val = bit_cast(get_vregisterd(vn, 0)); const double vm_val = bit_cast(get_vregisterd(vm, 0)); const int opc = instr->Bits(12, 4); double result; switch (opc) { case 0: // Format(instr, "fmuld 'vd, 'vn, 'vm"); result = vn_val * vm_val; break; case 1: // Format(instr, "fdivd 'vd, 'vn, 'vm"); result = vn_val / vm_val; break; case 2: // Format(instr, "faddd 'vd, 'vn, 'vm"); result = vn_val + vm_val; break; case 3: // Format(instr, "fsubd 'vd, 'vn, 'vm"); result = vn_val - vm_val; break; default: UnimplementedInstruction(instr); return; } set_vregisterd(vd, 0, bit_cast(result)); set_vregisterd(vd, 1, 0); } void Simulator::DecodeFPCompare(Instr* instr) { const VRegister vn = instr->VnField(); const VRegister vm = instr->VmField(); const double vn_val = bit_cast(get_vregisterd(vn, 0)); double vm_val; if ((instr->Bit(22) == 1) && (instr->Bits(3, 2) == 0)) { // Format(instr, "fcmpd 'vn, 'vm"); vm_val = bit_cast(get_vregisterd(vm, 0)); } else if ((instr->Bit(22) == 1) && (instr->Bits(3, 2) == 1)) { if (instr->VmField() == V0) { // Format(instr, "fcmpd 'vn, #0.0"); vm_val = 0.0; } else { UnimplementedInstruction(instr); return; } } else { UnimplementedInstruction(instr); return; } n_flag_ = false; z_flag_ = false; c_flag_ = false; v_flag_ = false; if (isnan(vn_val) || isnan(vm_val)) { c_flag_ = true; v_flag_ = true; } else if (vn_val == vm_val) { z_flag_ = true; c_flag_ = true; } else if (vn_val < vm_val) { n_flag_ = true; } else { c_flag_ = true; } } void Simulator::DecodeFP(Instr* instr) { if (instr->IsFPImmOp()) { DecodeFPImm(instr); } else if (instr->IsFPIntCvtOp()) { DecodeFPIntCvt(instr); } else if (instr->IsFPOneSourceOp()) { DecodeFPOneSource(instr); } else if (instr->IsFPTwoSourceOp()) { DecodeFPTwoSource(instr); } else if (instr->IsFPCompareOp()) { DecodeFPCompare(instr); } else { UnimplementedInstruction(instr); } } void Simulator::DecodeDPSimd2(Instr* instr) { if (instr->IsFPOp()) { DecodeFP(instr); } else { UnimplementedInstruction(instr); } } // Executes the current instruction. void Simulator::InstructionDecode(Instr* instr) { pc_modified_ = false; if (IsTracingExecution()) { THR_Print("%" Pu64 " ", icount_); const uword start = reinterpret_cast(instr); const uword end = start + Instr::kInstrSize; if (FLAG_support_disassembler) { Disassembler::Disassemble(start, end); } else { THR_Print("Disassembler not supported in this mode.\n"); } } if (instr->IsDPImmediateOp()) { DecodeDPImmediate(instr); } else if (instr->IsCompareBranchOp()) { DecodeCompareBranch(instr); } else if (instr->IsLoadStoreOp()) { DecodeLoadStore(instr); } else if (instr->IsDPRegisterOp()) { DecodeDPRegister(instr); } else if (instr->IsDPSimd1Op()) { DecodeDPSimd1(instr); } else if (instr->IsDPSimd2Op()) { DecodeDPSimd2(instr); } else { UnimplementedInstruction(instr); } if (!pc_modified_) { set_pc(reinterpret_cast(instr) + Instr::kInstrSize); } } void Simulator::Execute() { // Get the PC to simulate. Cannot use the accessor here as we need the // raw PC value and not the one used as input to arithmetic instructions. uword program_counter = get_pc(); if (FLAG_stop_sim_at == ULLONG_MAX) { // Fast version of the dispatch loop without checking whether the simulator // should be stopping at a particular executed instruction. while (program_counter != kEndSimulatingPC) { Instr* instr = reinterpret_cast(program_counter); icount_++; if (IsIllegalAddress(program_counter)) { HandleIllegalAccess(program_counter, instr); } else { InstructionDecode(instr); } program_counter = get_pc(); } } else { // FLAG_stop_sim_at is at the non-default value. Stop in the debugger when // we reach the particular instruction count or address. while (program_counter != kEndSimulatingPC) { Instr* instr = reinterpret_cast(program_counter); icount_++; if (icount_ == FLAG_stop_sim_at) { SimulatorDebugger dbg(this); dbg.Stop(instr, "Instruction count reached"); } else if (reinterpret_cast(instr) == FLAG_stop_sim_at) { SimulatorDebugger dbg(this); dbg.Stop(instr, "Instruction address reached"); } else if (IsIllegalAddress(program_counter)) { HandleIllegalAccess(program_counter, instr); } else { InstructionDecode(instr); } program_counter = get_pc(); } } } int64_t Simulator::Call(int64_t entry, int64_t parameter0, int64_t parameter1, int64_t parameter2, int64_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_register(R31, R31IsSP); // Setup parameters. if (fp_args) { set_vregisterd(V0, 0, parameter0); set_vregisterd(V0, 1, 0); set_vregisterd(V1, 0, parameter1); set_vregisterd(V1, 1, 0); set_vregisterd(V2, 0, parameter2); set_vregisterd(V2, 1, 0); set_vregisterd(V3, 0, parameter3); set_vregisterd(V3, 1, 0); } else { set_register(NULL, R0, parameter0); set_register(NULL, R1, parameter1); set_register(NULL, R2, parameter2); set_register(NULL, R3, 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_register(NULL, R31, stack_pointer, R31IsSP); // Prepare to execute the code at entry. set_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_register(NULL, LR, 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. int64_t preserved_vals[kAbiPreservedCpuRegCount]; const double dicount = static_cast(icount_); const int64_t callee_saved_value = bit_cast(dicount); for (int i = kAbiFirstPreservedCpuReg; i <= kAbiLastPreservedCpuReg; i++) { const Register r = static_cast(i); preserved_vals[i - kAbiFirstPreservedCpuReg] = get_register(r); set_register(NULL, r, callee_saved_value); } // Only the bottom half of the V registers must be preserved. int64_t preserved_dvals[kAbiPreservedFpuRegCount]; for (int i = kAbiFirstPreservedFpuReg; i <= kAbiLastPreservedFpuReg; i++) { const VRegister r = static_cast(i); preserved_dvals[i - kAbiFirstPreservedFpuReg] = get_vregisterd(r, 0); set_vregisterd(r, 0, callee_saved_value); set_vregisterd(r, 1, 0); } // Start the simulation. Execute(); // Check that the callee-saved registers have been preserved, // and restore them with the original value. for (int i = kAbiFirstPreservedCpuReg; i <= kAbiLastPreservedCpuReg; i++) { const Register r = static_cast(i); ASSERT(callee_saved_value == get_register(r)); set_register(NULL, r, preserved_vals[i - kAbiFirstPreservedCpuReg]); } for (int i = kAbiFirstPreservedFpuReg; i <= kAbiLastPreservedFpuReg; i++) { const VRegister r = static_cast(i); ASSERT(callee_saved_value == get_vregisterd(r, 0)); set_vregisterd(r, 0, preserved_dvals[i - kAbiFirstPreservedFpuReg]); set_vregisterd(r, 1, 0); } // Restore the SP register and return R0. set_register(NULL, R31, sp_before_call, R31IsSP); int64_t return_value; if (fp_return) { return_value = get_vregisterd(V0, 0); } else { return_value = get_register(R0); } return return_value; } void Simulator::JumpToFrame(uword pc, uword sp, uword fp, Thread* thread) { // Walk over all setjmp buffers (simulated --> C++ transitions) // and try to find the setjmp associated with the simulated stack pointer. SimulatorSetjmpBuffer* buf = last_setjmp_buffer(); while (buf->link() != NULL && buf->link()->sp() <= sp) { buf = buf->link(); } ASSERT(buf != NULL); // The C++ caller has not cleaned up the stack memory of C++ frames. // Prepare for unwinding frames by destroying all the stack resources // in the previous C++ frames. StackResource::Unwind(thread); // Keep the following code in sync with `StubCode::JumpToFrameStub()`. // Unwind the C++ stack and continue simulation in the target frame. set_pc(static_cast(pc)); set_register(NULL, SP, static_cast(sp)); set_register(NULL, FP, static_cast(fp)); set_register(NULL, THR, reinterpret_cast(thread)); // Set the tag. thread->set_vm_tag(VMTag::kDartTagId); // Clear top exit frame. thread->set_top_exit_frame_info(0); // Restore pool pointer. int64_t code = *reinterpret_cast(fp + kPcMarkerSlotFromFp * kWordSize); int64_t pp = (FLAG_precompiled_mode && FLAG_use_bare_instructions) ? static_cast(thread->global_object_pool()) : *reinterpret_cast( code + Code::object_pool_offset() - kHeapObjectTag); pp -= kHeapObjectTag; // In the PP register, the pool pointer is untagged. set_register(NULL, CODE_REG, code); set_register(NULL, PP, pp); set_register(NULL, BARRIER_MASK, thread->write_barrier_mask()); set_register(NULL, NULL_REG, static_cast(Object::null())); #if defined(DART_COMPRESSED_POINTERS) set_register(NULL, HEAP_BASE, thread->heap_base()); #endif if (FLAG_precompiled_mode && FLAG_use_bare_instructions) { set_register(NULL, DISPATCH_TABLE_REG, reinterpret_cast(thread->dispatch_table_array())); } buf->Longjmp(); } } // namespace dart #endif // !defined(USING_SIMULATOR) #endif // defined TARGET_ARCH_ARM64