// 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(DART_INCLUDE_SIMULATOR)

#include "vm/simulator.h"

#include "vm/compiler/assembler/disassembler.h"
#include "vm/constants.h"
#include "vm/image_snapshot.h"
#include "vm/native_arguments.h"
#include "vm/os_thread.h"
#include "vm/runtime_entry.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.");
DEFINE_FLAG(bool, sim_buffer_memory, false, "Simulate weak memory ordering.");

// 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);
    DART_LONGJMP(buffer_, 1);
  }

  explicit SimulatorSetjmpBuffer(Simulator* sim) {
    simulator_ = sim;
    link_ = sim->last_setjmp_buffer();
    sim->set_last_setjmp_buffer(this);
    sp_ = static_cast<uword>(sim->get_register(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 isolate_image(isolate_instructions);
  if (isolate_image.contains(pc)) {
    *offset = pc - isolate_instructions;
    return kSnapshotTextAsmSymbol;
  } 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)
  const uword vm_instructions = 0;
  const uword isolate_instructions =
      reinterpret_cast<uword>(T->isolate_group()->source()->snapshot_text);
  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 != nullptr);
  Function& function = Function::Handle(Z);
  Function& inlined_function = Function::Handle(Z);
  Code& code = Code::Handle(Z);
  Code& unoptimized_code = Code::Handle(Z);
  while (frame != nullptr) {
    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_ != nullptr) {
    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_ != nullptr) {
    sim_->break_pc_->SetInstructionBits(sim_->break_instr_);
  }

  sim_->break_pc_ = nullptr;
  sim_->break_instr_ = 0;
  return true;
}

void SimulatorDebugger::UndoBreakpoints() {
  if (sim_->break_pc_ != nullptr) {
    sim_->break_pc_->SetInstructionBits(sim_->break_instr_);
  }
}

void SimulatorDebugger::RedoBreakpoints() {
  if (sim_->break_pc_ != nullptr) {
    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 == nullptr) {
      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(nullptr)) {
          OS::PrintErr("deleting breakpoint failed\n");
        }
      } else if (strcmp(cmd, "flags") == 0) {
        OS::PrintErr("APSR: ");
        OS::PrintErr("N flag: %d; ", static_cast<int>(sim_->n_flag_));
        OS::PrintErr("Z flag: %d; ", static_cast<int>(sim_->z_flag_));
        OS::PrintErr("C flag: %d; ", static_cast<int>(sim_->c_flag_));
        OS::PrintErr("V flag: %d\n", static_cast<int>(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 = nullptr;
  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) == nullptr) {
      // fgets got an error. Just give up.
      if (result != nullptr) {
        delete[] result;
      }
      return nullptr;
    }
    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 == nullptr) {
      // Allocate the initial result and make room for the terminating '\0'
      result = new char[len + 1];
      if (result == nullptr) {
        // OOM, so cannot readline anymore.
        return nullptr;
      }
    } 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 == nullptr) {
        // OOM, free the buffer allocated so far and return nullptr.
        delete[] result;
        return nullptr;
      } 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 != nullptr);
  result[offset] = '\0';
  return result;
}

void Simulator::Init() {}

Simulator::Simulator() : memory_(FLAG_sim_buffer_memory) {
  // Setup simulator support first. Some of this information is needed to
  // setup the architecture state.
  // We allocate the stack here, the size is computed as the sum of
  // the size specified by the user and the buffer space needed for
  // handling stack overflow exceptions. To be safe in potential
  // stack underflows we also add some underflow buffer space.
  stack_ =
      new char[(OSThread::GetSpecifiedStackSize() +
                OSThread::kStackSizeBufferMax + kSimulatorStackUnderflowSize)];
  // 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_ = nullptr;
  break_instr_ = 0;
  last_setjmp_buffer_ = nullptr;

  // 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 != nullptr) {
    isolate->set_simulator(nullptr);
  }
}

// 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_(nullptr) {}

  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 == nullptr) {
    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_MACOS) || defined(DART_TARGET_OS_WINDOWS)
  // R18 is globally reserved on macOS/iOS, the TEB pointer on Windows.
  ASSERT(instr == nullptr || reg != R18);
#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 != nullptr) && (reg == R31) && !Utils::IsAligned(value, 16)) {
      UnalignedAccess("CSP set", value, instr);
    }

#if defined(DEBUG)
    if (reg == SP) {
      // Memory below CSP can be written to at any instruction boundary by a
      // signal handler. Simulate this to ensure we're keeping CSP far enough
      // ahead of SP to prevent Dart frames from being trashed.
      uword csp = registers_[R31];
      WriteX(csp - 1 * kWordSize, icount_, nullptr);
      WriteX(csp - 2 * kWordSize, icount_, nullptr);
      WriteX(csp - 3 * kWordSize, icount_, nullptr);
      WriteX(csp - 4 * kWordSize, icount_, nullptr);
    }
#endif
  }
}

// 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) {
    return memory_.Load<intptr_t>(addr);
  }
  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) {
    memory_.Store(addr, 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) {
    return memory_.Load<uint32_t>(addr);
  }
  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) {
    return memory_.Load<int32_t>(addr);
  }
  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) {
    memory_.Store(addr, 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) {
    return memory_.Load<uint16_t>(addr);
  }
  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) {
    return memory_.Load<int16_t>(addr);
  }
  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) {
    memory_.Store(addr, value);
    return;
  }
  UnalignedAccess("halfword write", addr, instr);
}

uint8_t Simulator::ReadBU(uword addr) {
  return memory_.Load<uint8_t>(addr);
}

int8_t Simulator::ReadB(uword addr) {
  return memory_.Load<int8_t>(addr);
}

void Simulator::WriteB(uword addr, uint8_t value) {
  memory_.Store(addr, 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_;
}

uint32_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.
  }

  intptr_t old_value = exclusive_access_value_;
  ClearExclusive();

  if ((random_.NextUInt32() % 16) == 0) {
    return 1;  // Suprious failure.
  }

  if (memory_.CompareExchange(addr, old_value, value,
                              std::memory_order_relaxed)) {
    return 0;  // Success.
  }
  return 1;  // Failure.
}

intptr_t Simulator::WriteExclusiveW(uword addr, uint32_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.
  }

  uint32_t old_value = static_cast<uint32_t>(exclusive_access_value_);
  ClearExclusive();

  if ((random_.NextUInt32() % 16) == 0) {
    return 1;  // Spurious failure.
  }

  if (memory_.CompareExchange(addr, old_value, value,
                              std::memory_order_relaxed)) {
    return 0;  // Success.
  }
  return 1;  // Failure.
}

intptr_t Simulator::ReadAcquire(uword addr, Instr* instr) {
  return memory_.Load<uint64_t>(addr, std::memory_order_acquire);
}

uint32_t Simulator::ReadAcquireW(uword addr, Instr* instr) {
  return memory_.Load<uint32_t>(addr, std::memory_order_acquire);
}

void Simulator::WriteRelease(uword addr, intptr_t value, Instr* instr) {
  memory_.Store(addr, value, std::memory_order_release);
}

void Simulator::WriteReleaseW(uword addr, uint32_t value, Instr* instr) {
  memory_.Store(addr, 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() != 0) {
    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() != 0) {
    // 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) {
  // We can't instrument the runtime.
  memory_.FlushAll();

  ASSERT(Utils::IsAligned(get_register(SPREG), OS::ActivationFrameAlignment()));

  SimulatorSetjmpBuffer buffer(this);
  if (!DART_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%" Px "\n", external);
    }

    if (redirection->call_kind() == kRuntimeCall) {
      NativeArguments* arguments =
          reinterpret_cast<NativeArguments*>(get_register(R0));
      SimulatorRuntimeCall target =
          reinterpret_cast<SimulatorRuntimeCall>(external);
      target(*arguments);
      ClobberVolatileRegisters();
    } else if (redirection->call_kind() == kLeafRuntimeCall) {
      ASSERT((0 <= redirection->argument_count()) &&
             (redirection->argument_count() <= 8));
      SimulatorLeafRuntimeCall target =
          reinterpret_cast<SimulatorLeafRuntimeCall>(external);
      const 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);
      ClobberVolatileRegisters();
      set_register(instr, R0, res);  // Set returned result from function.
    } else if (redirection->call_kind() == kLeafFloatRuntimeCall) {
      ASSERT((0 <= redirection->argument_count()) &&
             (redirection->argument_count() <= 8));
      SimulatorLeafFloatRuntimeCall target =
          reinterpret_cast<SimulatorLeafFloatRuntimeCall>(external);
      const double d0 = 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);
      ClobberVolatileRegisters();
      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);
      ClobberVolatileRegisters();
    }

    // Return.
    set_pc(saved_lr);
  } else {
    // Coming via long jump from a throw. Continue to exception handler.
  }
}

struct CalloutContext {
  uword saved_stack_pointer;
  uword saved_frame_pointer;
  uword simulator_stack_pointer;
  uword simulator_frame_pointer;
  uword integer_arguments[8];
  uword double_arguments[8];
  uword r8;
  uword target;
};

extern "C" void SimulatorFfiCalloutTrampoline(CalloutContext*);

void Simulator::DoRedirectedFfiCall(Instr* instr) {
#if !defined(HOST_ARCH_ARM64)
  FATAL("Unsupported FFI call");
#else
  // We can't instrument the runtime.
  memory_.FlushAll();

  SimulatorSetjmpBuffer buffer(this);
  if (!setjmp(buffer.buffer_)) {
    int64_t saved_pc = get_pc();
    uword external = get_register(R9);
    if (IsTracingExecution()) {
      THR_Print("Call to FFI function at 0x%" Px "\n", external);
    }

    CalloutContext ctxt;
    ctxt.simulator_stack_pointer = get_register(R31);
    ctxt.simulator_frame_pointer = get_register(FP);
    ctxt.integer_arguments[0] = get_register(R0);
    ctxt.integer_arguments[1] = get_register(R1);
    ctxt.integer_arguments[2] = get_register(R2);
    ctxt.integer_arguments[3] = get_register(R3);
    ctxt.integer_arguments[4] = get_register(R4);
    ctxt.integer_arguments[5] = get_register(R5);
    ctxt.integer_arguments[6] = get_register(R6);
    ctxt.integer_arguments[7] = get_register(R7);
    ctxt.double_arguments[0] = get_vregisterd(V0, 0);
    ctxt.double_arguments[1] = get_vregisterd(V1, 0);
    ctxt.double_arguments[2] = get_vregisterd(V2, 0);
    ctxt.double_arguments[3] = get_vregisterd(V3, 0);
    ctxt.double_arguments[4] = get_vregisterd(V4, 0);
    ctxt.double_arguments[5] = get_vregisterd(V5, 0);
    ctxt.double_arguments[6] = get_vregisterd(V6, 0);
    ctxt.double_arguments[7] = get_vregisterd(V7, 0);
    ctxt.r8 = get_register(R8);
    ctxt.target = external;

    SimulatorFfiCalloutTrampoline(&ctxt);

    if (IsTracingExecution()) {
      THR_Print("Return from FFI function at 0x%" Px "\n", external);
    }

    ClobberVolatileRegisters();
    set_register(instr, R0, ctxt.integer_arguments[0]);
    set_register(instr, R1, ctxt.integer_arguments[1]);
    set_vregisterd(V0, 0, ctxt.double_arguments[0]);
    set_vregisterd(V1, 0, ctxt.double_arguments[1]);
    set_vregisterd(V2, 0, ctxt.double_arguments[2]);
    set_vregisterd(V3, 0, ctxt.double_arguments[3]);

    // Skip over hlt and blr.
    set_pc(saved_pc + 2 * Instr::kInstrSize);
  } else {
    // Coming via long jump from a throw. Continue to exception handler.
  }
#endif
}

struct CallbackContext {
  uword integer_arguments[8];
  uword double_arguments[8];
  uword r8;
  uword sp;
};

#if defined(SIMULATOR_FFI) && defined(HOST_ARCH_ARM64)

extern "C" void DoRedirectedFfiCallback(CallbackContext* ctxt,
                                        uword trampoline) {
  // Assumptions in ffi_trampolines_arm64.S
  COMPILE_ASSERT(sizeof(CallbackContext) == 144);
  COMPILE_ASSERT(FfiCallbackMetadata::kDoRedirectedFfiCallback == 1);
#if defined(DART_TARGET_OS_FUCHSIA)
  COMPILE_ASSERT(FfiCallbackMetadata::kPageSize == 4 * KB);
  COMPILE_ASSERT(FfiCallbackMetadata::NumCallbackTrampolinesPerPage() == 483);
#elif defined(DART_TARGET_OS_MACOS)
  COMPILE_ASSERT(FfiCallbackMetadata::kPageSize == 16 * KB);
  COMPILE_ASSERT(FfiCallbackMetadata::NumCallbackTrampolinesPerPage() == 2019);
#else
  COMPILE_ASSERT(FfiCallbackMetadata::kPageSize == 64 * KB);
  COMPILE_ASSERT(FfiCallbackMetadata::NumCallbackTrampolinesPerPage() == 8163);
#endif

  CallbackMetadata out;
  Thread* thread = DLRT_GetFfiCallbackMetadata(trampoline, &out);
  if (thread == nullptr) {
    // If GetFfiCallbackMetadata returned a null thread, it means that the async
    // callback was invoked after it was deleted. In this case, do nothing.
    return;
  }

  Simulator* sim = Simulator::Current();
  ASSERT(sim != nullptr);
  sim->DoRedirectedFfiCallback(thread, ctxt, &out);
}

#endif  // defined(SIMULATOR_FFI) && defined(HOST_ARCH_ARM64)

// Compare FfiCallbackTrampolineStub.
void Simulator::DoRedirectedFfiCallback(Thread* thread,
                                        CallbackContext* ctxt,
                                        CallbackMetadata* out) {
  // The C caller might not be using frame pointers, so we just hard-code a
  // maximum frame size instead of using FP-SP like we do for callouts.
  constexpr intptr_t kStackSlotsCopied = 128;

  {
    // <copy stack arguments>
    // stp lr, thr, [sp, -16]!
    uword* sp = reinterpret_cast<uword*>(get_register(R31, R31IsSP));
    uword* sp_in = reinterpret_cast<uword*>(ctxt->sp);
    for (intptr_t i = kStackSlotsCopied - 1; i >= 0; i--) {
      *--sp = sp_in[i];
    }
    *--sp = get_register(THR);
    *--sp = get_register(LR);
    *--sp = get_register(R20);
    *--sp = get_register(R21);
    set_register(nullptr, R31, reinterpret_cast<uword>(sp));
    COMPILE_ASSERT(FfiCallbackMetadata::kNativeCallbackTrampolineStackDelta ==
                   4);
  }

  set_register(nullptr, R0, ctxt->integer_arguments[0]);
  set_register(nullptr, R1, ctxt->integer_arguments[1]);
  set_register(nullptr, R2, ctxt->integer_arguments[2]);
  set_register(nullptr, R3, ctxt->integer_arguments[3]);
  set_register(nullptr, R4, ctxt->integer_arguments[4]);
  set_register(nullptr, R5, ctxt->integer_arguments[5]);
  set_register(nullptr, R6, ctxt->integer_arguments[6]);
  set_register(nullptr, R7, ctxt->integer_arguments[7]);
  set_vregisterd(V0, 0, ctxt->double_arguments[0]);
  set_vregisterd(V1, 0, ctxt->double_arguments[1]);
  set_vregisterd(V2, 0, ctxt->double_arguments[2]);
  set_vregisterd(V3, 0, ctxt->double_arguments[3]);
  set_vregisterd(V4, 0, ctxt->double_arguments[4]);
  set_vregisterd(V5, 0, ctxt->double_arguments[5]);
  set_vregisterd(V6, 0, ctxt->double_arguments[6]);
  set_vregisterd(V7, 0, ctxt->double_arguments[7]);
  set_register(nullptr, R8, ctxt->r8);
  set_register(nullptr, THR, reinterpret_cast<uword>(thread));

  set_register(nullptr, LR, kEndSimulatingPC);
  set_pc(out->entry_point);
  Execute();

  ctxt->integer_arguments[0] = get_register(R0);
  ctxt->integer_arguments[1] = get_register(R1);
  ctxt->double_arguments[0] = get_vregisterd(V0, 0);
  ctxt->double_arguments[1] = get_vregisterd(V1, 0);
  ctxt->double_arguments[2] = get_vregisterd(V2, 0);
  ctxt->double_arguments[3] = get_vregisterd(V3, 0);

  {
    // ldp lr, thr, [sp], 16!
    // <drop arguments>
    uword* sp = reinterpret_cast<uword*>(get_register(R31, R31IsSP));
    set_register(nullptr, R21, *sp++);
    set_register(nullptr, R20, *sp++);
    set_register(nullptr, LR, *sp++);
    set_register(nullptr, THR, *sp++);
    sp += kStackSlotsCopied;
    set_register(nullptr, R31, reinterpret_cast<uword>(sp));
    COMPILE_ASSERT(FfiCallbackMetadata::kNativeCallbackTrampolineStackDelta ==
                   4);
  }

  auto epilogue = reinterpret_cast<void* (*)(Thread*)>(out->epilogue);
  epilogue(thread);
}

void Simulator::ClobberVolatileRegisters() {
  // Clear atomic reservation.
  exclusive_access_addr_ = exclusive_access_value_ = 0;

  for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) {
    if ((kAbiVolatileCpuRegs & (1 << i)) != 0) {
      registers_[i] = random_.NextUInt64();
    }
  }

  for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) {
    if ((kAbiFullyVolatileFpuRegs & (1 << i)) != 0) {
      vregisters_[i].bits.i64[0] = icount_;
      vregisters_[i].bits.i64[1] = icount_;
    }
    if ((kAbiPartiallyVolatileFpuRegs & (1 << i)) != 0) {
      // V8-V15: Low bits preserved.
      vregisters_[i].bits.i64[1] = icount_;
    }
  }
}

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 if (imm == Instr::kSimulatorFfiRedirectCode) {
      DoRedirectedFfiCall(instr);
    } else {
      UnimplementedInstruction(instr);
    }
  } else {
    UnimplementedInstruction(instr);
  }
}

void Simulator::DecodeSystem(Instr* instr) {
  if (instr->InstructionBits() == CLREX) {
    // Format(instr, "clrex");
    ClearExclusive();
    return;
  }

  if (instr->InstructionBits() == kDMB_ISH) {
    // Format(instr, "dmb ish");
    memory_.FlushAll();
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic ignored "-Wtsan"
#endif
    std::atomic_thread_fence(std::memory_order_seq_cst);
    return;
  }
  if (instr->InstructionBits() == kDMB_ISHST) {
    // Format(instr, "dmb ishst");
    memory_.FlushAll();
    StoreStoreFence();
    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 LR first so that the profiler does not get confused by
        // observing the update to PC without the update to LR when we are
        // calling out of the entry stub, i.e., failing to indentify the entry
        // stub. On real hardware, the profiler's signal handler cannot
        // observe a partially execute BLR.
        set_register(instr, LR, ret);
        set_pc(dest);
        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);
  }
}

DART_FORCE_INLINE
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 is_signed = 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 =
          (is_signed && (instr->Bit(22) == 1)) || (!is_signed && (size == 2));
      int64_t val = 0;  // Sign extend into an int64_t.
      switch (size) {
        case 0: {
          if (is_signed) {
            val = static_cast<int64_t>(ReadB(address));
          } else {
            val = static_cast<int64_t>(ReadBU(address));
          }
          break;
        }
        case 1: {
          if (is_signed) {
            val = static_cast<int64_t>(ReadH(address, instr));
          } else {
            val = static_cast<int64_t>(ReadHU(address, instr));
          }
          break;
        }
        case 2: {
          if (is_signed) {
            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 int64_t rn_val = get_register(rn, R31IsSP);
  const intptr_t shift =
      (instr->Bit(26) == 1) ? 2 + instr->SzField() : 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)) {
    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(26) == 1) {
    // SIMD/FP.
    const VRegister vt = instr->VtField();
    const VRegister vt2 = instr->Vt2Field();
    if (instr->Bit(22) != 0) {
      // Format(instr, "ldp 'vt, 'vt2, 'memop");
      switch (size) {
        case 4:
          set_vregisterd(vt, 0, static_cast<int64_t>(ReadWU(address, instr)));
          set_vregisterd(vt, 1, 0);
          set_vregisterd(vt2, 0,
                         static_cast<int64_t>(ReadWU(address + 4, instr)));
          set_vregisterd(vt2, 1, 0);
          break;
        case 8:
          set_vregisterd(vt, 0, ReadX(address, instr));
          set_vregisterd(vt, 1, 0);
          set_vregisterd(vt2, 0, ReadX(address + 8, instr));
          set_vregisterd(vt2, 1, 0);
          break;
        case 16: {
          simd_value_t val;
          val.bits.i64[0] = ReadX(address, instr);
          val.bits.i64[1] = ReadX(address + 8, instr);
          set_vregister(vt, val);
          val.bits.i64[0] = ReadX(address + 16, instr);
          val.bits.i64[1] = ReadX(address + 24, instr);
          set_vregister(vt2, val);
          break;
        }
        default:
          UnimplementedInstruction(instr);
          return;
      }
    } else {
      // Format(instr, "stp 'vt, 'vt2, 'memop");
      switch (size) {
        case 4:
          WriteW(address, get_vregisterd(vt, 0) & kWRegMask, instr);
          WriteW(address + 4, get_vregisterd(vt2, 0) & kWRegMask, instr);
          break;
        case 8:
          WriteX(address, get_vregisterd(vt, 0), instr);
          WriteX(address + 8, get_vregisterd(vt2, 0), instr);
          break;
        case 16: {
          simd_value_t val;
          get_vregister(vt, &val);
          WriteX(address, val.bits.i64[0], instr);
          WriteX(address + 8, val.bits.i64[1], instr);
          get_vregister(vt2, &val);
          WriteX(address + 16, val.bits.i64[0], instr);
          WriteX(address + 24, val.bits.i64[1], instr);
          break;
        }
        default:
          UnimplementedInstruction(instr);
          return;
      }
    }
  } else {
    // Integer.
    const Register rt = instr->RtField();
    const Register rt2 = instr->Rt2Field();
    if (instr->Bit(22) != 0) {
      // Format(instr, "ldp'sf 'rt, 'rt2, 'memop");
      const bool is_signed = 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 (is_signed) {
          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) {
        if (rt == FP && rt2 == LR) {
          // Special case `ldp fp, lr` for LeaveDartFrame so that the profiler
          // does not get confused by observing the update to fp before lr when
          // our caller is the entry stub, i.e., failing to identify the entry
          // frame. On real hardware, the profiler's signal handler cannot
          // observe a partially executed load-pair instruction.
          set_register(instr, rt2, val2, R31IsZR);
          set_register(instr, rt, val1, R31IsZR);
        } else {
          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, 'rt2, '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) != 0) {
    // 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, R31IsZR);
    } 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, R31IsZR);
    }
  } 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, R31IsZR);
      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, R31IsZR);
      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::DecodeAtomicMemory(Instr* instr) {
  const int32_t size = instr->Bits(30, 2);
  std::memory_order order;
  switch (instr->Bits(22, 2)) {
    case 3:
      order = std::memory_order_acq_rel;
      break;
    case 2:
      order = std::memory_order_acquire;
      break;
    case 1:
      order = std::memory_order_release;
      break;
    case 0:
      order = std::memory_order_relaxed;
      break;
  }
  const Register rs = instr->RsField();
  const Register rn = instr->RnField();
  const Register rt = instr->RtField();
  const int32_t opc = instr->Bits(12, 3);

  if (size == 3) {
    uint64_t in = get_register(rs, R31IsZR);
    auto addr = std::atomic_ref(
        *reinterpret_cast<uint64_t*>(get_register(rn, R31IsSP)));
    uint64_t out;
    switch (opc) {
      case 1:
        out = addr.fetch_and(~in, order);
        break;
      case 3:
        out = addr.fetch_or(in, order);
        break;
      default:
        UNIMPLEMENTED();
    }
    set_register(instr, rt, out, R31IsZR);
  } else if (size == 2) {
    ASSERT(size == 2);
    uint32_t in = get_wregister(rs, R31IsZR);
    auto addr = std::atomic_ref(
        *reinterpret_cast<uint32_t*>(get_register(rn, R31IsSP)));
    uint32_t out;
    switch (opc) {
      case 1:
        out = addr.fetch_and(~in, order);
        break;
      case 3:
        out = addr.fetch_or(in, order);
        break;
      default:
        UNIMPLEMENTED();
    }
    set_wregister(rt, out, R31IsZR);
  } else {
    UNIMPLEMENTED();
  }
}

DART_FORCE_INLINE
void Simulator::DecodeLoadStore(Instr* instr) {
  if (instr->IsAtomicMemoryOp()) {
    DecodeAtomicMemory(instr);
  } else 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() != 0 ? 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() != 0) {
    // 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() != 0) {
    // 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 is_signed) {
  // ARM64 does not trap on integer division by zero. The destination register
  // is instead set to 0.
  if (bottom == 0) {
    return 0;
  }

  if (is_signed) {
    // 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 is_signed) {
  // ARM64 does not trap on integer division by zero. The destination register
  // is instead set to 0.
  if (bottom == 0) {
    return 0;
  }

  if (is_signed) {
    // 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 is_signed = instr->Bit(10) == 1;
      if (instr->SFField() == 1) {
        set_register(instr, rd, divide64(rn_val64, rm_val64, is_signed),
                     R31IsZR);
      } else {
        set_wregister(rd, divide32(rn_val32, rm_val32, is_signed), 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) != 0) {
    idx4 = imm4;
    idx5 = imm5 >> 1;
    element_bytes = 1;
  } else if ((imm5 & 0x2) != 0) {
    idx4 = imm4 >> 1;
    idx5 = imm5 >> 2;
    element_bytes = 2;
  } else if ((imm5 & 0x4) != 0) {
    idx4 = imm4 >> 2;
    idx5 = imm5 >> 3;
    element_bytes = 4;
  } else if ((imm5 & 0x8) != 0) {
    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);
  }
}

static float vminf(float f1, float f2) {
  if (isnan(f1) || isnan(f2)) {
    return std::numeric_limits<float>::quiet_NaN();
  }
  if (f1 == f2) {
    // take care of (-0.0) < 0.0, (they are equal according to minss)
    return signbit(f1) ? f1 : f2;
  }
  return f1 > f2 ? f2 : f1;
}

static float vmaxf(float f1, float f2) {
  if (isnan(f1) || isnan(f2)) {
    return std::numeric_limits<float>::quiet_NaN();
  }
  if (f1 == f2) {
    // take care of (-0.0) < 0.0, (they are equal according to minss)
    return signbit(f1) ? f2 : f1;
  }
  return f1 < f2 ? f2 : f1;
}

static double vmind(double f1, double f2) {
  if (isnan(f1) || isnan(f2)) {
    return std::numeric_limits<double>::quiet_NaN();
  }
  if (f1 == f2) {
    // take care of (-0.0) < 0.0, (they are equal according to minss)
    return signbit(f1) ? f1 : f2;
  }
  return f1 > f2 ? f2 : f1;
}

static double vmaxd(double f1, double f2) {
  if (isnan(f1) || isnan(f2)) {
    return std::numeric_limits<double>::quiet_NaN();
  }
  if (f1 == f2) {
    // take care of (-0.0) < 0.0, (they are equal according to minss)
    return signbit(f1) ? f2 : f1;
  }
  return f1 < f2 ? f2 : f1;
}

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 volatile float vn_flt = bit_cast<float, int32_t>(vn_val);
      const volatile 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 = vminf(vn_flt, vm_flt);
          res = bit_cast<int32_t, float>(m);
        } else {
          // Format(instr, "vmax'vsz 'vd, 'vn, 'vm");
          const float m = vmaxf(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 volatile double vn_dbl = bit_cast<double, int64_t>(vn_val);
      const volatile 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 = vmind(vn_dbl, vm_dbl);
          res = bit_cast<int64_t, double>(m);
        } else {
          // Format(instr, "vmax'vsz 'vd, 'vn, 'vm");
          const double m = vmaxd(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.
DART_FORCE_INLINE
void Simulator::InstructionDecodeImpl(Instr* instr) {
  pc_modified_ = false;

  if (instr->IsLoadStoreOp()) {
    DecodeLoadStore(instr);
  } else if (instr->IsDPImmediateOp()) {
    DecodeDPImmediate(instr);
  } else if (instr->IsCompareBranchOp()) {
    DecodeCompareBranch(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::InstructionDecode(Instr* instr) {
  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");
    }
  }
  InstructionDecodeImpl(instr);
}

void Simulator::Execute() {
  if (FLAG_stop_sim_at == ULLONG_MAX && FLAG_trace_sim_after == ULLONG_MAX)
      [[unlikely]] {
    ExecuteNoTrace();
  } else {
    ExecuteTrace();
  }

  // We can't instrument the runtime.
  memory_.FlushAll();
}

void Simulator::ExecuteNoTrace() {
  // 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();

  // 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_++;
    InstructionDecodeImpl(instr);
    program_counter = get_pc();
  }
}

void Simulator::ExecuteTrace() {
  // 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();

  // 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();
  }
}

// Verifies that callee-saved registers are preserved across Dart execution.
// On construction, saves and overwrites callee-saved registers with a known
// value. On destruction, asserts that the known value is still present and
// restores the original values.
class CalleeRegisterVerifier {
 public:
  explicit CalleeRegisterVerifier(Simulator* sim)
      : sim_(sim),
        callee_saved_value_(
            bit_cast<int64_t, double>(static_cast<double>(sim->get_icount()))) {
    for (int i = kAbiFirstPreservedCpuReg; i <= kAbiLastPreservedCpuReg; i++) {
      const Register r = static_cast<Register>(i);
      preserved_cpu_[i - kAbiFirstPreservedCpuReg] = sim->get_register(r);
      sim->set_register(nullptr, r, callee_saved_value_);
    }
    // Only the bottom half of the V registers must be preserved.
    for (int i = kAbiFirstPreservedFpuReg; i <= kAbiLastPreservedFpuReg; i++) {
      const VRegister r = static_cast<VRegister>(i);
      preserved_fpu_[i - kAbiFirstPreservedFpuReg] = sim->get_vregisterd(r, 0);
      sim->set_vregisterd(r, 0, callee_saved_value_);
      sim->set_vregisterd(r, 1, 0);
    }
  }

  ~CalleeRegisterVerifier() {
    for (int i = kAbiFirstPreservedCpuReg; i <= kAbiLastPreservedCpuReg; i++) {
      const Register r = static_cast<Register>(i);
      ASSERT(callee_saved_value_ == sim_->get_register(r));
      sim_->set_register(nullptr, r,
                         preserved_cpu_[i - kAbiFirstPreservedCpuReg]);
    }
    for (int i = kAbiFirstPreservedFpuReg; i <= kAbiLastPreservedFpuReg; i++) {
      const VRegister r = static_cast<VRegister>(i);
      ASSERT(callee_saved_value_ == sim_->get_vregisterd(r, 0));
      sim_->set_vregisterd(r, 0, preserved_fpu_[i - kAbiFirstPreservedFpuReg]);
      sim_->set_vregisterd(r, 1, 0);
    }
  }

 private:
  Simulator* const sim_;
  const int64_t callee_saved_value_;
  int64_t preserved_cpu_[kAbiPreservedCpuRegCount];
  int64_t preserved_fpu_[kAbiPreservedFpuRegCount];

  DISALLOW_COPY_AND_ASSIGN(CalleeRegisterVerifier);
};

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(nullptr, R0, parameter0);
    set_register(nullptr, R1, parameter1);
    set_register(nullptr, R2, parameter2);
    set_register(nullptr, 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(nullptr, 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(nullptr, LR, kEndSimulatingPC);

  {
    // Verify callee-saved registers are preserved across Dart execution.
    CalleeRegisterVerifier callee_saved(this);
    Execute();
  }

  // Restore the SP register and return R0.
  set_register(nullptr, 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() != nullptr && buf->link()->sp() <= sp) {
    buf = buf->link();
  }
  ASSERT(buf != nullptr);

  // 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(nullptr, SP, static_cast<int64_t>(sp));
  set_register(nullptr, FP, static_cast<int64_t>(fp));
  set_register(nullptr, THR, reinterpret_cast<int64_t>(thread));
  set_register(nullptr, R31, thread->saved_stack_limit() - 4096);
#if defined(DART_TARGET_OS_FUCHSIA)
  set_register(nullptr, R18, thread->saved_shadow_call_stack());
#endif
  // Set the tag.
  thread->set_vm_tag(VMTag::kDartTagId);
  // Clear top exit frame.
  thread->set_top_exit_frame_info(0);
  // Restore pool pointer.
  int64_t code =
      *reinterpret_cast<int64_t*>(fp + kPcMarkerSlotFromFp * kWordSize);
  int64_t pp = FLAG_precompiled_mode
                   ? 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(nullptr, CODE_REG, code);
  set_register(nullptr, PP, pp);
  set_register(
      nullptr, HEAP_BITS,
      (thread->write_barrier_mask() << 32) | (thread->heap_base() >> 32));
  set_register(nullptr, NULL_REG, static_cast<int64_t>(Object::null()));
  if (FLAG_precompiled_mode) {
    set_register(nullptr, DISPATCH_TABLE_REG,
                 reinterpret_cast<int64_t>(thread->dispatch_table_array()));
  }

  buf->Longjmp();
}

}  // namespace dart

#endif  // !defined(DART_INCLUDE_SIMULATOR)

#endif  // defined TARGET_ARCH_ARM64
