dart-sdk/runtime/vm/simulator_arm64.cc
Alexander Markov 16e8dc257e [vm] Faster double.floor()/ceil()/truncate() in AOT mode (x64, arm64)
1) double.truncate() now simply calls toInt(), omitting
truncateToDouble() call.

2) double.floor() and ceil() are now intrinsified on arm64 and AOT/x64
and generated using DoubleToInteger instruction.

3) DoubleToInteger instruction is extended to support floor and ceil.
On arm64 DoubleToInteger is implemented using fcvtms and fcvtps
instructions. On x64 DoubleToInteger is implemented using roundsd
under a check if it is supported (with a fallback to a stub and a
runtime call).

AOT/x64:
Before: BenchFloor(RunTime): 318.82148549569655 us.
After:  BenchFloor(RunTime): 133.29430189936687 us.

TEST=ci
Closes https://github.com/dart-lang/sdk/issues/46876
Closes https://github.com/dart-lang/sdk/issues/46650

Change-Id: I16ca18faf97954f8e8e25f0b72a2bbfac5bdc672
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/212381
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
2021-09-07 19:52:04 +00:00

3768 lines
126 KiB
C++

// 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 <setjmp.h> // NOLINT
#include <stdlib.h>
#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<uword>(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* const 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", &reg_nr);
if (ok && (0 <= reg_nr) && (reg_nr < kNumberOfVRegisters)) {
return static_cast<VRegister>(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<int64_t*>(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<uint32_t*>(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<uint64_t*>(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<simd_value_t*>(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 "<unknown>";
}
}
#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<uword>(
Dart::vm_isolate_group()->source()->snapshot_instructions);
auto const isolate_instructions = reinterpret_cast<uword>(
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 <address>\n"
" disasm <address> <number_of_instructions>\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 <address> -- set break point at specified address\n"
"p/print <reg or icount or value or *addr> -- print integer\n"
"pf/printfloat <vreg or *addr> --print float value\n"
"pd/printdouble <vreg or *addr> -- print double value\n"
"pq/printquad <vreg or *addr> -- 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<Instr*>(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<Instr*>(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 <reg or icount or value or *addr>\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<float, uint32_t>(value);
OS::PrintErr("%s: %d 0x%x %.8g\n", arg1, value, value, svalue);
} else {
OS::PrintErr("%s unrecognized\n", arg1);
}
} else {
OS::PrintErr("printfloat <vreg or *addr>\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<double, uint64_t>(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 <vreg or *addr>\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<double, int64_t>(d0);
const double dval1 = bit_cast<double, int64_t>(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<float, int32_t>(s0);
const float sval1 = bit_cast<float, int32_t>(s1);
const float sval2 = bit_cast<float, int32_t>(s2);
const float sval3 = bit_cast<float, int32_t>(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 <vreg or *addr>\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<ObjectPtr>(static_cast<uword>(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 [<address> [<number_of_instructions>]]\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<Instr*>(addr))) {
OS::PrintErr("setting breakpoint failed\n");
}
} else {
OS::PrintErr("%s unrecognized\n", arg1);
}
} else {
OS::PrintErr("break <addr>\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<Instr*>(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<Thread*>(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<uword>(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<uword>(&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<char*>(hlt_instruction);
char* addr_of_redirection =
addr_of_hlt - OFFSET_OF(Redirection, hlt_instruction_);
return reinterpret_cast<Redirection*>(addr_of_redirection);
}
// Please note that this function is called by the signal handler of the
// profiling thread. It can therefore run at any point in time and is not
// allowed to hold any locks - which is precisely the reason why the list is
// prepend-only and a memory fence is used when writing the list head [list_]!
static uword FunctionForRedirect(uword address_of_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<Redirection*> list_;
static Mutex* mutex_;
};
std::atomic<Redirection*> Redirection::list_ = {nullptr};
Mutex* Redirection::mutex_ = new Mutex();
uword Simulator::RedirectExternalReference(uword function,
CallKind call_kind,
int argument_count) {
Redirection* redirection =
Redirection::Get(function, call_kind, argument_count);
return redirection->address_of_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(DART_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<uint32_t>(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<int32_t>(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<intptr_t*>(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<intptr_t*>(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<uint32_t*>(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<int32_t*>(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<uint32_t*>(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<uint16_t*>(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<int16_t*>(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<uint16_t*>(addr);
*ptr = value;
return;
}
UnalignedAccess("halfword write", addr, instr);
}
uint8_t Simulator::ReadBU(uword addr) {
uint8_t* ptr = reinterpret_cast<uint8_t*>(addr);
return *ptr;
}
int8_t Simulator::ReadB(uword addr) {
int8_t* ptr = reinterpret_cast<int8_t*>(addr);
return *ptr;
}
void Simulator::WriteB(uword addr, uint8_t value) {
uint8_t* ptr = reinterpret_cast<uint8_t*>(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<RelaxedAtomic<int64_t>*>(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<uint32_t>(exclusive_access_value_);
ClearExclusive();
auto atomic_addr = reinterpret_cast<RelaxedAtomic<int32_t>*>(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<T>` which supports performing atomic operations on
// non-atomic data.
COMPILE_ASSERT(sizeof(std::atomic<intptr_t>) == sizeof(intptr_t));
return reinterpret_cast<std::atomic<intptr_t>*>(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<T>` which supports performing atomic operations on
// non-atomic data.
COMPILE_ASSERT(sizeof(std::atomic<intptr_t>) == sizeof(intptr_t));
return reinterpret_cast<std::atomic<uint32_t>*>(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<T>` which supports performing atomic operations on
// non-atomic data.
COMPILE_ASSERT(sizeof(std::atomic<intptr_t>) == sizeof(intptr_t));
reinterpret_cast<std::atomic<intptr_t>*>(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<T>` which supports performing atomic operations on
// non-atomic data.
COMPILE_ASSERT(sizeof(std::atomic<intptr_t>) == sizeof(intptr_t));
reinterpret_cast<std::atomic<uint32_t>*>(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<uint32_t>(left);
uint64_t uright = static_cast<uint32_t>(right);
uint64_t ucarry = static_cast<uint32_t>(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<int64_t>(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<int64_t>(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<uint64_t>(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<word>(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<NativeArguments*>(get_register(R0));
SimulatorRuntimeCall target =
reinterpret_cast<SimulatorRuntimeCall>(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<SimulatorLeafRuntimeCall>(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<SimulatorLeafFloatRuntimeCall>(external);
const double d0 = bit_cast<double, int64_t>(get_vregisterd(V0, 0));
const double d1 = bit_cast<double, int64_t>(get_vregisterd(V1, 0));
const double d2 = bit_cast<double, int64_t>(get_vregisterd(V2, 0));
const double d3 = bit_cast<double, int64_t>(get_vregisterd(V3, 0));
const double d4 = bit_cast<double, int64_t>(get_vregisterd(V4, 0));
const double d5 = bit_cast<double, int64_t>(get_vregisterd(V5, 0));
const double d6 = bit_cast<double, int64_t>(get_vregisterd(V6, 0));
const double d7 = bit_cast<double, int64_t>(get_vregisterd(V7, 0));
const double res =
InvokeFloatLeafRuntime(target, d0, d1, d2, d3, d4, d5, d6, d7);
set_vregisterd(V0, 0, bit_cast<int64_t, double>(res));
set_vregisterd(V0, 1, 0);
} else {
ASSERT(redirection->call_kind() == kNativeCallWrapper);
SimulatorNativeCallWrapper wrapper =
reinterpret_cast<SimulatorNativeCallWrapper>(external);
Dart_NativeArguments arguments =
reinterpret_cast<Dart_NativeArguments>(get_register(R0));
Dart_NativeFunction target =
reinterpret_cast<Dart_NativeFunction>(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<uint16_t>(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<uint32_t>(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<int64_t>(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<int64_t>(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<int64_t>(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<uint8_t>(rt_val32);
WriteB(address, val);
break;
}
case 1: {
const uint16_t val = static_cast<uint16_t>(rt_val32);
WriteH(address, val, instr);
break;
}
case 2: {
const uint32_t val = static_cast<uint32_t>(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<int64_t>(ReadB(address));
} else {
val = static_cast<int64_t>(ReadBU(address));
}
break;
}
case 1: {
if (signd) {
val = static_cast<int64_t>(ReadH(address, instr));
} else {
val = static_cast<int64_t>(ReadHU(address, instr));
}
break;
}
case 2: {
if (signd) {
val = static_cast<int64_t>(ReadW(address, instr));
} else {
val = static_cast<int64_t>(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<int32_t>(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<uint32_t>(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<int64_t>(ReadW(address, instr));
val2 = static_cast<int64_t>(ReadW(address + size, instr));
} else {
val1 = static_cast<int64_t>(ReadWU(address, instr));
val2 = static_cast<int64_t>(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<int32_t>(val1), R31IsZR);
set_wregister(rt2, static_cast<int32_t>(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<int64_t>(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<int32_t>(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<uint32_t>(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<uint32_t>(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<uint64_t>(value) << amount) & mask;
case LSR:
return static_cast<uint64_t>(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<uint64_t>(value) >> amount) |
((static_cast<uint64_t>(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<int64_t>(static_cast<uint64_t>(value) << 56) >> 56;
break;
case SXTH:
value = static_cast<int64_t>(static_cast<uint64_t>(value) << 48) >> 48;
break;
case SXTW:
value = static_cast<int64_t>(static_cast<uint64_t>(value) << 32) >> 32;
break;
case UXTX:
case SXTX:
break;
default:
UNREACHABLE();
break;
}
int64_t mask = (reg_size == kXRegSizeInBits) ? kXRegMask : kWRegMask;
return (static_cast<uint64_t>(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<uint32_t>(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<int64_t>(0x8000000000000000LL)) &&
(bottom == static_cast<int64_t>(0xffffffffffffffffLL))) {
return static_cast<int64_t>(0x8000000000000000LL);
} else {
return top / bottom;
}
} else {
const uint64_t utop = static_cast<uint64_t>(top);
const uint64_t ubottom = static_cast<uint64_t>(bottom);
return static_cast<int64_t>(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<int32_t>(0x80000000)) &&
(bottom == static_cast<int32_t>(0xffffffff))) {
return static_cast<int32_t>(0x80000000);
} else {
return top / bottom;
}
} else {
const uint32_t utop = static_cast<uint32_t>(top);
const uint32_t ubottom = static_cast<uint32_t>(bottom);
return static_cast<int32_t>(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<uint64_t>(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<uint32_t>(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<uint64_t>(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<uint32_t>(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(DART_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<int64_t>(res >> 64);
#endif // DART_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(DART_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<unsigned __int128>(rn_val) *
static_cast<unsigned __int128>(rm_val);
const uint64_t alu_out = static_cast<uint64_t>(res >> 64);
#endif // DART_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<uint32_t>(get_wregister(rn, R31IsZR));
const uint64_t rm_val = static_cast<uint32_t>(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<int32_t>(get_wregister(rn, R31IsZR));
const int64_t rm_val = static_cast<int32_t>(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<float, int32_t>(vn_val);
const float vm_flt = bit_cast<float, int32_t>(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<int32_t, float>(vn_flt + vm_flt);
} else {
// Format(instr, "vsub'vsz 'vd, 'vn, 'vm");
res = bit_cast<int32_t, float>(vn_flt - vm_flt);
}
} else if ((U == 1) && (opcode == 0x1b)) {
// Format(instr, "vmul'vsz 'vd, 'vn, 'vm");
res = bit_cast<int32_t, float>(vn_flt * vm_flt);
} else if ((U == 1) && (opcode == 0x1f)) {
// Format(instr, "vdiv'vsz 'vd, 'vn, 'vm");
res = bit_cast<int32_t, float>(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<int32_t, float>(m);
} else {
// Format(instr, "vmax'vsz 'vd, 'vn, 'vm");
const float m = fmaxf(vn_flt, vm_flt);
res = bit_cast<int32_t, float>(m);
}
} else if ((U == 0) && (opcode == 0x1f)) {
if (instr->Bit(23) == 0) {
// Format(instr, "vrecps'vsz 'vd, 'vn, 'vm");
res = bit_cast<int32_t, float>(2.0 - (vn_flt * vm_flt));
} else {
// Format(instr, "vrsqrt'vsz 'vd, 'vn, 'vm");
res = bit_cast<int32_t, float>((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<double, int64_t>(vn_val);
const double vm_dbl = bit_cast<double, int64_t>(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<int64_t, double>(vn_dbl + vm_dbl);
} else {
// Format(instr, "vsub'vsz 'vd, 'vn, 'vm");
res = bit_cast<int64_t, double>(vn_dbl - vm_dbl);
}
} else if ((U == 1) && (opcode == 0x1b)) {
// Format(instr, "vmul'vsz 'vd, 'vn, 'vm");
res = bit_cast<int64_t, double>(vn_dbl * vm_dbl);
} else if ((U == 1) && (opcode == 0x1f)) {
// Format(instr, "vdiv'vsz 'vd, 'vn, 'vm");
res = bit_cast<int64_t, double>(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<int64_t, double>(m);
} else {
// Format(instr, "vmax'vsz 'vd, 'vn, 'vm");
const double m = fmax(vn_dbl, vm_dbl);
res = bit_cast<int64_t, double>(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<uint32_t, float>(a);
uint64_t scaled;
if (((a_bits >> 23) & 1) != 0) {
// scaled = '0 01111111101' : operand<22:0> : Zeros(29)
scaled = (static_cast<uint64_t>(0x3fd) << 52) |
((static_cast<uint64_t>(a_bits) & 0x7fffff) << 29);
} else {
// scaled = '0 01111111110' : operand<22:0> : Zeros(29)
scaled = (static_cast<uint64_t>(0x3fe) << 52) |
((static_cast<uint64_t>(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<double, uint64_t>(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<int32_t>(scaled_d * 512.0);
// reciprocal root r.
r = 1.0 / sqrt((static_cast<double>(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<int32_t>(scaled_d * 256.0);
// reciprocal root r.
r = 1.0 / sqrt((static_cast<double>(q1) + 0.5) / 256.0);
}
// r in units of 1/256 rounded to nearest.
int32_t s = static_cast<int>(256.0 * r + 0.5);
double estimate = static_cast<double>(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<uint64_t, double>(estimate) >> 29) & 0x7fffff);
return bit_cast<float, int32_t>(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<uint32_t, float>(a);
// scaled = '0011 1111 1110' : a<22:0> : Zeros(29)
uint64_t scaled = (static_cast<uint64_t>(0x3fe) << 52) |
((static_cast<uint64_t>(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<double, uint64_t>(scaled);
ASSERT((scaled_d >= 0.5) && (scaled_d < 1.0));
// a in units of 1/512 rounded down.
int32_t q = static_cast<int32_t>(scaled_d * 512.0);
// reciprocal r.
double r = 1.0 / ((static_cast<double>(q) + 0.5) / 512.0);
// r in units of 1/256 rounded to nearest.
int32_t s = static_cast<int32_t>(256.0 * r + 0.5);
double estimate = static_cast<double>(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<uint64_t, double>(estimate) >> 29) & 0x7fffff);
return bit_cast<float, int32_t>(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<float, int32_t>(vn_val);
set_vregisters(vd, i, bit_cast<int32_t, float>(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<double, int64_t>(vn_val);
set_vregisterd(vd, i, bit_cast<int64_t, double>(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<float, int32_t>(vn_val);
set_vregisters(vd, i, bit_cast<int32_t, float>(-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<double, int64_t>(vn_val);
set_vregisterd(vd, i, bit_cast<int64_t, double>(-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<float, int32_t>(vn_val);
set_vregisters(vd, i, bit_cast<int32_t, float>(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<double, int64_t>(vn_val);
set_vregisterd(vd, i, bit_cast<int64_t, double>(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<float, int32_t>(vn_val);
const float re = arm_recip_estimate(vn_flt);
set_vregisters(vd, i, bit_cast<int32_t, float>(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<float, int32_t>(vn_val);
const float re = arm_reciprocal_sqrt_estimate(vn_flt);
set_vregisters(vd, i, bit_cast<int32_t, float>(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<double>(rn_val64)
: static_cast<double>(rn_val32);
set_vregisterd(vd, 0, bit_cast<int64_t, double>(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) == 8) || (instr->Bits(16, 5) == 16) ||
(instr->Bits(16, 5) == 24)) {
const intptr_t max = instr->Bit(31) == 1 ? INT64_MAX : INT32_MAX;
const intptr_t min = instr->Bit(31) == 1 ? INT64_MIN : INT32_MIN;
double vn_val = bit_cast<double, int64_t>(get_vregisterd(vn, 0));
switch (instr->Bits(16, 5)) {
case 8:
// Format(instr, "fcvtps'sf 'rd, 'vn");
vn_val = ceil(vn_val);
break;
case 16:
// Format(instr, "fcvtms'sf 'rd, 'vn");
vn_val = floor(vn_val);
break;
case 24:
// Format(instr, "fcvtzs'sf 'rd, 'vn");
break;
}
int64_t result;
if (vn_val >= static_cast<double>(max)) {
result = max;
} else if (vn_val <= static_cast<double>(min)) {
result = min;
} else {
result = static_cast<int64_t>(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<double, int64_t>(vn_val);
const float vn_flt = bit_cast<float, int32_t>(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<int64_t, double>(fabs(vn_dbl));
break;
case 2:
// Format("fnegd 'vd, 'vn");
res_val = bit_cast<int64_t, double>(-vn_dbl);
break;
case 3:
// Format("fsqrtd 'vd, 'vn");
res_val = bit_cast<int64_t, double>(sqrt(vn_dbl));
break;
case 4: {
// Format(instr, "fcvtsd 'vd, 'vn");
const uint32_t val =
bit_cast<uint32_t, float>(static_cast<float>(vn_dbl));
res_val = static_cast<int64_t>(val);
break;
}
case 5:
// Format(instr, "fcvtds 'vd, 'vn");
res_val = bit_cast<int64_t, double>(static_cast<double>(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<double, int64_t>(get_vregisterd(vn, 0));
const double vm_val = bit_cast<double, int64_t>(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<int64_t, double>(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<double, int64_t>(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<double, int64_t>(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<uword>(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<int64_t>(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<Instr*>(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<Instr*>(program_counter);
icount_++;
if (icount_ == FLAG_stop_sim_at) {
SimulatorDebugger dbg(this);
dbg.Stop(instr, "Instruction count reached");
} else if (reinterpret_cast<uint64_t>(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<double>(icount_);
const int64_t callee_saved_value = bit_cast<int64_t, double>(dicount);
for (int i = kAbiFirstPreservedCpuReg; i <= kAbiLastPreservedCpuReg; i++) {
const Register r = static_cast<Register>(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<VRegister>(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<Register>(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<VRegister>(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<int64_t>(pc));
set_register(NULL, SP, static_cast<int64_t>(sp));
set_register(NULL, FP, static_cast<int64_t>(fp));
set_register(NULL, THR, reinterpret_cast<int64_t>(thread));
// Set the tag.
thread->set_vm_tag(VMTag::kDartTagId);
// Clear top exit frame.
thread->set_top_exit_frame_info(0);
// Restore pool pointer.
int64_t code =
*reinterpret_cast<int64_t*>(fp + kPcMarkerSlotFromFp * kWordSize);
int64_t pp = (FLAG_precompiled_mode && FLAG_use_bare_instructions)
? static_cast<int64_t>(thread->global_object_pool())
: *reinterpret_cast<int64_t*>(
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, HEAP_BITS,
(thread->write_barrier_mask() << 32) | (thread->heap_base() >> 32));
set_register(NULL, NULL_REG, static_cast<int64_t>(Object::null()));
if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
set_register(NULL, DISPATCH_TABLE_REG,
reinterpret_cast<int64_t>(thread->dispatch_table_array()));
}
buf->Longjmp();
}
} // namespace dart
#endif // !defined(USING_SIMULATOR)
#endif // defined TARGET_ARCH_ARM64