// Copyright (c) 2013, 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.

#if !defined(DART_PRECOMPILED_RUNTIME)

#include "vm/deopt_instructions.h"

#include "vm/code_patcher.h"
#include "vm/compiler/assembler/assembler.h"
#include "vm/compiler/assembler/disassembler.h"
#include "vm/compiler/backend/il.h"
#include "vm/compiler/backend/locations.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/parser.h"
#include "vm/stack_frame.h"
#include "vm/thread.h"
#include "vm/timeline.h"

namespace dart {

DEFINE_FLAG(bool,
            compress_deopt_info,
            true,
            "Compress the size of the deoptimization info for optimized code.");
DECLARE_FLAG(bool, trace_deoptimization);
DECLARE_FLAG(bool, trace_deoptimization_verbose);

DeoptContext::DeoptContext(const StackFrame* frame,
                           const Code& code,
                           DestFrameOptions dest_options,
                           fpu_register_t* fpu_registers,
                           intptr_t* cpu_registers,
                           bool is_lazy_deopt,
                           bool deoptimizing_code)
    : code_(code.ptr()),
      object_pool_(code.GetObjectPool()),
      deopt_info_(TypedData::null()),
      dest_frame_is_allocated_(false),
      dest_frame_(nullptr),
      dest_frame_size_(0),
      source_frame_is_allocated_(false),
      source_frame_(nullptr),
      source_frame_size_(0),
      cpu_registers_(cpu_registers),
      fpu_registers_(fpu_registers),
      num_args_(0),
      deopt_reason_(ICData::kDeoptUnknown),
      deopt_flags_(0),
      thread_(Thread::Current()),
      deopt_start_micros_(0),
      deferred_slots_(nullptr),
      deferred_objects_count_(0),
      deferred_objects_(nullptr),
      is_lazy_deopt_(is_lazy_deopt),
      deoptimizing_code_(deoptimizing_code) {
  const TypedData& deopt_info = TypedData::Handle(
      code.GetDeoptInfoAtPc(frame->pc(), &deopt_reason_, &deopt_flags_));
#if defined(DEBUG)
  if (deopt_info.IsNull()) {
    OS::PrintErr("Missing deopt info for pc %" Px "\n", frame->pc());
    DisassembleToStdout formatter;
    code.Disassemble(&formatter);
  }
#endif
  ASSERT(!deopt_info.IsNull());
  deopt_info_ = deopt_info.ptr();

  const Function& function = Function::Handle(code.function());

  // Do not include incoming arguments if there are optional arguments
  // (they are copied into local space at method entry).
  num_args_ =
      function.MakesCopyOfParameters() ? 0 : function.num_fixed_parameters();

  // The fixed size section of the (fake) Dart frame called via a stub by the
  // optimized function contains FP, PP (ARM only), PC-marker and
  // return-address. This section is copied as well, so that its contained
  // values can be updated before returning to the deoptimized function.
  ASSERT(frame->fp() >= frame->sp());
  const intptr_t frame_size = (frame->fp() - frame->sp()) / kWordSize;

  source_frame_size_ = +kDartFrameFixedSize   // For saved values below sp.
                       + frame_size           // For frame size incl. sp.
                       + 1                    // For fp.
                       + kParamEndSlotFromFp  // For saved values above fp.
                       + num_args_;           // For arguments.

  source_frame_ = FrameBase(frame);

  if (dest_options == kDestIsOriginalFrame) {
    // Work from a copy of the source frame.
    intptr_t* original_frame = source_frame_;
    source_frame_ = new intptr_t[source_frame_size_];
    ASSERT(source_frame_ != nullptr);
    for (intptr_t i = 0; i < source_frame_size_; i++) {
      source_frame_[i] = original_frame[i];
    }
    source_frame_is_allocated_ = true;
  }
  caller_fp_ = GetSourceFp();

  dest_frame_size_ = DeoptInfo::FrameSize(deopt_info);

  if (dest_options == kDestIsAllocated) {
    dest_frame_ = new intptr_t[dest_frame_size_];
    ASSERT(source_frame_ != nullptr);
    for (intptr_t i = 0; i < dest_frame_size_; i++) {
      dest_frame_[i] = 0;
    }
    dest_frame_is_allocated_ = true;
  }

  if (dest_options != kDestIsAllocated) {
    // kDestIsAllocated is used by the debugger to generate a stack trace
    // and does not signal a real deopt.
    deopt_start_micros_ = OS::GetCurrentMonotonicMicros();
  }

  if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) {
    THR_Print(
        "Deoptimizing (reason %d '%s') at "
        "pc=%" Pp " fp=%" Pp " '%s' (count %d)\n",
        deopt_reason(), DeoptReasonToCString(deopt_reason()), frame->pc(),
        frame->fp(), function.ToFullyQualifiedCString(),
        function.deoptimization_counter());
  }
}

DeoptContext::~DeoptContext() {
  // Delete memory for source frame and registers.
  if (source_frame_is_allocated_) {
    delete[] source_frame_;
  }
  source_frame_ = nullptr;
  delete[] fpu_registers_;
  delete[] cpu_registers_;
  fpu_registers_ = nullptr;
  cpu_registers_ = nullptr;
  if (dest_frame_is_allocated_) {
    delete[] dest_frame_;
  }
  dest_frame_ = nullptr;

  // Delete all deferred objects.
  for (intptr_t i = 0; i < deferred_objects_count_; i++) {
    delete deferred_objects_[i];
  }
  delete[] deferred_objects_;
  deferred_objects_ = nullptr;
  deferred_objects_count_ = 0;

#if defined(SUPPORT_TIMELINE)
  if (deopt_start_micros_ != 0) {
    TimelineStream* compiler_stream = Timeline::GetCompilerStream();
    ASSERT(compiler_stream != nullptr);
    if (compiler_stream->enabled()) {
      // Allocate all Dart objects needed before calling StartEvent,
      // which blocks safe points until Complete is called.
      const Code& code = Code::Handle(zone(), code_);
      const Function& function = Function::Handle(zone(), code.function());
      const String& function_name =
          String::Handle(zone(), function.QualifiedScrubbedName());
      const char* reason = DeoptReasonToCString(deopt_reason());
      const int counter = function.deoptimization_counter();
      TimelineEvent* timeline_event = compiler_stream->StartEvent();
      if (timeline_event != nullptr) {
        timeline_event->Duration("Deoptimize", deopt_start_micros_,
                                 OS::GetCurrentMonotonicMicros());
        timeline_event->SetNumArguments(3);
        timeline_event->CopyArgument(0, "function", function_name.ToCString());
        timeline_event->CopyArgument(1, "reason", reason);
        timeline_event->FormatArgument(2, "deoptimizationCount", "%d", counter);
        timeline_event->Complete();
      }
    }
  }
#endif  // !PRODUCT
}

void DeoptContext::VisitObjectPointers(ObjectPointerVisitor* visitor) {
  visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&code_));
  visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&object_pool_));
  visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&deopt_info_));

  // Visit any object pointers on the destination stack.
  if (dest_frame_is_allocated_) {
    for (intptr_t i = 0; i < dest_frame_size_; i++) {
      if (dest_frame_[i] != 0) {
        visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&dest_frame_[i]));
      }
    }
  }
}

intptr_t DeoptContext::DestStackAdjustment() const {
  return dest_frame_size_ - kDartFrameFixedSize - num_args_ - 1  // For fp.
         - kParamEndSlotFromFp;
}

intptr_t DeoptContext::GetSourceFp() const {
  return source_frame_[source_frame_size_ - 1 - num_args_ -
                       kParamEndSlotFromFp + kSavedCallerFpSlotFromFp];
}

intptr_t DeoptContext::GetSourcePp() const {
  return source_frame_[source_frame_size_ - 1 - num_args_ -
                       kParamEndSlotFromFp +
                       StackFrame::SavedCallerPpSlotFromFp()];
}

intptr_t DeoptContext::GetSourcePc() const {
  return source_frame_[source_frame_size_ - num_args_ + kSavedPcSlotFromSp];
}

intptr_t DeoptContext::GetCallerFp() const {
  return caller_fp_;
}

void DeoptContext::SetCallerFp(intptr_t caller_fp) {
  caller_fp_ = caller_fp;
}

static bool IsObjectInstruction(DeoptInstr::Kind kind) {
  switch (kind) {
    case DeoptInstr::kConstant:
    case DeoptInstr::kPp:
    case DeoptInstr::kCallerPp:
    case DeoptInstr::kMaterializedObjectRef:
    case DeoptInstr::kFloat32x4:
    case DeoptInstr::kInt32x4:
    case DeoptInstr::kFloat64x2:
    case DeoptInstr::kWord:
    case DeoptInstr::kFloat:
    case DeoptInstr::kDouble:
    case DeoptInstr::kMint:
    case DeoptInstr::kMintPair:
    case DeoptInstr::kInt32:
    case DeoptInstr::kUint32:
      return true;

    case DeoptInstr::kRetAddress:
    case DeoptInstr::kPcMarker:
    case DeoptInstr::kCallerFp:
    case DeoptInstr::kCallerPc:
      return false;

    case DeoptInstr::kMaterializeObject:
    default:
      // We should not encounter these instructions when filling stack slots.
      UNREACHABLE();
      return false;
  }
  UNREACHABLE();
  return false;
}

intptr_t DeoptContext::FillDestFrame() {
  const Code& code = Code::Handle(code_);
  const TypedData& deopt_info = TypedData::Handle(deopt_info_);

  GrowableArray<DeoptInstr*> deopt_instructions;
  const Array& deopt_table = Array::Handle(code.deopt_info_array());
  ASSERT(!deopt_table.IsNull());
  DeoptInfo::Unpack(deopt_table, deopt_info, &deopt_instructions);

  const intptr_t len = deopt_instructions.length();
  const intptr_t frame_size = dest_frame_size_;

  // For now, we never place non-objects in the deoptimized frame if
  // the destination frame is a copy.  This allows us to copy the
  // deoptimized frame into an Array.
  const bool objects_only = dest_frame_is_allocated_;

  // All kMaterializeObject instructions are emitted before the instructions
  // that describe stack frames. Skip them and defer materialization of
  // objects until the frame is fully reconstructed and it is safe to perform
  // GC.
  // Arguments (class of the instance to allocate and field-value pairs) are
  // described as part of the expression stack for the bottom-most deoptimized
  // frame. They will be used during materialization and removed from the stack
  // right before control switches to the unoptimized code.
  const intptr_t num_materializations =
      DeoptInfo::NumMaterializations(deopt_instructions);
  PrepareForDeferredMaterialization(num_materializations);
  for (intptr_t from_index = 0, to_index = kDartFrameFixedSize;
       from_index < num_materializations; from_index++) {
    const intptr_t field_count =
        DeoptInstr::GetFieldCount(deopt_instructions[from_index]);
    intptr_t* args = GetDestFrameAddressAt(to_index);
    DeferredObject* obj = new DeferredObject(field_count, args);
    SetDeferredObjectAt(from_index, obj);
    to_index += obj->ArgumentCount();
  }

  // Populate stack frames.
  intptr_t frame_count = 0;
  for (intptr_t to_index = frame_size - 1, from_index = len - 1; to_index >= 0;
       to_index--, from_index--) {
    intptr_t* to_addr = GetDestFrameAddressAt(to_index);
    DeoptInstr* instr = deopt_instructions[from_index];
    if (!objects_only || IsObjectInstruction(instr->kind())) {
      instr->Execute(this, to_addr);
    } else {
      *reinterpret_cast<ObjectPtr*>(to_addr) = Object::null();
    }
    if (instr->kind() == DeoptInstr::kRetAddress) {
      frame_count++;
    }
  }

  if (FLAG_trace_deoptimization_verbose) {
    for (intptr_t i = 0; i < frame_size; i++) {
      intptr_t* to_addr = GetDestFrameAddressAt(i);
      THR_Print("*%" Pd ". [%p] 0x%" Px " [%s]\n", i, to_addr, *to_addr,
                deopt_instructions[i + (len - frame_size)]->ToCString());
    }
  }

  return frame_count;
}

static void FillDeferredSlots(DeoptContext* deopt_context,
                              DeferredSlot** slot_list) {
  DeferredSlot* slot = *slot_list;
  *slot_list = nullptr;

  while (slot != nullptr) {
    DeferredSlot* current = slot;
    slot = slot->next();

    current->Materialize(deopt_context);

    delete current;
  }
}

// Materializes all deferred objects.  Returns the total number of
// artificial arguments used during deoptimization.
intptr_t DeoptContext::MaterializeDeferredObjects() {
  // Populate slots with references to all unboxed "primitive" values (doubles,
  // mints, simd) and deferred objects. Deferred objects are only allocated
  // but not filled with data. This is done later because deferred objects
  // can references each other.
  FillDeferredSlots(this, &deferred_slots_);

  // Compute total number of artificial arguments used during deoptimization.
  intptr_t deopt_arg_count = 0;
  for (intptr_t i = 0; i < DeferredObjectsCount(); i++) {
    GetDeferredObject(i)->Fill();
    deopt_arg_count += GetDeferredObject(i)->ArgumentCount();
  }

  // Since this is the only step where GC can occur during deoptimization,
  // use it to report the source line where deoptimization occurred.
  if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) {
    DartFrameIterator iterator(Thread::Current(),
                               StackFrameIterator::kNoCrossThreadIteration);
    StackFrame* top_frame = iterator.NextFrame();
    ASSERT(top_frame != nullptr);
    const Code& code = Code::Handle(top_frame->LookupDartCode());
    const Function& top_function = Function::Handle(code.function());
    const Script& script = Script::Handle(top_function.script());
    const TokenPosition token_pos = code.GetTokenIndexOfPC(top_frame->pc());
    THR_Print("  Function: %s\n", top_function.ToFullyQualifiedCString());
    intptr_t line;
    if (script.GetTokenLocation(token_pos, &line)) {
      String& line_string = String::Handle(script.GetLine(line));
      char line_buffer[80];
      Utils::SNPrint(line_buffer, sizeof(line_buffer), "  Line %" Pd ": '%s'",
                     line, line_string.ToCString());
      THR_Print("%s\n", line_buffer);
    }
    THR_Print("  Deopt args: %" Pd "\n", deopt_arg_count);
  }

  return deopt_arg_count;
}

ArrayPtr DeoptContext::DestFrameAsArray() {
  ASSERT(dest_frame_ != nullptr && dest_frame_is_allocated_);
  const Array& dest_array = Array::Handle(zone(), Array::New(dest_frame_size_));
  PassiveObject& obj = PassiveObject::Handle(zone());
  for (intptr_t i = 0; i < dest_frame_size_; i++) {
    obj = static_cast<ObjectPtr>(dest_frame_[i]);
    dest_array.SetAt(i, obj);
  }
  return dest_array.ptr();
}

// Deoptimization instruction creating return address using function and
// deopt-id stored at 'object_table_index'.
class DeoptRetAddressInstr : public DeoptInstr {
 public:
  DeoptRetAddressInstr(intptr_t object_table_index, intptr_t deopt_id)
      : object_table_index_(object_table_index), deopt_id_(deopt_id) {
    ASSERT(object_table_index >= 0);
    ASSERT(deopt_id >= 0);
  }

  explicit DeoptRetAddressInstr(intptr_t source_index)
      : object_table_index_(ObjectTableIndex::decode(source_index)),
        deopt_id_(DeoptId::decode(source_index)) {}

  virtual intptr_t source_index() const {
    return ObjectTableIndex::encode(object_table_index_) |
           DeoptId::encode(deopt_id_);
  }

  virtual DeoptInstr::Kind kind() const { return kRetAddress; }

  virtual const char* ArgumentsToCString() const {
    return Thread::Current()->zone()->PrintToString(
        "%" Pd ", %" Pd "", object_table_index_, deopt_id_);
  }

  void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) {
    *dest_addr = Smi::RawValue(0);
    deopt_context->DeferRetAddrMaterialization(object_table_index_, deopt_id_,
                                               dest_addr);
  }

  intptr_t object_table_index() const { return object_table_index_; }
  intptr_t deopt_id() const { return deopt_id_; }

 private:
  static constexpr intptr_t kFieldWidth = kBitsPerWord / 2;
  using ObjectTableIndex = BitField<intptr_t, intptr_t, 0, kFieldWidth>;
  using DeoptId =
      BitField<intptr_t, intptr_t, ObjectTableIndex::kNextBit, kFieldWidth>;

  const intptr_t object_table_index_;
  const intptr_t deopt_id_;

  DISALLOW_COPY_AND_ASSIGN(DeoptRetAddressInstr);
};

// Deoptimization instruction moving a constant stored at 'object_table_index'.
class DeoptConstantInstr : public DeoptInstr {
 public:
  explicit DeoptConstantInstr(intptr_t object_table_index)
      : object_table_index_(object_table_index) {
    ASSERT(object_table_index >= 0);
  }

  virtual intptr_t source_index() const { return object_table_index_; }
  virtual DeoptInstr::Kind kind() const { return kConstant; }

  virtual const char* ArgumentsToCString() const {
    return Thread::Current()->zone()->PrintToString("%" Pd "",
                                                    object_table_index_);
  }

  void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) {
    const PassiveObject& obj = PassiveObject::Handle(
        deopt_context->zone(), deopt_context->ObjectAt(object_table_index_));
    *reinterpret_cast<ObjectPtr*>(dest_addr) = obj.ptr();
  }

 private:
  const intptr_t object_table_index_;

  DISALLOW_COPY_AND_ASSIGN(DeoptConstantInstr);
};

// Deoptimization instruction moving value from optimized frame at
// 'source_index' to specified slots in the unoptimized frame.
// 'source_index' represents the slot index of the frame (0 being
// first argument) and accounts for saved return address, frame
// pointer, pool pointer and pc marker.
// Deoptimization instruction moving a CPU register.
class DeoptWordInstr : public DeoptInstr {
 public:
  explicit DeoptWordInstr(intptr_t source_index) : source_(source_index) {}

  explicit DeoptWordInstr(const CpuRegisterSource& source) : source_(source) {}

  virtual intptr_t source_index() const { return source_.source_index(); }
  virtual DeoptInstr::Kind kind() const { return kWord; }

  virtual const char* ArgumentsToCString() const { return source_.ToCString(); }

  void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) {
    *dest_addr = source_.Value<intptr_t>(deopt_context);
  }

 private:
  const CpuRegisterSource source_;

  DISALLOW_COPY_AND_ASSIGN(DeoptWordInstr);
};

class DeoptIntegerInstrBase : public DeoptInstr {
 public:
  DeoptIntegerInstrBase() {}

  void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) {
    const int64_t value = GetValue(deopt_context);
    if (Smi::IsValid(value)) {
      *dest_addr = Smi::RawValue(static_cast<intptr_t>(value));
    } else {
      *dest_addr = Smi::RawValue(0);
      deopt_context->DeferMintMaterialization(
          value, reinterpret_cast<MintPtr*>(dest_addr));
    }
  }

  virtual int64_t GetValue(DeoptContext* deopt_context) = 0;

 private:
  DISALLOW_COPY_AND_ASSIGN(DeoptIntegerInstrBase);
};

class DeoptMintPairInstr : public DeoptIntegerInstrBase {
 public:
  explicit DeoptMintPairInstr(intptr_t source_index)
      : DeoptIntegerInstrBase(),
        lo_(LoRegister::decode(source_index)),
        hi_(HiRegister::decode(source_index)) {}

  DeoptMintPairInstr(const CpuRegisterSource& lo, const CpuRegisterSource& hi)
      : DeoptIntegerInstrBase(), lo_(lo), hi_(hi) {}

  virtual intptr_t source_index() const {
    return LoRegister::encode(lo_.source_index()) |
           HiRegister::encode(hi_.source_index());
  }
  virtual DeoptInstr::Kind kind() const { return kMintPair; }

  virtual const char* ArgumentsToCString() const {
    return Thread::Current()->zone()->PrintToString("%s,%s", lo_.ToCString(),
                                                    hi_.ToCString());
  }

  virtual int64_t GetValue(DeoptContext* deopt_context) {
    return Utils::LowHighTo64Bits(lo_.Value<uint32_t>(deopt_context),
                                  hi_.Value<int32_t>(deopt_context));
  }

 private:
  static constexpr intptr_t kFieldWidth = kBitsPerWord / 2;
  using LoRegister = BitField<intptr_t, intptr_t, 0, kFieldWidth>;
  using HiRegister =
      BitField<intptr_t, intptr_t, LoRegister::kNextBit, kFieldWidth>;

  const CpuRegisterSource lo_;
  const CpuRegisterSource hi_;

  DISALLOW_COPY_AND_ASSIGN(DeoptMintPairInstr);
};

template <DeoptInstr::Kind K, CatchEntryMove::SourceKind slot_kind, typename T>
class DeoptIntInstr : public DeoptIntegerInstrBase {
 public:
  explicit DeoptIntInstr(intptr_t source_index)
      : DeoptIntegerInstrBase(), source_(source_index) {}

  explicit DeoptIntInstr(const CpuRegisterSource& source)
      : DeoptIntegerInstrBase(), source_(source) {}

  virtual intptr_t source_index() const { return source_.source_index(); }
  virtual DeoptInstr::Kind kind() const { return K; }

  virtual const char* ArgumentsToCString() const { return source_.ToCString(); }

  virtual int64_t GetValue(DeoptContext* deopt_context) {
    return static_cast<int64_t>(source_.Value<T>(deopt_context));
  }

 private:
  const CpuRegisterSource source_;

  DISALLOW_COPY_AND_ASSIGN(DeoptIntInstr);
};

typedef DeoptIntInstr<DeoptInstr::kUint32,
                      CatchEntryMove::SourceKind::kUint32Slot,
                      uint32_t>
    DeoptUint32Instr;
typedef DeoptIntInstr<DeoptInstr::kInt32,
                      CatchEntryMove::SourceKind::kInt32Slot,
                      int32_t>
    DeoptInt32Instr;
typedef DeoptIntInstr<DeoptInstr::kMint,
                      CatchEntryMove::SourceKind::kInt64Slot,
                      int64_t>
    DeoptMintInstr;

template <DeoptInstr::Kind K,
          CatchEntryMove::SourceKind slot_kind,
          typename Type,
          typename PtrType>
class DeoptFpuInstr : public DeoptInstr {
 public:
  explicit DeoptFpuInstr(intptr_t source_index) : source_(source_index) {}

  explicit DeoptFpuInstr(const FpuRegisterSource& source) : source_(source) {}

  virtual intptr_t source_index() const { return source_.source_index(); }
  virtual DeoptInstr::Kind kind() const { return K; }

  virtual const char* ArgumentsToCString() const { return source_.ToCString(); }

  void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) {
    *dest_addr = Smi::RawValue(0);
    deopt_context->DeferMaterialization(source_.Value<Type>(deopt_context),
                                        reinterpret_cast<PtrType*>(dest_addr));
  }

 private:
  const FpuRegisterSource source_;

  DISALLOW_COPY_AND_ASSIGN(DeoptFpuInstr);
};

typedef DeoptFpuInstr<DeoptInstr::kFloat,
                      CatchEntryMove::SourceKind::kFloatSlot,
                      float,
                      DoublePtr>
    DeoptFloatInstr;

typedef DeoptFpuInstr<DeoptInstr::kDouble,
                      CatchEntryMove::SourceKind::kDoubleSlot,
                      double,
                      DoublePtr>
    DeoptDoubleInstr;

// Simd128 types.
typedef DeoptFpuInstr<DeoptInstr::kFloat32x4,
                      CatchEntryMove::SourceKind::kFloat32x4Slot,
                      simd128_value_t,
                      Float32x4Ptr>
    DeoptFloat32x4Instr;
typedef DeoptFpuInstr<DeoptInstr::kFloat64x2,
                      CatchEntryMove::SourceKind::kFloat64x2Slot,
                      simd128_value_t,
                      Float64x2Ptr>
    DeoptFloat64x2Instr;
typedef DeoptFpuInstr<DeoptInstr::kInt32x4,
                      CatchEntryMove::SourceKind::kInt32x4Slot,
                      simd128_value_t,
                      Int32x4Ptr>
    DeoptInt32x4Instr;

// Deoptimization instruction creating a PC marker for the code of
// function at 'object_table_index'.
class DeoptPcMarkerInstr : public DeoptInstr {
 public:
  explicit DeoptPcMarkerInstr(intptr_t object_table_index)
      : object_table_index_(object_table_index) {
    ASSERT(object_table_index >= 0);
  }

  virtual intptr_t source_index() const { return object_table_index_; }
  virtual DeoptInstr::Kind kind() const { return kPcMarker; }

  virtual const char* ArgumentsToCString() const {
    return Thread::Current()->zone()->PrintToString("%" Pd "",
                                                    object_table_index_);
  }

  void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) {
    Function& function = Function::Handle(deopt_context->zone());
    function ^= deopt_context->ObjectAt(object_table_index_);
    if (function.IsNull()) {
      *reinterpret_cast<ObjectPtr*>(dest_addr) =
          deopt_context->is_lazy_deopt()
              ? StubCode::DeoptimizeLazyFromReturn().ptr()
              : StubCode::Deoptimize().ptr();
      return;
    }

    // We don't always have the Code object for the frame's corresponding
    // unoptimized code as it may have been collected. Use a stub as the pc
    // marker until we can recreate that Code object during deferred
    // materialization to maintain the invariant that Dart frames always have
    // a pc marker.
    *reinterpret_cast<ObjectPtr*>(dest_addr) =
        StubCode::FrameAwaitingMaterialization().ptr();
    deopt_context->DeferPcMarkerMaterialization(object_table_index_, dest_addr);
  }

 private:
  intptr_t object_table_index_;

  DISALLOW_COPY_AND_ASSIGN(DeoptPcMarkerInstr);
};

// Deoptimization instruction creating a pool pointer for the code of
// function at 'object_table_index'.
class DeoptPpInstr : public DeoptInstr {
 public:
  explicit DeoptPpInstr(intptr_t object_table_index)
      : object_table_index_(object_table_index) {
    ASSERT(object_table_index >= 0);
  }

  virtual intptr_t source_index() const { return object_table_index_; }
  virtual DeoptInstr::Kind kind() const { return kPp; }

  virtual const char* ArgumentsToCString() const {
    return Thread::Current()->zone()->PrintToString("%" Pd "",
                                                    object_table_index_);
  }

  void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) {
    *dest_addr = Smi::RawValue(0);
    deopt_context->DeferPpMaterialization(
        object_table_index_, reinterpret_cast<ObjectPtr*>(dest_addr));
  }

 private:
  intptr_t object_table_index_;

  DISALLOW_COPY_AND_ASSIGN(DeoptPpInstr);
};

// Deoptimization instruction copying the caller saved FP from optimized frame.
class DeoptCallerFpInstr : public DeoptInstr {
 public:
  DeoptCallerFpInstr() {}

  virtual intptr_t source_index() const { return 0; }
  virtual DeoptInstr::Kind kind() const { return kCallerFp; }

  void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) {
    *dest_addr = deopt_context->GetCallerFp();
    deopt_context->SetCallerFp(
        reinterpret_cast<intptr_t>(dest_addr - kSavedCallerFpSlotFromFp));
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(DeoptCallerFpInstr);
};

// Deoptimization instruction copying the caller saved PP from optimized frame.
class DeoptCallerPpInstr : public DeoptInstr {
 public:
  DeoptCallerPpInstr() {}

  virtual intptr_t source_index() const { return 0; }
  virtual DeoptInstr::Kind kind() const { return kCallerPp; }

  void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) {
    *dest_addr = deopt_context->GetSourcePp();
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(DeoptCallerPpInstr);
};

// Deoptimization instruction copying the caller return address from optimized
// frame.
class DeoptCallerPcInstr : public DeoptInstr {
 public:
  DeoptCallerPcInstr() {}

  virtual intptr_t source_index() const { return 0; }
  virtual DeoptInstr::Kind kind() const { return kCallerPc; }

  void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) {
    *dest_addr = deopt_context->GetSourcePc();
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(DeoptCallerPcInstr);
};

// Write reference to a materialized object with the given index into the
// stack slot.
class DeoptMaterializedObjectRefInstr : public DeoptInstr {
 public:
  explicit DeoptMaterializedObjectRefInstr(intptr_t index) : index_(index) {
    ASSERT(index >= 0);
  }

  virtual intptr_t source_index() const { return index_; }
  virtual DeoptInstr::Kind kind() const { return kMaterializedObjectRef; }

  virtual const char* ArgumentsToCString() const {
    return Thread::Current()->zone()->PrintToString("#%" Pd "", index_);
  }

  void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) {
    *reinterpret_cast<SmiPtr*>(dest_addr) = Smi::New(0);
    deopt_context->DeferMaterializedObjectRef(index_, dest_addr);
  }

 private:
  intptr_t index_;

  DISALLOW_COPY_AND_ASSIGN(DeoptMaterializedObjectRefInstr);
};

// Materialize object with the given number of fields.
// Arguments for materialization (class and field-value pairs) are pushed
// to the expression stack of the bottom-most frame.
class DeoptMaterializeObjectInstr : public DeoptInstr {
 public:
  explicit DeoptMaterializeObjectInstr(intptr_t field_count)
      : field_count_(field_count) {
    ASSERT(field_count >= 0);
  }

  virtual intptr_t source_index() const { return field_count_; }
  virtual DeoptInstr::Kind kind() const { return kMaterializeObject; }

  virtual const char* ArgumentsToCString() const {
    return Thread::Current()->zone()->PrintToString("%" Pd "", field_count_);
  }

  void Execute(DeoptContext* deopt_context, intptr_t* dest_addr) {
    // This instructions are executed manually by the DeoptimizeWithDeoptInfo.
    UNREACHABLE();
  }

 private:
  intptr_t field_count_;

  DISALLOW_COPY_AND_ASSIGN(DeoptMaterializeObjectInstr);
};

uword DeoptInstr::GetRetAddress(DeoptInstr* instr,
                                const ObjectPool& object_table,
                                Code* code) {
  ASSERT(instr->kind() == kRetAddress);
  DeoptRetAddressInstr* ret_address_instr =
      static_cast<DeoptRetAddressInstr*>(instr);
  ASSERT(!object_table.IsNull());
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  Function& function = Function::Handle(zone);
  function ^= object_table.ObjectAt(ret_address_instr->object_table_index());
  ASSERT(!function.ForceOptimize());
  ASSERT(code != nullptr);
  const Error& error =
      Error::Handle(zone, Compiler::EnsureUnoptimizedCode(thread, function));
  if (!error.IsNull()) {
    Exceptions::PropagateError(error);
  }
  *code = function.unoptimized_code();
  ASSERT(!code->IsNull());
  uword res = code->GetPcForDeoptId(ret_address_instr->deopt_id(),
                                    UntaggedPcDescriptors::kDeopt);
  ASSERT(res != 0);
  return res;
}

DeoptInstr* DeoptInstr::Create(intptr_t kind_as_int, intptr_t source_index) {
  Kind kind = static_cast<Kind>(kind_as_int);
  switch (kind) {
    case kWord:
      return new DeoptWordInstr(source_index);
    case kFloat:
      return new DeoptFloatInstr(source_index);
    case kDouble:
      return new DeoptDoubleInstr(source_index);
    case kMint:
      return new DeoptMintInstr(source_index);
    case kMintPair:
      return new DeoptMintPairInstr(source_index);
    case kInt32:
      return new DeoptInt32Instr(source_index);
    case kUint32:
      return new DeoptUint32Instr(source_index);
    case kFloat32x4:
      return new DeoptFloat32x4Instr(source_index);
    case kFloat64x2:
      return new DeoptFloat64x2Instr(source_index);
    case kInt32x4:
      return new DeoptInt32x4Instr(source_index);
    case kRetAddress:
      return new DeoptRetAddressInstr(source_index);
    case kConstant:
      return new DeoptConstantInstr(source_index);
    case kPcMarker:
      return new DeoptPcMarkerInstr(source_index);
    case kPp:
      return new DeoptPpInstr(source_index);
    case kCallerFp:
      return new DeoptCallerFpInstr();
    case kCallerPp:
      return new DeoptCallerPpInstr();
    case kCallerPc:
      return new DeoptCallerPcInstr();
    case kMaterializedObjectRef:
      return new DeoptMaterializedObjectRefInstr(source_index);
    case kMaterializeObject:
      return new DeoptMaterializeObjectInstr(source_index);
  }
  UNREACHABLE();
  return nullptr;
}

const char* DeoptInstr::KindToCString(Kind kind) {
  switch (kind) {
    case kWord:
      return "word";
    case kFloat:
      return "float";
    case kDouble:
      return "double";
    case kMint:
    case kMintPair:
      return "mint";
    case kInt32:
      return "int32";
    case kUint32:
      return "uint32";
    case kFloat32x4:
      return "float32x4";
    case kFloat64x2:
      return "float64x2";
    case kInt32x4:
      return "int32x4";
    case kRetAddress:
      return "retaddr";
    case kConstant:
      return "const";
    case kPcMarker:
      return "pc";
    case kPp:
      return "pp";
    case kCallerFp:
      return "callerfp";
    case kCallerPp:
      return "callerpp";
    case kCallerPc:
      return "callerpc";
    case kMaterializedObjectRef:
      return "ref";
    case kMaterializeObject:
      return "mat";
  }
  UNREACHABLE();
  return nullptr;
}

class DeoptInfoBuilder::TrieNode : public ZoneObject {
 public:
  // Construct the root node representing the implicit "shared" terminator
  // at the end of each deopt info.
  TrieNode() : instruction_(nullptr), info_number_(-1), children_(16) {}

  // Construct a node representing a written instruction.
  TrieNode(DeoptInstr* instruction, intptr_t info_number)
      : instruction_(instruction), info_number_(info_number), children_(4) {}

  intptr_t info_number() const { return info_number_; }

  void AddChild(TrieNode* child) {
    if (child != nullptr) children_.Add(child);
  }

  TrieNode* FindChild(const DeoptInstr& instruction) {
    for (intptr_t i = 0; i < children_.length(); ++i) {
      TrieNode* child = children_[i];
      if (child->instruction_->Equals(instruction)) return child;
    }
    return nullptr;
  }

 private:
  const DeoptInstr* instruction_;  // Instruction that was written.
  const intptr_t info_number_;     // Index of the deopt info it was written to.

  GrowableArray<TrieNode*> children_;
};

DeoptInfoBuilder::DeoptInfoBuilder(Zone* zone,
                                   const intptr_t num_args,
                                   compiler::Assembler* assembler)
    : zone_(zone),
      instructions_(),
      num_args_(num_args),
      assembler_(assembler),
      trie_root_(new (zone) TrieNode()),
      current_info_number_(0),
      frame_start_(-1),
      materializations_() {}

intptr_t DeoptInfoBuilder::FindOrAddObjectInTable(const Object& obj) const {
  return assembler_->object_pool_builder().FindObject(obj);
}

intptr_t DeoptInfoBuilder::CalculateStackIndex(
    const Location& source_loc) const {
  intptr_t index = -compiler::target::frame_layout.VariableIndexForFrameSlot(
      source_loc.stack_index());
  return index < 0 ? index + num_args_
                   : index + num_args_ + kDartFrameFixedSize;
}

CpuRegisterSource DeoptInfoBuilder::ToCpuRegisterSource(const Location& loc) {
  if (loc.IsRegister()) {
    return CpuRegisterSource(CpuRegisterSource::kRegister, loc.reg());
  } else {
    ASSERT(loc.IsStackSlot());
    return CpuRegisterSource(CpuRegisterSource::kStackSlot,
                             CalculateStackIndex(loc));
  }
}

FpuRegisterSource DeoptInfoBuilder::ToFpuRegisterSource(
    const Location& loc,
    Location::Kind stack_slot_kind) {
  if (loc.IsFpuRegister()) {
    return FpuRegisterSource(FpuRegisterSource::kRegister, loc.fpu_reg());
  } else {
    ASSERT((stack_slot_kind == Location::kQuadStackSlot) ||
           (stack_slot_kind == Location::kDoubleStackSlot));
    ASSERT(loc.kind() == stack_slot_kind);
    return FpuRegisterSource(FpuRegisterSource::kStackSlot,
                             CalculateStackIndex(loc));
  }
}

void DeoptInfoBuilder::AddReturnAddress(const Function& function,
                                        intptr_t deopt_id,
                                        intptr_t dest_index) {
  const intptr_t object_table_index = FindOrAddObjectInTable(function);
  ASSERT(dest_index == FrameSize());
  instructions_.Add(new (zone())
                        DeoptRetAddressInstr(object_table_index, deopt_id));
}

void DeoptInfoBuilder::AddPcMarker(const Function& function,
                                   intptr_t dest_index) {
  intptr_t object_table_index = FindOrAddObjectInTable(function);
  ASSERT(dest_index == FrameSize());
  instructions_.Add(new (zone()) DeoptPcMarkerInstr(object_table_index));
}

void DeoptInfoBuilder::AddPp(const Function& function, intptr_t dest_index) {
  intptr_t object_table_index = FindOrAddObjectInTable(function);
  ASSERT(dest_index == FrameSize());
  instructions_.Add(new (zone()) DeoptPpInstr(object_table_index));
}

void DeoptInfoBuilder::AddCopy(Value* value,
                               const Location& source_loc,
                               const intptr_t dest_index) {
  DeoptInstr* deopt_instr = nullptr;
  if (source_loc.IsConstant()) {
    intptr_t object_table_index = FindOrAddObjectInTable(source_loc.constant());
    deopt_instr = new (zone()) DeoptConstantInstr(object_table_index);
  } else if (source_loc.IsInvalid() &&
             value->definition()->IsMaterializeObject()) {
    const intptr_t index =
        FindMaterialization(value->definition()->AsMaterializeObject());
    ASSERT(index >= 0);
    deopt_instr = new (zone()) DeoptMaterializedObjectRefInstr(index);
  } else {
    ASSERT(!source_loc.IsInvalid());
    Representation rep = value->definition()->representation();
    switch (rep) {
      case kTagged:
        deopt_instr =
            new (zone()) DeoptWordInstr(ToCpuRegisterSource(source_loc));
        break;
#if defined(TARGET_ARCH_IS_64_BIT)
      case kUntagged:
#endif
      case kUnboxedInt64: {
        if (source_loc.IsPairLocation()) {
          PairLocation* pair = source_loc.AsPairLocation();
          deopt_instr =
              new (zone()) DeoptMintPairInstr(ToCpuRegisterSource(pair->At(0)),
                                              ToCpuRegisterSource(pair->At(1)));
        } else {
          ASSERT(!source_loc.IsPairLocation());
          deopt_instr =
              new (zone()) DeoptMintInstr(ToCpuRegisterSource(source_loc));
        }
        break;
      }
#if defined(TARGET_ARCH_IS_32_BIT)
      case kUntagged:
#endif
      case kUnboxedInt32:
        deopt_instr =
            new (zone()) DeoptInt32Instr(ToCpuRegisterSource(source_loc));
        break;
      case kUnboxedUint32:
        deopt_instr =
            new (zone()) DeoptUint32Instr(ToCpuRegisterSource(source_loc));
        break;
      case kUnboxedFloat:
        deopt_instr = new (zone()) DeoptFloatInstr(
            ToFpuRegisterSource(source_loc, Location::kDoubleStackSlot));
        break;
      case kUnboxedDouble:
        deopt_instr = new (zone()) DeoptDoubleInstr(
            ToFpuRegisterSource(source_loc, Location::kDoubleStackSlot));
        break;
      case kUnboxedFloat32x4:
        deopt_instr = new (zone()) DeoptFloat32x4Instr(
            ToFpuRegisterSource(source_loc, Location::kQuadStackSlot));
        break;
      case kUnboxedFloat64x2:
        deopt_instr = new (zone()) DeoptFloat64x2Instr(
            ToFpuRegisterSource(source_loc, Location::kQuadStackSlot));
        break;
      case kUnboxedInt32x4:
        deopt_instr = new (zone()) DeoptInt32x4Instr(
            ToFpuRegisterSource(source_loc, Location::kQuadStackSlot));
        break;
      default:
        UNREACHABLE();
        break;
    }
  }
  ASSERT(dest_index == FrameSize());
  ASSERT(deopt_instr != nullptr);
  instructions_.Add(deopt_instr);
}

void DeoptInfoBuilder::AddCallerFp(intptr_t dest_index) {
  ASSERT(dest_index == FrameSize());
  instructions_.Add(new (zone()) DeoptCallerFpInstr());
}

void DeoptInfoBuilder::AddCallerPp(intptr_t dest_index) {
  ASSERT(dest_index == FrameSize());
  instructions_.Add(new (zone()) DeoptCallerPpInstr());
}

void DeoptInfoBuilder::AddCallerPc(intptr_t dest_index) {
  ASSERT(dest_index == FrameSize());
  instructions_.Add(new (zone()) DeoptCallerPcInstr());
}

void DeoptInfoBuilder::AddConstant(const Object& obj, intptr_t dest_index) {
  ASSERT(dest_index == FrameSize());
  intptr_t object_table_index = FindOrAddObjectInTable(obj);
  instructions_.Add(new (zone()) DeoptConstantInstr(object_table_index));
}

void DeoptInfoBuilder::AddMaterialization(MaterializeObjectInstr* mat) {
  const intptr_t index = FindMaterialization(mat);
  if (index >= 0) {
    return;  // Already added.
  }
  materializations_.Add(mat);

  // Count initialized fields and emit kMaterializeObject instruction.
  // There is no need to write nulls into fields because object is null
  // initialized by default.
  intptr_t non_null_fields = 0;
  for (intptr_t i = 0; i < mat->InputCount(); i++) {
    if (!mat->InputAt(i)->BindsToConstantNull()) {
      non_null_fields++;
    }
  }

  instructions_.Add(new (zone()) DeoptMaterializeObjectInstr(non_null_fields));

  for (intptr_t i = 0; i < mat->InputCount(); i++) {
    MaterializeObjectInstr* nested_mat =
        mat->InputAt(i)->definition()->AsMaterializeObject();
    if (nested_mat != nullptr) {
      AddMaterialization(nested_mat);
    }
  }
}

intptr_t DeoptInfoBuilder::EmitMaterializationArguments(intptr_t dest_index) {
  ASSERT(dest_index == kDartFrameFixedSize);
  for (intptr_t i = 0; i < materializations_.length(); i++) {
    MaterializeObjectInstr* mat = materializations_[i];
    // Class of the instance to allocate.
    AddConstant(mat->cls(), dest_index++);
    AddConstant(Smi::ZoneHandle(Smi::New(mat->length_or_shape())),
                dest_index++);
    for (intptr_t i = 0; i < mat->InputCount(); i++) {
      if (!mat->InputAt(i)->BindsToConstantNull()) {
        // Emit offset-value pair.
        AddConstant(Smi::ZoneHandle(Smi::New(mat->FieldOffsetAt(i))),
                    dest_index++);
        AddCopy(mat->InputAt(i), mat->LocationAt(i), dest_index++);
      }
    }
  }
  return dest_index;
}

intptr_t DeoptInfoBuilder::FindMaterialization(
    MaterializeObjectInstr* mat) const {
  for (intptr_t i = 0; i < materializations_.length(); i++) {
    if (materializations_[i] == mat) {
      return i;
    }
  }
  return -1;
}

TypedDataPtr DeoptInfoBuilder::CreateDeoptInfo(const Array& deopt_table) {
  intptr_t length = instructions_.length();

  // Count the number of instructions that are a shared suffix of some deopt
  // info already written.
  TrieNode* suffix = trie_root_;
  intptr_t suffix_length = 0;
  if (FLAG_compress_deopt_info) {
    for (intptr_t i = length - 1; i >= 0; --i) {
      TrieNode* node = suffix->FindChild(*instructions_[i]);
      if (node == nullptr) break;
      suffix = node;
      ++suffix_length;
    }
  }

  // Allocate space for the translation.  If the shared suffix is longer
  // than one instruction, we replace it with a single suffix instruction.
  const bool use_suffix = suffix_length > 1;
  if (use_suffix) {
    length -= (suffix_length - 1);
  }

  typedef ZoneWriteStream::Raw<sizeof(intptr_t), intptr_t> Writer;
  ZoneWriteStream stream(zone(), 2 * length * kWordSize);

  Writer::Write(&stream, FrameSize());

  if (use_suffix) {
    Writer::Write(&stream, suffix_length);
    Writer::Write(&stream, suffix->info_number());
  } else {
    Writer::Write(&stream, 0);
  }

  // Write the unshared instructions and build their sub-tree.
  TrieNode* node = use_suffix ? suffix : trie_root_;
  const intptr_t write_count = use_suffix ? length - 1 : length;
  for (intptr_t i = write_count - 1; i >= 0; --i) {
    DeoptInstr* instr = instructions_[i];
    Writer::Write(&stream, instr->kind());
    Writer::Write(&stream, instr->source_index());

    TrieNode* child = new (zone()) TrieNode(instr, current_info_number_);
    node->AddChild(child);
    node = child;
  }

  const TypedData& deopt_info = TypedData::Handle(
      zone(), TypedData::New(kTypedDataUint8ArrayCid, stream.bytes_written(),
                             Heap::kOld));
  {
    NoSafepointScope no_safepoint;
    memmove(deopt_info.DataAddr(0), stream.buffer(), stream.bytes_written());
  }

  ASSERT(
      DeoptInfo::VerifyDecompression(instructions_, deopt_table, deopt_info));
  instructions_.Clear();
  materializations_.Clear();
  frame_start_ = -1;

  ++current_info_number_;
  return deopt_info.ptr();
}

intptr_t DeoptTable::SizeFor(intptr_t length) {
  return length * kEntrySize;
}

void DeoptTable::SetEntry(const Array& table,
                          intptr_t index,
                          const Smi& offset,
                          const TypedData& info,
                          const Smi& reason) {
  ASSERT((table.Length() % kEntrySize) == 0);
  intptr_t i = index * kEntrySize;
  table.SetAt(i, offset);
  table.SetAt(i + 1, info);
  table.SetAt(i + 2, reason);
}

intptr_t DeoptTable::GetLength(const Array& table) {
  ASSERT((table.Length() % kEntrySize) == 0);
  return table.Length() / kEntrySize;
}

void DeoptTable::GetEntry(const Array& table,
                          intptr_t index,
                          Smi* offset,
                          TypedData* info,
                          Smi* reason) {
  intptr_t i = index * kEntrySize;
  *offset ^= table.At(i);
  *info ^= table.At(i + 1);
  *reason ^= table.At(i + 2);
}

intptr_t DeoptInfo::FrameSize(const TypedData& packed) {
  NoSafepointScope no_safepoint;
  typedef ReadStream::Raw<sizeof(intptr_t), intptr_t> Reader;
  ReadStream read_stream(reinterpret_cast<uint8_t*>(packed.DataAddr(0)),
                         packed.LengthInBytes());
  return Reader::Read(&read_stream);
}

intptr_t DeoptInfo::NumMaterializations(
    const GrowableArray<DeoptInstr*>& unpacked) {
  intptr_t num = 0;
  while (unpacked[num]->kind() == DeoptInstr::kMaterializeObject) {
    num++;
  }
  return num;
}

void DeoptInfo::UnpackInto(const Array& table,
                           const TypedData& packed,
                           GrowableArray<DeoptInstr*>* unpacked,
                           intptr_t length) {
  NoSafepointScope no_safepoint;
  typedef ReadStream::Raw<sizeof(intptr_t), intptr_t> Reader;
  ReadStream read_stream(reinterpret_cast<uint8_t*>(packed.DataAddr(0)),
                         packed.LengthInBytes());
  const intptr_t frame_size = Reader::Read(&read_stream);  // Skip frame size.
  USE(frame_size);

  const intptr_t suffix_length = Reader::Read(&read_stream);
  if (suffix_length != 0) {
    ASSERT(suffix_length > 1);
    const intptr_t info_number = Reader::Read(&read_stream);

    TypedData& suffix = TypedData::Handle();
    Smi& offset = Smi::Handle();
    Smi& reason_and_flags = Smi::Handle();
    DeoptTable::GetEntry(table, info_number, &offset, &suffix,
                         &reason_and_flags);
    UnpackInto(table, suffix, unpacked, suffix_length);
  }

  while ((read_stream.PendingBytes() > 0) && (unpacked->length() < length)) {
    const intptr_t instruction = Reader::Read(&read_stream);
    const intptr_t from_index = Reader::Read(&read_stream);
    unpacked->Add(DeoptInstr::Create(instruction, from_index));
  }
}

void DeoptInfo::Unpack(const Array& table,
                       const TypedData& packed,
                       GrowableArray<DeoptInstr*>* unpacked) {
  ASSERT(unpacked->is_empty());

  // Pass kMaxInt32 as the length to unpack all instructions from the
  // packed stream.
  UnpackInto(table, packed, unpacked, kMaxInt32);

  unpacked->Reverse();
}

const char* DeoptInfo::ToCString(const Array& deopt_table,
                                 const TypedData& packed) {
#define FORMAT "[%s]"
  GrowableArray<DeoptInstr*> deopt_instrs;
  Unpack(deopt_table, packed, &deopt_instrs);

  // Compute the buffer size required.
  intptr_t len = 1;  // Trailing '\0'.
  for (intptr_t i = 0; i < deopt_instrs.length(); i++) {
    len += Utils::SNPrint(nullptr, 0, FORMAT, deopt_instrs[i]->ToCString());
  }

  // Allocate the buffer.
  char* buffer = Thread::Current()->zone()->Alloc<char>(len);

  // Layout the fields in the buffer.
  intptr_t index = 0;
  for (intptr_t i = 0; i < deopt_instrs.length(); i++) {
    index += Utils::SNPrint((buffer + index), (len - index), FORMAT,
                            deopt_instrs[i]->ToCString());
  }

  return buffer;
#undef FORMAT
}

// Returns a bool so it can be asserted.
bool DeoptInfo::VerifyDecompression(const GrowableArray<DeoptInstr*>& original,
                                    const Array& deopt_table,
                                    const TypedData& packed) {
  GrowableArray<DeoptInstr*> unpacked;
  Unpack(deopt_table, packed, &unpacked);
  ASSERT(unpacked.length() == original.length());
  for (intptr_t i = 0; i < unpacked.length(); ++i) {
    ASSERT(unpacked[i]->Equals(*original[i]));
  }
  return true;
}

}  // namespace dart

#endif  // !defined(DART_PRECOMPILED_RUNTIME)
