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

#ifndef RUNTIME_VM_COMPILER_BACKEND_FLOW_GRAPH_COMPILER_H_
#define RUNTIME_VM_COMPILER_BACKEND_FLOW_GRAPH_COMPILER_H_

#include "vm/compiler/runtime_api.h"
#if defined(DART_PRECOMPILED_RUNTIME)
#error "AOT runtime should not use compiler sources (including header files)"
#endif  // defined(DART_PRECOMPILED_RUNTIME)

#include <functional>

#include "vm/allocation.h"
#include "vm/code_descriptors.h"
#include "vm/compiler/assembler/assembler.h"
#include "vm/compiler/assembler/object_pool_builder.h"
#include "vm/compiler/backend/code_statistics.h"
#include "vm/compiler/backend/il.h"
#include "vm/compiler/backend/locations.h"
#include "vm/runtime_entry.h"

namespace dart {

// Forward declarations.
class CatchEntryMovesMapBuilder;
class Code;
class DeoptInfoBuilder;
class FlowGraph;
class FlowGraphCompiler;
class Function;
template <typename T>
class GrowableArray;
class ParsedFunction;

namespace compiler {
struct TableSelector;
}

// Used in methods which need conditional access to a temporary register.
// May only be used to allocate a single temporary register.
class TemporaryRegisterAllocator : public ValueObject {
 public:
  virtual ~TemporaryRegisterAllocator() {}
  virtual Register AllocateTemporary() = 0;
  virtual void ReleaseTemporary() = 0;
};

class ConstantTemporaryAllocator : public TemporaryRegisterAllocator {
 public:
  explicit ConstantTemporaryAllocator(Register tmp) : tmp_(tmp) {}

  Register AllocateTemporary() override { return tmp_; }
  void ReleaseTemporary() override {}

 private:
  Register const tmp_;
};

class NoTemporaryAllocator : public TemporaryRegisterAllocator {
 public:
  Register AllocateTemporary() override { UNREACHABLE(); }
  void ReleaseTemporary() override { UNREACHABLE(); }
};

// Used for describing a deoptimization point after call (lazy deoptimization).
// For deoptimization before instruction use class CompilerDeoptInfoWithStub.
class CompilerDeoptInfo : public ZoneObject {
 public:
  CompilerDeoptInfo(intptr_t deopt_id,
                    ICData::DeoptReasonId reason,
                    uint32_t flags,
                    Environment* deopt_env)
      : pc_offset_(-1),
        deopt_id_(deopt_id),
        reason_(reason),
        flags_(flags),
        deopt_env_(deopt_env) {
    ASSERT(deopt_env != nullptr);
  }
  virtual ~CompilerDeoptInfo() {}

  TypedDataPtr CreateDeoptInfo(FlowGraphCompiler* compiler,
                               DeoptInfoBuilder* builder,
                               const Array& deopt_table);

  // No code needs to be generated.
  virtual void GenerateCode(FlowGraphCompiler* compiler, intptr_t stub_ix) {}

  intptr_t pc_offset() const { return pc_offset_; }
  void set_pc_offset(intptr_t offset) { pc_offset_ = offset; }

  intptr_t deopt_id() const { return deopt_id_; }
  ICData::DeoptReasonId reason() const { return reason_; }
  uint32_t flags() const { return flags_; }
  const Environment* deopt_env() const { return deopt_env_; }

 private:
  void EmitMaterializations(Environment* env, DeoptInfoBuilder* builder);

  void AllocateOutgoingArguments(Environment* env);

  intptr_t pc_offset_;
  const intptr_t deopt_id_;
  const ICData::DeoptReasonId reason_;
  const uint32_t flags_;
  Environment* deopt_env_;

  DISALLOW_COPY_AND_ASSIGN(CompilerDeoptInfo);
};

class CompilerDeoptInfoWithStub : public CompilerDeoptInfo {
 public:
  CompilerDeoptInfoWithStub(intptr_t deopt_id,
                            ICData::DeoptReasonId reason,
                            uint32_t flags,
                            Environment* deopt_env)
      : CompilerDeoptInfo(deopt_id, reason, flags, deopt_env), entry_label_() {
    ASSERT(reason != ICData::kDeoptAtCall);
  }

  compiler::Label* entry_label() { return &entry_label_; }

  // Implementation is in architecture specific file.
  virtual void GenerateCode(FlowGraphCompiler* compiler, intptr_t stub_ix);

  const char* Name() const {
    const char* kFormat = "Deopt stub for id %d, reason: %s";
    const intptr_t len = Utils::SNPrint(nullptr, 0, kFormat, deopt_id(),
                                        DeoptReasonToCString(reason())) +
                         1;
    char* chars = Thread::Current()->zone()->Alloc<char>(len);
    Utils::SNPrint(chars, len, kFormat, deopt_id(),
                   DeoptReasonToCString(reason()));
    return chars;
  }

 private:
  compiler::Label entry_label_;

  DISALLOW_COPY_AND_ASSIGN(CompilerDeoptInfoWithStub);
};

class SlowPathCode : public ZoneObject {
 public:
  explicit SlowPathCode(Instruction* instruction)
      : instruction_(instruction), entry_label_(), exit_label_() {}
  virtual ~SlowPathCode() {}

  Instruction* instruction() const { return instruction_; }
  compiler::Label* entry_label() { return &entry_label_; }
  compiler::Label* exit_label() { return &exit_label_; }

  void GenerateCode(FlowGraphCompiler* compiler) {
    EmitNativeCode(compiler);
    ASSERT(entry_label_.IsBound());
  }

 private:
  virtual void EmitNativeCode(FlowGraphCompiler* compiler) = 0;

  Instruction* instruction_;
  compiler::Label entry_label_;
  compiler::Label exit_label_;

  DISALLOW_COPY_AND_ASSIGN(SlowPathCode);
};

template <typename T>
class TemplateSlowPathCode : public SlowPathCode {
 public:
  explicit TemplateSlowPathCode(T* instruction) : SlowPathCode(instruction) {}

  T* instruction() const {
    return static_cast<T*>(SlowPathCode::instruction());
  }
};

class BoxAllocationSlowPath : public TemplateSlowPathCode<Instruction> {
 public:
  BoxAllocationSlowPath(Instruction* instruction,
                        const Class& cls,
                        Register result)
      : TemplateSlowPathCode(instruction), cls_(cls), result_(result) {}

  virtual void EmitNativeCode(FlowGraphCompiler* compiler);

  static void Allocate(FlowGraphCompiler* compiler,
                       Instruction* instruction,
                       const Class& cls,
                       Register result,
                       Register temp);

 private:
  const Class& cls_;
  const Register result_;
};

class DoubleToIntegerSlowPath
    : public TemplateSlowPathCode<DoubleToIntegerInstr> {
 public:
  DoubleToIntegerSlowPath(DoubleToIntegerInstr* instruction,
                          FpuRegister value_reg)
      : TemplateSlowPathCode(instruction), value_reg_(value_reg) {}

  virtual void EmitNativeCode(FlowGraphCompiler* compiler);

 private:
  FpuRegister value_reg_;
};

// Slow path code which calls runtime entry to throw an exception.
class ThrowErrorSlowPathCode : public TemplateSlowPathCode<Instruction> {
 public:
  ThrowErrorSlowPathCode(Instruction* instruction,
                         const RuntimeEntry& runtime_entry)
      : TemplateSlowPathCode(instruction), runtime_entry_(runtime_entry) {}

  // This name appears in disassembly.
  virtual const char* name() = 0;

  // Subclasses can override these methods to customize slow path code.
  virtual void EmitCodeAtSlowPathEntry(FlowGraphCompiler* compiler) {}
  virtual void AddMetadataForRuntimeCall(FlowGraphCompiler* compiler) {}
  virtual void PushArgumentsForRuntimeCall(FlowGraphCompiler* compiler) {}

  // Returns number of arguments for runtime call (if shared stub is not used).
  virtual intptr_t GetNumberOfArgumentsForRuntimeCall() { return 0; }

  virtual void EmitSharedStubCall(FlowGraphCompiler* compiler,
                                  bool save_fpu_registers) {
    UNREACHABLE();
  }

  virtual void EmitNativeCode(FlowGraphCompiler* compiler);

 private:
  const RuntimeEntry& runtime_entry_;
};

class NullErrorSlowPath : public ThrowErrorSlowPathCode {
 public:
  explicit NullErrorSlowPath(CheckNullInstr* instruction)
      : ThrowErrorSlowPathCode(instruction,
                               GetRuntimeEntry(instruction->exception_type())) {
  }

  CheckNullInstr::ExceptionType exception_type() const {
    return instruction()->AsCheckNull()->exception_type();
  }

  const char* name() override;

  void EmitSharedStubCall(FlowGraphCompiler* compiler,
                          bool save_fpu_registers) override;

  void AddMetadataForRuntimeCall(FlowGraphCompiler* compiler) override {
    CheckNullInstr::AddMetadataForRuntimeCall(instruction()->AsCheckNull(),
                                              compiler);
  }

  static CodePtr GetStub(FlowGraphCompiler* compiler,
                         CheckNullInstr::ExceptionType exception_type,
                         bool save_fpu_registers);

 private:
  static const RuntimeEntry& GetRuntimeEntry(
      CheckNullInstr::ExceptionType exception_type);
};

class RangeErrorSlowPath : public ThrowErrorSlowPathCode {
 public:
  explicit RangeErrorSlowPath(GenericCheckBoundInstr* instruction)
      : ThrowErrorSlowPathCode(
            instruction,
            GenericCheckBoundInstr::UseUnboxedRepresentation()
                ? kRangeErrorUnboxedInt64RuntimeEntry
                : kRangeErrorRuntimeEntry) {}
  virtual const char* name() { return "check bound"; }

  virtual intptr_t GetNumberOfArgumentsForRuntimeCall() {
    if (GenericCheckBoundInstr::UseUnboxedRepresentation()) {
      return 0;  // Unboxed arguments are passed through Thread.
    }
    return 2;  // length and index
  }

  virtual void PushArgumentsForRuntimeCall(FlowGraphCompiler* compiler);

  virtual void EmitSharedStubCall(FlowGraphCompiler* compiler,
                                  bool save_fpu_registers);
};

class WriteErrorSlowPath : public ThrowErrorSlowPathCode {
 public:
  explicit WriteErrorSlowPath(CheckWritableInstr* instruction)
      : ThrowErrorSlowPathCode(instruction, kWriteErrorRuntimeEntry) {}
  virtual const char* name() { return "check writable"; }

  virtual void EmitSharedStubCall(FlowGraphCompiler* compiler,
                                  bool save_fpu_registers);

  virtual void PushArgumentsForRuntimeCall(FlowGraphCompiler* compiler);

  virtual intptr_t GetNumberOfArgumentsForRuntimeCall() {
    return 2;  // receiver, kind
  }
};

class LateInitializationErrorSlowPath : public ThrowErrorSlowPathCode {
 public:
  explicit LateInitializationErrorSlowPath(Instruction* instruction)
      : ThrowErrorSlowPathCode(instruction,
                               kLateFieldNotInitializedErrorRuntimeEntry) {
    ASSERT(instruction->IsLoadField() || instruction->IsLoadStaticField());
  }
  virtual const char* name() { return "late initialization error"; }

  virtual intptr_t GetNumberOfArgumentsForRuntimeCall() {
    return 1;  // field
  }

  virtual void PushArgumentsForRuntimeCall(FlowGraphCompiler* compiler);

  virtual void EmitSharedStubCall(FlowGraphCompiler* compiler,
                                  bool save_fpu_registers);

 private:
  FieldPtr OriginalField() const {
    return instruction()->IsLoadField()
               ? instruction()->AsLoadField()->slot().field().Original()
               : instruction()->AsLoadStaticField()->field().Original();
  }
};

class FieldAccessErrorSlowPath : public ThrowErrorSlowPathCode {
 public:
  explicit FieldAccessErrorSlowPath(Instruction* instruction)
      : ThrowErrorSlowPathCode(
            instruction,
            kStaticFieldAccessedWithoutIsolateErrorRuntimeEntry) {
    ASSERT(instruction->IsStoreStaticField());
  }
  virtual const char* name() { return "field access error"; }

  virtual intptr_t GetNumberOfArgumentsForRuntimeCall() {
    return 1;  // field
  }

  virtual void PushArgumentsForRuntimeCall(FlowGraphCompiler* compiler);

 private:
  FieldPtr OriginalField() const {
    return instruction()->AsStoreStaticField()->field().Original();
  }
};

class CheckedStoreIntoSharedSlowPath
    : public TemplateSlowPathCode<StoreStaticFieldInstr> {
 public:
  CheckedStoreIntoSharedSlowPath(StoreStaticFieldInstr* instruction,
                                 Register value)
      : TemplateSlowPathCode(instruction), value_(value) {}

  virtual void EmitNativeCode(FlowGraphCompiler* compiler);

 private:
  Register value_;

  FieldPtr OriginalField() const {
    return instruction()->AsStoreStaticField()->field().Original();
  }

  Register value() const { return value_; }
};

class EnsureDeeplyImmutableSlowPath : public TemplateSlowPathCode<Instruction> {
 public:
  EnsureDeeplyImmutableSlowPath(CheckFieldImmutabilityInstr* instruction,
                                Register value)
      : TemplateSlowPathCode(instruction), value_(value) {}

  virtual void EmitNativeCode(FlowGraphCompiler* compiler);

 private:
  Register value_;

  Register value() const { return value_; }
};

enum class TypeTestOutcome { kConclusive, kNotConclusive };

class FlowGraphCompiler : public ValueObject {
 private:
  class BlockInfo : public ZoneObject {
   public:
    BlockInfo()
        : block_label_(),
          jump_label_(&block_label_),
          next_nonempty_label_(nullptr),
          is_marked_(false) {}

    // The label to jump to when control is transferred to this block.  For
    // nonempty blocks it is the label of the block itself.  For empty
    // blocks it is the label of the first nonempty successor block.
    compiler::Label* jump_label() const { return jump_label_; }
    void set_jump_label(compiler::Label* label) { jump_label_ = label; }

    // The label of the first nonempty block after this one in the block
    // order, or nullptr if there is no nonempty block following this one.
    compiler::Label* next_nonempty_label() const {
      return next_nonempty_label_;
    }
    void set_next_nonempty_label(compiler::Label* label) {
      next_nonempty_label_ = label;
    }

    bool WasCompacted() const { return jump_label_ != &block_label_; }

    // Block compaction is recursive.  Block info for already-compacted
    // blocks is marked so as to avoid cycles in the graph.
    bool is_marked() const { return is_marked_; }
    void mark() { is_marked_ = true; }

   private:
    compiler::Label block_label_;

    compiler::Label* jump_label_;
    compiler::Label* next_nonempty_label_;

    bool is_marked_;
  };

 public:
  FlowGraphCompiler(compiler::Assembler* assembler,
                    FlowGraph* flow_graph,
                    const ParsedFunction& parsed_function,
                    bool is_optimizing,
                    ZoneGrowableArray<const ICData*>* deopt_id_to_ic_data,
                    CodeStatistics* stats = nullptr);

  void ArchSpecificInitialization();

  ~FlowGraphCompiler();

  static bool SupportsUnboxedSimd128();
  static bool CanConvertInt64ToDouble();

  // Accessors.
  compiler::Assembler* assembler() const { return assembler_; }
  const ParsedFunction& parsed_function() const { return parsed_function_; }
  const Function& function() const { return parsed_function_.function(); }
  const GrowableArray<BlockEntryInstr*>& block_order() const {
    return block_order_;
  }
  const GrowableArray<const compiler::TableSelector*>&
  dispatch_table_call_targets() const {
    return dispatch_table_call_targets_;
  }

  // If 'ForcedOptimization()' returns 'true', we are compiling in optimized
  // mode for a function which cannot deoptimize. Certain optimizations, e.g.
  // speculative optimizations and call patching are disabled.
  bool ForcedOptimization() const { return function().ForceOptimize(); }

  const FlowGraph& flow_graph() const {
    return intrinsic_mode() ? *intrinsic_flow_graph_ : flow_graph_;
  }

  BlockEntryInstr* current_block() const { return current_block_; }
  void set_current_block(BlockEntryInstr* value) { current_block_ = value; }

  Instruction* current_instruction() const { return current_instruction_; }

  bool CanOptimize() const;
  bool CanOptimizeFunction() const;
  bool CanOSRFunction() const;
  bool is_optimizing() const { return is_optimizing_; }

  // The function was fully intrinsified, so the body is unreachable.
  //
  // We still need to compile the body in unoptimized mode because the
  // 'ICData's are added to the function's 'ic_data_array_' when instance
  // calls are compiled.
  bool skip_body_compilation() const {
    return fully_intrinsified_ && is_optimizing();
  }

  void EnterIntrinsicMode();
  void ExitIntrinsicMode();
  bool intrinsic_mode() const { return intrinsic_mode_; }

  void set_intrinsic_flow_graph(const FlowGraph& flow_graph) {
    intrinsic_flow_graph_ = &flow_graph;
  }

  void set_intrinsic_slow_path_label(compiler::Label* label) {
    ASSERT(intrinsic_slow_path_label_ == nullptr || label == nullptr);
    intrinsic_slow_path_label_ = label;
  }
  compiler::Label* intrinsic_slow_path_label() const {
    ASSERT(intrinsic_slow_path_label_ != nullptr);
    return intrinsic_slow_path_label_;
  }

  bool ForceSlowPathForStackOverflow() const;

  const GrowableArray<BlockInfo*>& block_info() const { return block_info_; }

  void StatsBegin(Instruction* instr) {
    if (stats_ != nullptr) stats_->Begin(instr);
  }

  void StatsEnd(Instruction* instr) {
    if (stats_ != nullptr) stats_->End(instr);
  }

  void SpecialStatsBegin(intptr_t tag) {
    if (stats_ != nullptr) stats_->SpecialBegin(tag);
  }

  void SpecialStatsEnd(intptr_t tag) {
    if (stats_ != nullptr) stats_->SpecialEnd(tag);
  }

  GrowableArray<const Field*>& used_static_fields() {
    return used_static_fields_;
  }

  // Constructor is lightweight, major initialization work should occur here.
  // This makes it easier to measure time spent in the compiler.
  void InitCompiler();

  void CompileGraph();

  void EmitPrologue();

  void VisitBlocks();

  void EmitFunctionEntrySourcePositionDescriptorIfNeeded();

  // Bail out of the flow graph compiler. Does not return to the caller.
  void Bailout(const char* reason);

  // Returns 'true' if regular code generation should be skipped.
  bool TryIntrinsify();

  // Emits code for a generic move from a location 'src' to a location 'dst'.
  //
  // Note that Location does not include a size (that can only be deduced from
  // a Representation), so these moves might overapproximate the size needed
  // to move. The maximal overapproximation is moving 8 bytes instead of 4 on
  // 64 bit architectures. This overapproximation is not a problem, because
  // the Dart calling convention only uses word-sized stack slots.
  //
  // TODO(dartbug.com/40400): Express this in terms of EmitMove(NativeLocation
  // NativeLocation) to remove code duplication.
  void EmitMove(Location dst, Location src, TemporaryRegisterAllocator* temp);

  // Emits code for a move from a location `src` to a location `dst`.
  //
  // Takes into account the payload and container representations of `dst` and
  // `src` to do the smallest move possible, and sign (or zero) extend or
  // truncate if needed.
  //
  // Makes use of TMP, FpuTMP, and `temp`.
  void EmitNativeMove(const compiler::ffi::NativeLocation& dst,
                      const compiler::ffi::NativeLocation& src,
                      TemporaryRegisterAllocator* temp);

  // Helper method to move from a Location to a NativeLocation.
  void EmitMoveToNative(const compiler::ffi::NativeLocation& dst,
                        Location src_loc,
                        Representation src_type,
                        TemporaryRegisterAllocator* temp);

  // Helper method to move from a NativeLocation to a Location.
  void EmitMoveFromNative(Location dst_loc,
                          Representation dst_type,
                          const compiler::ffi::NativeLocation& src,
                          TemporaryRegisterAllocator* temp);

  // Helper method to move a Dart const to a native location.
  void EmitMoveConst(const compiler::ffi::NativeLocation& dst,
                     Location src,
                     Representation src_type,
                     TemporaryRegisterAllocator* temp);

  bool CheckAssertAssignableTypeTestingABILocations(
      const LocationSummary& locs);

  void GenerateAssertAssignable(CompileType* receiver_type,
                                const InstructionSource& source,
                                intptr_t deopt_id,
                                Environment* env,
                                const String& dst_name,
                                LocationSummary* locs);

#if !defined(TARGET_ARCH_IA32)
  void GenerateCallerChecksForAssertAssignable(CompileType* receiver_type,
                                               const AbstractType& dst_type,
                                               compiler::Label* done);

  void GenerateTTSCall(const InstructionSource& source,
                       intptr_t deopt_id,
                       Environment* env,
                       Register reg_with_type,
                       const AbstractType& dst_type,
                       const String& dst_name,
                       LocationSummary* locs);

  static void GenerateIndirectTTSCall(compiler::Assembler* assembler,
                                      Register reg_with_type,
                                      intptr_t sub_type_cache_index);
#endif

  void GenerateStubCall(const InstructionSource& source,
                        const Code& stub,
                        UntaggedPcDescriptors::Kind kind,
                        LocationSummary* locs,
                        intptr_t deopt_id,
                        Environment* env);

  void GenerateNonLazyDeoptableStubCall(
      const InstructionSource& source,
      const Code& stub,
      UntaggedPcDescriptors::Kind kind,
      LocationSummary* locs,
      ObjectPool::SnapshotBehavior snapshot_behavior =
          compiler::ObjectPoolBuilderEntry::kSnapshotable);

  void GeneratePatchableCall(
      const InstructionSource& source,
      const Code& stub,
      UntaggedPcDescriptors::Kind kind,
      LocationSummary* locs,
      ObjectPool::SnapshotBehavior snapshot_behavior =
          compiler::ObjectPoolBuilderEntry::kSnapshotable);

  void GenerateDartCall(intptr_t deopt_id,
                        const InstructionSource& source,
                        const Code& stub,
                        UntaggedPcDescriptors::Kind kind,
                        LocationSummary* locs,
                        Code::EntryKind entry_kind = Code::EntryKind::kNormal);

  void GenerateStaticDartCall(
      intptr_t deopt_id,
      const InstructionSource& source,
      UntaggedPcDescriptors::Kind kind,
      LocationSummary* locs,
      const Function& target,
      Code::EntryKind entry_kind = Code::EntryKind::kNormal);

  void GenerateInstanceOf(const InstructionSource& source,
                          intptr_t deopt_id,
                          Environment* env,
                          const AbstractType& type,
                          LocationSummary* locs);

  void GenerateInstanceCall(intptr_t deopt_id,
                            const InstructionSource& source,
                            LocationSummary* locs,
                            const ICData& ic_data,
                            Code::EntryKind entry_kind,
                            bool receiver_can_be_smi);

  void GenerateStaticCall(
      intptr_t deopt_id,
      const InstructionSource& source,
      const Function& function,
      ArgumentsInfo args_info,
      LocationSummary* locs,
      const ICData& ic_data_in,
      ICData::RebindRule rebind_rule,
      Code::EntryKind entry_kind = Code::EntryKind::kNormal);

  void GenerateNumberTypeCheck(Register kClassIdReg,
                               const AbstractType& type,
                               compiler::Label* is_instance_lbl,
                               compiler::Label* is_not_instance_lbl);
  void GenerateStringTypeCheck(Register kClassIdReg,
                               compiler::Label* is_instance_lbl,
                               compiler::Label* is_not_instance_lbl);
  void GenerateListTypeCheck(Register kClassIdReg,
                             compiler::Label* is_instance_lbl);

  // We test up to 4 different cid ranges, if we would need to test more in
  // order to get a definite answer we fall back to the old mechanism (namely
  // of going into the subtyping cache)
  static constexpr intptr_t kMaxNumberOfCidRangesToTest = 4;

  // If [fall_through_if_inside] is `true`, then [outside_range_lbl] must be
  // supplied, since it will be jumped to in the last case if the cid is outside
  // the range.
  //
  // Returns whether [class_id_reg] is clobbered by the check.
  static bool GenerateCidRangesCheck(
      compiler::Assembler* assembler,
      Register class_id_reg,
      const CidRangeVector& cid_ranges,
      compiler::Label* inside_range_lbl,
      compiler::Label* outside_range_lbl = nullptr,
      bool fall_through_if_inside = false);

  void EmitOptimizedInstanceCall(
      const Code& stub,
      const ICData& ic_data,
      intptr_t deopt_id,
      const InstructionSource& source,
      LocationSummary* locs,
      Code::EntryKind entry_kind = Code::EntryKind::kNormal);

  void EmitInstanceCallJIT(const Code& stub,
                           const ICData& ic_data,
                           intptr_t deopt_id,
                           const InstructionSource& source,
                           LocationSummary* locs,
                           Code::EntryKind entry_kind);

  void EmitPolymorphicInstanceCall(const PolymorphicInstanceCallInstr* call,
                                   const CallTargets& targets,
                                   ArgumentsInfo args_info,
                                   intptr_t deopt_id,
                                   const InstructionSource& source,
                                   LocationSummary* locs,
                                   bool complete,
                                   intptr_t total_call_count,
                                   bool receiver_can_be_smi = true);

  void EmitMegamorphicInstanceCall(const ICData& icdata,
                                   intptr_t deopt_id,
                                   const InstructionSource& source,
                                   LocationSummary* locs) {
    const String& name = String::Handle(icdata.target_name());
    const Array& arguments_descriptor =
        Array::Handle(icdata.arguments_descriptor());
    EmitMegamorphicInstanceCall(name, arguments_descriptor, deopt_id, source,
                                locs);
  }

  void EmitMegamorphicInstanceCall(const String& function_name,
                                   const Array& arguments_descriptor,
                                   intptr_t deopt_id,
                                   const InstructionSource& source,
                                   LocationSummary* locs);

  void EmitInstanceCallAOT(
      const ICData& ic_data,
      intptr_t deopt_id,
      const InstructionSource& source,
      LocationSummary* locs,
      Code::EntryKind entry_kind = Code::EntryKind::kNormal,
      bool receiver_can_be_smi = true);

  void EmitTestAndCall(const CallTargets& targets,
                       const String& function_name,
                       ArgumentsInfo args_info,
                       compiler::Label* failed,
                       compiler::Label* match_found,
                       intptr_t deopt_id,
                       const InstructionSource& source_index,
                       LocationSummary* locs,
                       bool complete,
                       intptr_t total_ic_calls,
                       Code::EntryKind entry_kind = Code::EntryKind::kNormal);

  void EmitDispatchTableCall(int32_t selector_offset,
                             const Array& arguments_descriptor);

  Condition EmitEqualityRegConstCompare(Register reg,
                                        const Object& obj,
                                        bool needs_number_check,
                                        const InstructionSource& source,
                                        intptr_t deopt_id);
  Condition EmitEqualityRegRegCompare(Register left,
                                      Register right,
                                      bool needs_number_check,
                                      const InstructionSource& source,
                                      intptr_t deopt_id);
  Condition EmitBoolTest(Register value, BranchLabels labels, bool invert);

  bool NeedsEdgeCounter(BlockEntryInstr* block);

  void EmitEdgeCounter(intptr_t edge_id);

  void RecordCatchEntryMoves(Environment* env);

  void EmitCallToStub(const Code& stub,
                      ObjectPool::SnapshotBehavior snapshot_behavior =
                          compiler::ObjectPoolBuilderEntry::kSnapshotable);
  void EmitJumpToStub(const Code& stub);
  void EmitTailCallToStub(const Code& stub);

  void EmitDropArguments(intptr_t count);

  // Emits the following metadata for the current PC:
  //
  //   * Attaches current try index
  //   * Attaches stackmaps
  //   * Attaches catch entry moves (in AOT)
  //   * Deoptimization information (in JIT)
  //
  // If [env] is not `nullptr` it will be used instead of the
  // `pending_deoptimization_env`.
  void EmitCallsiteMetadata(const InstructionSource& source,
                            intptr_t deopt_id,
                            UntaggedPcDescriptors::Kind kind,
                            LocationSummary* locs,
                            Environment* env);

  void EmitYieldPositionMetadata(const InstructionSource& source,
                                 intptr_t yield_index);

  void EmitComment(Instruction* instr);

  // Returns stack size (number of variables on stack for unoptimized
  // code, or number of spill slots for optimized code).
  intptr_t StackSize() const;

  // Returns the number of extra stack slots used during an Osr entry
  // (values for all [ParameterInstr]s, representing local variables
  // and expression stack values, are already on the stack).
  intptr_t ExtraStackSlotsOnOsrEntry() const;

#if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64)
  // Changes the base register of this Location if this allows us to utilize
  // a better addressing mode. For RISC-V, this is the wider range of compressed
  // instructions available for SP-relative load compared to FP-relative loads.
  // Assumes `StackSize` accounts for everything at the point of use.
  Location RebaseIfImprovesAddressing(Location loc) const;
#endif  // defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64)

  // Returns assembler label associated with the given block entry.
  compiler::Label* GetJumpLabel(BlockEntryInstr* block_entry) const;
  bool WasCompacted(BlockEntryInstr* block_entry) const;

  // Returns the label of the fall-through of the current block.
  compiler::Label* NextNonEmptyLabel() const;

  // Returns true if there is a next block after the current one in
  // the block order and if it is the given block.
  bool CanFallThroughTo(BlockEntryInstr* block_entry) const;

  // Return true-, false- and fall-through label for a branch instruction.
  BranchLabels CreateBranchLabels(BranchInstr* branch) const;

  void AddExceptionHandler(CatchBlockEntryInstr* entry);
  void SetNeedsStackTrace(intptr_t try_index);
  void AddCurrentDescriptor(UntaggedPcDescriptors::Kind kind,
                            intptr_t deopt_id,
                            const InstructionSource& source);
  void AddDescriptor(
      UntaggedPcDescriptors::Kind kind,
      intptr_t pc_offset,
      intptr_t deopt_id,
      const InstructionSource& source,
      intptr_t try_index,
      intptr_t yield_index = UntaggedPcDescriptors::kInvalidYieldIndex);

  // Add NullCheck information for the current PC.
  void AddNullCheck(const InstructionSource& source, const String& name);

  void RecordSafepoint(LocationSummary* locs,
                       intptr_t slow_path_argument_count = 0);

  compiler::Label* AddDeoptStub(intptr_t deopt_id,
                                ICData::DeoptReasonId reason,
                                uint32_t flags = 0);

  CompilerDeoptInfo* AddDeoptIndexAtCall(intptr_t deopt_id, Environment* env);
  CompilerDeoptInfo* AddSlowPathDeoptInfo(intptr_t deopt_id, Environment* env);

  void AddSlowPathCode(SlowPathCode* slow_path);

  void FinalizeExceptionHandlers(const Code& code);
  void FinalizePcDescriptors(const Code& code);
  ArrayPtr CreateDeoptInfo(compiler::Assembler* assembler);
  void FinalizeStackMaps(const Code& code);
  void FinalizeVarDescriptors(const Code& code);
  void FinalizeCatchEntryMovesMap(const Code& code);
  void FinalizeStaticCallTargetsTable(const Code& code);
  void FinalizeCodeSourceMap(const Code& code);

  const Class& double_class() const { return double_class_; }
  const Class& mint_class() const { return mint_class_; }
  const Class& float32x4_class() const { return float32x4_class_; }
  const Class& float64x2_class() const { return float64x2_class_; }
  const Class& int32x4_class() const { return int32x4_class_; }

  const Class& BoxClassFor(Representation rep);

  void SaveLiveRegisters(LocationSummary* locs);
  void RestoreLiveRegisters(LocationSummary* locs);
#if defined(DEBUG)
  void ClobberDeadTempRegisters(LocationSummary* locs);
#endif

  // Returns a new environment based on [env] which accounts for the new
  // locations of values in the slow path call.
  Environment* SlowPathEnvironmentFor(Instruction* inst,
                                      intptr_t num_slow_path_args) {
    if (inst->env() == nullptr && is_optimizing()) {
      if (pending_deoptimization_env_ == nullptr) {
        return nullptr;
      }
      return SlowPathEnvironmentFor(pending_deoptimization_env_, inst->locs(),
                                    num_slow_path_args);
    }
    return SlowPathEnvironmentFor(inst->env(), inst->locs(),
                                  num_slow_path_args);
  }

  Environment* SlowPathEnvironmentFor(Environment* env,
                                      LocationSummary* locs,
                                      intptr_t num_slow_path_args);

  intptr_t CurrentTryIndex() const {
    if (current_block_ == nullptr) {
      return kInvalidTryIndex;
    }
    return current_block_->try_index();
  }

  bool may_reoptimize() const { return may_reoptimize_; }

  // Use in unoptimized compilation to preserve/reuse ICData.
  //
  // If [binary_smi_target] is non-null and we have to create the ICData, the
  // ICData will get an (kSmiCid, kSmiCid, binary_smi_target) entry.
  const ICData* GetOrAddInstanceCallICData(intptr_t deopt_id,
                                           const String& target_name,
                                           const Array& arguments_descriptor,
                                           intptr_t num_args_tested,
                                           const AbstractType& receiver_type,
                                           const Function& binary_smi_target);

  const ICData* GetOrAddStaticCallICData(intptr_t deopt_id,
                                         const Function& target,
                                         const Array& arguments_descriptor,
                                         intptr_t num_args_tested,
                                         ICData::RebindRule rebind_rule);

  static const CallTargets* ResolveCallTargetsForReceiverCid(
      intptr_t cid,
      const String& selector,
      const Array& args_desc_array);

  const ZoneGrowableArray<const ICData*>& deopt_id_to_ic_data() const {
    return *deopt_id_to_ic_data_;
  }

  Thread* thread() const { return thread_; }
  IsolateGroup* isolate_group() const { return thread_->isolate_group(); }
  Zone* zone() const { return zone_; }

  void AddStubCallTarget(const Code& code);
  void AddDispatchTableCallTarget(const compiler::TableSelector* selector);

  ArrayPtr edge_counters_array() const { return edge_counters_array_.ptr(); }

  ArrayPtr InliningIdToFunction() const;

  void BeginCodeSourceRange(const InstructionSource& source);
  void EndCodeSourceRange(const InstructionSource& source);

  static bool LookupMethodFor(int class_id,
                              const String& name,
                              const ArgumentsDescriptor& args_desc,
                              Function* fn_return,
                              bool* class_is_abstract_return = nullptr);

  // Returns new class-id bias.
  //
  // TODO(kustermann): We should move this code out of the [FlowGraphCompiler]!
  static int EmitTestAndCallCheckCid(compiler::Assembler* assembler,
                                     compiler::Label* label,
                                     Register class_id_reg,
                                     const CidRangeValue& range,
                                     int bias,
                                     bool jump_on_miss = true);

  bool IsEmptyBlock(BlockEntryInstr* block) const;

  void EmitOptimizedStaticCall(
      const Function& function,
      const Array& arguments_descriptor,
      intptr_t size_with_type_args,
      intptr_t deopt_id,
      const InstructionSource& source,
      LocationSummary* locs,
      Code::EntryKind entry_kind = Code::EntryKind::kNormal);

 private:
  friend class BoxInt64Instr;            // For AddPcRelativeCallStubTarget().
  friend class CheckNullInstr;           // For AddPcRelativeCallStubTarget().
  friend class NullErrorSlowPath;        // For AddPcRelativeCallStubTarget().
  friend class CheckStackOverflowInstr;  // For AddPcRelativeCallStubTarget().
  friend class StoreIndexedInstr;        // For AddPcRelativeCallStubTarget().
  friend class StoreFieldInstr;          // For AddPcRelativeCallStubTarget().
  friend class CheckStackOverflowSlowPath;  // For pending_deoptimization_env_.
  friend class GraphIntrinsicCodeGenScope;  // For optimizing_.

  // Architecture specific implementation of simple native moves.
  void EmitNativeMoveArchitecture(const compiler::ffi::NativeLocation& dst,
                                  const compiler::ffi::NativeLocation& src);
  void EmitNativeLoad(Register dst,
                      Register base,
                      intptr_t offset,
                      compiler::ffi::PrimitiveType type);

  void EmitFrameEntry();

  bool TryIntrinsifyHelper();
  void AddPcRelativeCallTarget(const Function& function,
                               Code::EntryKind entry_kind);
  void AddPcRelativeCallStubTarget(const Code& stub_code);
  void AddPcRelativeTailCallStubTarget(const Code& stub_code);
  void AddPcRelativeTTSCallTypeTarget(const AbstractType& type);
  void AddStaticCallTarget(const Function& function,
                           Code::EntryKind entry_kind);

  void GenerateDeferredCode();

  void EmitInstructionPrologue(Instruction* instr);
  void EmitInstructionEpilogue(Instruction* instr);

  // Emit code to load a Value into register 'dst'.
  void LoadValue(Register dst, Value* value);

  void EmitUnoptimizedStaticCall(
      intptr_t size_with_type_args,
      intptr_t deopt_id,
      const InstructionSource& source,
      LocationSummary* locs,
      const ICData& ic_data,
      Code::EntryKind entry_kind = Code::EntryKind::kNormal);

  // Helper for TestAndCall that calculates a good bias that
  // allows more compact instructions to be emitted.
  intptr_t ComputeGoodBiasForCidComparison(const CallTargets& sorted,
                                           intptr_t max_immediate);

  // More helpers for EmitTestAndCall.

  static Register EmitTestCidRegister();

  void EmitTestAndCallLoadReceiver(intptr_t count_without_type_args,
                                   const Array& arguments_descriptor);

  void EmitTestAndCallSmiBranch(compiler::Label* label, bool jump_if_smi);

  void EmitTestAndCallLoadCid(Register class_id_reg);

  // Type checking helper methods.
  void CheckClassIds(Register class_id_reg,
                     const GrowableArray<intptr_t>& class_ids,
                     compiler::Label* is_instance_lbl,
                     compiler::Label* is_not_instance_lbl);

  SubtypeTestCachePtr GenerateInlineInstanceof(
      const InstructionSource& source,
      const AbstractType& type,
      compiler::Label* is_instance_lbl,
      compiler::Label* is_not_instance_lbl);

  SubtypeTestCachePtr GenerateInstantiatedTypeWithArgumentsTest(
      const InstructionSource& source,
      const AbstractType& dst_type,
      compiler::Label* is_instance_lbl,
      compiler::Label* is_not_instance_lbl);

  TypeTestOutcome GenerateInstantiatedTypeNoArgumentsTest(
      const InstructionSource& source,
      const AbstractType& dst_type,
      compiler::Label* is_instance_lbl,
      compiler::Label* is_not_instance_lbl);

  SubtypeTestCachePtr GenerateUninstantiatedTypeTest(
      const InstructionSource& source,
      const AbstractType& dst_type,
      compiler::Label* is_instance_lbl,
      compiler::Label* is_not_instance_label);

  SubtypeTestCachePtr GenerateFunctionTypeTest(
      const InstructionSource& source,
      const AbstractType& dst_type,
      compiler::Label* is_instance_lbl,
      compiler::Label* is_not_instance_label);

  SubtypeTestCachePtr GenerateSubtype1TestCacheLookup(
      const InstructionSource& source,
      const Class& type_class,
      compiler::Label* is_instance_lbl,
      compiler::Label* is_not_instance_lbl);

  enum class TypeTestStubKind {
    // Just check the instance cid (no closures).
    kTestTypeOneArg = 1,
    // Also check the instance type arguments.
    kTestTypeTwoArgs = 2,
    // Also check the instantiator type arguments for the destination type.
    kTestTypeThreeArgs = 3,
    // Also check the function type arguments for the destination type.
    kTestTypeFourArgs = 4,
    // Also check the parent function and delayed type arguments for a closure.
    kTestTypeSixArgs = 6,
    // Also check the destination type, as it is not known at compile time.
    kTestTypeSevenArgs = 7,
  };

  static_assert(static_cast<intptr_t>(TypeTestStubKind::kTestTypeSevenArgs) ==
                    SubtypeTestCache::kMaxInputs,
                "Need to adjust kTestTypeMaxArgs");
  static constexpr TypeTestStubKind kTestTypeMaxArgs =
      TypeTestStubKind::kTestTypeSevenArgs;

  // Returns the number of used inputs for a given type test stub kind.
  intptr_t UsedInputsForTTSKind(TypeTestStubKind kind) {
    return static_cast<intptr_t>(kind);
  }

  // Returns type test stub kind for a type test against type parameter type.
  TypeTestStubKind GetTypeTestStubKindForTypeParameter(
      const TypeParameter& type_param);

  // Takes input from TypeTestABI registers (or stack on IA32), see
  // StubCodeCompiler::GenerateSubtypeNTestCacheStub for caller-save registers.
  SubtypeTestCachePtr GenerateCallSubtypeTestStub(
      TypeTestStubKind test_kind,
      compiler::Label* is_instance_lbl,
      compiler::Label* is_not_instance_lbl);

  void GenerateBoolToJump(Register bool_reg,
                          compiler::Label* is_true,
                          compiler::Label* is_false);

  // Perform a greedy local register allocation.  Consider all registers free.
  void AllocateRegistersLocally(Instruction* instr);

  // Map a block number in a forward iteration into the block number in the
  // corresponding reverse iteration.  Used to obtain an index into
  // block_order for reverse iterations.
  intptr_t reverse_index(intptr_t index) const {
    return block_order_.length() - index - 1;
  }

  void set_current_instruction(Instruction* current_instruction) {
    current_instruction_ = current_instruction;
  }

  void CompactBlock(BlockEntryInstr* block);
  void CompactBlocks();

  bool IsListClass(const Class& cls) const {
    return cls.ptr() == list_class_.ptr();
  }

  void EmitSourceLine(Instruction* instr);

  intptr_t GetOptimizationThreshold() const;

#if defined(DEBUG)
  void FrameStateUpdateWith(Instruction* instr);
  void FrameStatePush(Definition* defn);
  void FrameStatePop(intptr_t count);
  bool FrameStateIsSafeToCall();
  void FrameStateClear();
#endif

  // Returns true if instruction lookahead (window size one)
  // is amenable to a peephole optimization.
  bool IsPeephole(Instruction* instr) const;

#if defined(DEBUG)
  bool CanCallDart() const {
    return current_instruction_ == nullptr ||
           current_instruction_->CanCallDart();
  }
#else
  bool CanCallDart() const { return true; }
#endif

  bool CanPcRelativeCall(const Function& target) const;
  bool CanPcRelativeCall(const Code& target) const;
  bool CanPcRelativeCall(const AbstractType& target) const;

  // This struct contains either function or code, the other one being nullptr.
  class StaticCallsStruct : public ZoneObject {
   public:
    Code::CallKind call_kind;
    Code::CallEntryPoint entry_point;
    const intptr_t offset;
    const Function* function;      // Can be nullptr.
    const Code* code;              // Can be nullptr.
    const AbstractType* dst_type;  // Can be nullptr.
    StaticCallsStruct(Code::CallKind call_kind,
                      Code::CallEntryPoint entry_point,
                      intptr_t offset_arg,
                      const Function* function_arg,
                      const Code* code_arg,
                      const AbstractType* dst_type)
        : call_kind(call_kind),
          entry_point(entry_point),
          offset(offset_arg),
          function(function_arg),
          code(code_arg),
          dst_type(dst_type) {
      DEBUG_ASSERT(function == nullptr ||
                   function->IsNotTemporaryScopedHandle());
      DEBUG_ASSERT(code == nullptr || code->IsNotTemporaryScopedHandle());
      DEBUG_ASSERT(dst_type == nullptr ||
                   dst_type->IsNotTemporaryScopedHandle());
      ASSERT(code == nullptr || dst_type == nullptr);
    }

   private:
    DISALLOW_COPY_AND_ASSIGN(StaticCallsStruct);
  };

  Thread* thread_;
  Zone* zone_;
  compiler::Assembler* assembler_;
  const ParsedFunction& parsed_function_;
  const FlowGraph& flow_graph_;
  const FlowGraph* intrinsic_flow_graph_ = nullptr;
  const GrowableArray<BlockEntryInstr*>& block_order_;

#if defined(DEBUG)
  GrowableArray<Representation> frame_state_;
#endif

  // Compiler specific per-block state.  Indexed by postorder block number
  // for convenience.  This is not the block's index in the block order,
  // which is reverse postorder.
  BlockEntryInstr* current_block_;
  ExceptionHandlerList* exception_handlers_list_;
  DescriptorList* pc_descriptors_list_;
  CompressedStackMapsBuilder* compressed_stackmaps_builder_;
  CodeSourceMapBuilder* code_source_map_builder_;
  CatchEntryMovesMapBuilder* catch_entry_moves_maps_builder_;
  GrowableArray<BlockInfo*> block_info_;
  GrowableArray<CompilerDeoptInfo*> deopt_infos_;
  GrowableArray<SlowPathCode*> slow_path_code_;
  // Fields that were referenced by generated code.
  // This list is needed by precompiler to ensure they are retained.
  GrowableArray<const Field*> used_static_fields_;
  // Stores static call targets as well as stub targets.
  // TODO(srdjan): Evaluate if we should store allocation stub targets into a
  // separate table?
  GrowableArray<StaticCallsStruct*> static_calls_target_table_;
  // The table selectors of all dispatch table calls in the current function.
  GrowableArray<const compiler::TableSelector*> dispatch_table_call_targets_;
  GrowableArray<IndirectGotoInstr*> indirect_gotos_;
  bool is_optimizing_;
  // Set to true if optimized code has IC calls.
  bool may_reoptimize_;
  // True while emitting intrinsic code.
  bool intrinsic_mode_;
  compiler::Label* intrinsic_slow_path_label_ = nullptr;
  bool fully_intrinsified_ = false;
  CodeStatistics* stats_;

  // The definition whose value is supposed to be at the top of the
  // expression stack. Used by peephole optimization (window size one)
  // to eliminate redundant push/pop pairs.
  Definition* top_of_stack_ = nullptr;

  const Class& double_class_;
  const Class& mint_class_;
  const Class& float32x4_class_;
  const Class& float64x2_class_;
  const Class& int32x4_class_;
  const Class& list_class_;

  // Currently instructions generate deopt stubs internally by
  // calling AddDeoptStub.  To communicate deoptimization environment
  // that should be used when deoptimizing we store it in this variable.
  // In future AddDeoptStub should be moved out of the instruction template.
  Environment* pending_deoptimization_env_;

  ZoneGrowableArray<const ICData*>* deopt_id_to_ic_data_;
  Array& edge_counters_array_;

  // Instruction currently running EmitNativeCode().
  Instruction* current_instruction_ = nullptr;

  DISALLOW_COPY_AND_ASSIGN(FlowGraphCompiler);
};

}  // namespace dart

#endif  // RUNTIME_VM_COMPILER_BACKEND_FLOW_GRAPH_COMPILER_H_
