// 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.

#ifndef RUNTIME_VM_COMPILER_BACKEND_FLOW_GRAPH_H_
#define RUNTIME_VM_COMPILER_BACKEND_FLOW_GRAPH_H_

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

#include <utility>

#include "vm/bit_vector.h"
#include "vm/compiler/backend/dart_calling_conventions.h"
#include "vm/compiler/backend/il.h"
#include "vm/growable_array.h"
#include "vm/hash_map.h"
#include "vm/parser.h"
#include "vm/thread.h"

namespace dart {

class LoopHierarchy;
class VariableLivenessAnalysis;

namespace compiler {
class GraphIntrinsifier;
}

class BlockIterator : public ValueObject {
 public:
  explicit BlockIterator(const GrowableArray<BlockEntryInstr*>& block_order)
      : block_order_(block_order), current_(0) {}

  BlockIterator(const BlockIterator& other)
      : ValueObject(),
        block_order_(other.block_order_),
        current_(other.current_) {}

  void Advance() {
    ASSERT(!Done());
    current_++;
  }

  bool Done() const { return current_ >= block_order_.length(); }

  BlockEntryInstr* Current() const { return block_order_[current_]; }

 private:
  const GrowableArray<BlockEntryInstr*>& block_order_;
  intptr_t current_;
};

class ConstantAndRepresentation {
 public:
  ConstantAndRepresentation(const Object& constant, Representation rep)
      : constant_(constant),
        representation_(rep),
        hash_(ComputeHash(constant)) {}

  const Object& constant() const { return constant_; }
  Representation representation() const { return representation_; }
  uword hash() const { return hash_; }

 private:
  static inline uword ComputeHash(const Object& constant) {
    // Caveat: a null might be hiding inside a handle which overrides
    // CanonicalizeHash() and does not check for |null| before computing the
    // hash. Thus doing Instance::Cast(constant).CanonicalizeHash() and
    // Instance::Handle(constant.ptr()).CanonicalizeHash() will produce
    // different results. To work-around this problem check for null first.
    if (constant.IsNull()) {
      return kNullIdentityHash;
    }
    return constant.IsInstance() ? Instance::Cast(constant).CanonicalizeHash()
                                 : Utils::WordHash(constant.GetClassId());
  }

  const Object& constant_;
  Representation representation_;
  uword hash_;
};

struct ConstantPoolTrait {
  typedef ConstantInstr* Value;
  typedef ConstantAndRepresentation Key;
  typedef ConstantInstr* Pair;

  static Key KeyOf(Pair kv) { return {kv->value(), kv->representation()}; }

  static Value ValueOf(Pair kv) { return kv; }

  static inline uword Hash(Key key) { return key.hash(); }

  static inline bool IsKeyEqual(Pair kv, Key key) {
    return (kv->value().ptr() == key.constant().ptr()) &&
           (kv->representation() == key.representation());
  }
};

struct PrologueInfo {
  // The first blockid used for prologue building.  This information can be used
  // by the inliner for budget calculations: The prologue code falls away when
  // inlining, so we should not include it in the budget.
  intptr_t min_block_id;

  // The last blockid used for prologue building.  This information can be used
  // by the inliner for budget calculations: The prologue code falls away when
  // inlining, so we should not include it in the budget.
  intptr_t max_block_id;

  PrologueInfo(intptr_t min, intptr_t max)
      : min_block_id(min), max_block_id(max) {}

  bool Contains(intptr_t block_id) const {
    return min_block_id <= block_id && block_id <= max_block_id;
  }
};

struct InliningInfo {
  // Maps inline_id_to_function[inline_id] -> function. Top scope
  // function has inline_id 0. The map is populated by the inliner.
  GrowableArray<const Function*> inline_id_to_function;
  // Token position where inlining occurred.
  GrowableArray<TokenPosition> inline_id_to_token_pos;
  // For a given inlining-id(index) specifies the caller's inlining-id.
  GrowableArray<intptr_t> caller_inline_id;

  explicit InliningInfo(const Function* function) {
    // Top scope function is at inlining id 0.
    inline_id_to_function.Add(function);
    // Top scope function has no caller (-1).
    caller_inline_id.Add(-1);
    // We do not add a token position for the top scope function to
    // |inline_id_to_token_pos| because it is not (currently) inlined into
    // another graph at a given token position. A side effect of this is that
    // the length of |inline_id_to_function| and |caller_inline_id| is always
    // larger than the length of |inline_id_to_token_pos| by one.
  }
};

// Class to encapsulate the construction and manipulation of the flow graph.
class FlowGraph : public ZoneObject {
 public:
  enum class CompilationMode {
    kUnoptimized,
    kOptimized,
    kIntrinsic,
  };

  FlowGraph(const ParsedFunction& parsed_function,
            GraphEntryInstr* graph_entry,
            intptr_t max_block_id,
            PrologueInfo prologue_info,
            CompilationMode compilation_mode);

  // Function properties.
  const ParsedFunction& parsed_function() const { return parsed_function_; }
  const Function& function() const { return parsed_function_.function(); }

  void Print(const char* phase = "unknown");

  // The number of directly accessable parameters (above the frame pointer).
  // All other parameters can only be indirectly loaded via metadata found in
  // the arguments descriptor.
  intptr_t num_direct_parameters() const { return num_direct_parameters_; }

  // The number of variables (or boxes) which code can load from / store to.
  // The SSA renaming will insert phi's for them (and only them - i.e. there
  // will be no phi insertion for [LocalVariable]s pointing to the expression
  // stack!).
  intptr_t variable_count() const {
    return num_direct_parameters_ + parsed_function_.num_stack_locals();
  }

  // The number of variables during OSR, which may include stack slots
  // that pass in initial contents for the expression stack.
  intptr_t osr_variable_count() const {
    ASSERT(IsCompiledForOsr());
    return variable_count() + graph_entry()->osr_entry()->stack_depth();
  }

  static Representation ParameterRepresentationAt(const Function& function,
                                                  intptr_t index);

  static Representation ReturnRepresentationOf(const Function& function);

  // The number of variables (or boxes) inside the functions frame - meaning
  // below the frame pointer.  This does not include the expression stack.
  intptr_t num_stack_locals() const {
    return parsed_function_.num_stack_locals();
  }

  bool IsIrregexpFunction() const { return function().IsIrregexpFunction(); }

  LocalVariable* SuspendStateVar() const {
    return parsed_function().suspend_state_var();
  }

  intptr_t SuspendStateEnvIndex() const { return EnvIndex(SuspendStateVar()); }

  LocalVariable* CurrentContextVar() const {
    return parsed_function().current_context_var();
  }

  intptr_t CurrentContextEnvIndex() const {
    return EnvIndex(parsed_function().current_context_var());
  }

  intptr_t RawTypeArgumentEnvIndex() const {
    return EnvIndex(parsed_function().RawTypeArgumentsVariable());
  }

  intptr_t ArgumentDescriptorEnvIndex() const {
    return EnvIndex(parsed_function().arg_desc_var());
  }

  intptr_t EnvIndex(const LocalVariable* variable) const {
    ASSERT(!variable->is_captured());
    return num_direct_parameters_ - variable->index().value();
  }

  // Context and :suspend_state variables are never pruned and
  // artificially kept alive.
  bool IsImmortalVariable(intptr_t env_index) const {
    return (env_index == CurrentContextEnvIndex()) ||
           (SuspendStateVar() != nullptr &&
            env_index == SuspendStateEnvIndex());
  }

  // Flow graph orders.
  const GrowableArray<BlockEntryInstr*>& preorder() const { return preorder_; }
  const GrowableArray<BlockEntryInstr*>& postorder() const {
    return postorder_;
  }
  const GrowableArray<BlockEntryInstr*>& reverse_postorder() const {
    return reverse_postorder_;
  }
  const GrowableArray<BlockEntryInstr*>& optimized_block_order() const {
    return optimized_block_order_;
  }
  const GrowableArray<TryEntryInstr*>& try_entries() const {
    return try_entries_;
  }
  TryEntryInstr* GetTryEntryByTryIndex(intptr_t try_index) const {
    ASSERT(try_index < try_entries_.length());
    return try_entries_[try_index];
  }
  CatchBlockEntryInstr* GetCatchBlockByTryIndex(intptr_t try_index) const {
    return GetTryEntryByTryIndex(try_index)->catch_target();
  }
  intptr_t max_try_index() const { return max_try_index_; }

  // In AOT these are guaranteed to be topologically sorted, but not in JIT.
  GrowableArray<BlockEntryInstr*>* CodegenBlockOrder();
  const GrowableArray<BlockEntryInstr*>* CodegenBlockOrder() const;

  // Iterators.
  BlockIterator reverse_postorder_iterator() const {
    return BlockIterator(reverse_postorder());
  }
  BlockIterator postorder_iterator() const {
    return BlockIterator(postorder());
  }

  void EnsureSSATempIndex(Definition* defn, Definition* replacement);

  void ReplaceCurrentInstruction(ForwardInstructionIterator* iterator,
                                 Instruction* current,
                                 Instruction* replacement);

  Instruction* CreateCheckClass(Definition* to_check,
                                const Cids& cids,
                                intptr_t deopt_id,
                                const InstructionSource& source);

  bool ShouldOmitCheckBoundsIn(const Function& caller);

  Instruction* AppendCheckBound(Instruction* cursor,
                                Definition* length,
                                Definition** index,
                                intptr_t deopt_id,
                                Environment* env);

  void AddExactnessGuard(InstanceCallInstr* call, intptr_t receiver_cid);

  intptr_t current_ssa_temp_index() const { return current_ssa_temp_index_; }
  void set_current_ssa_temp_index(intptr_t index) {
    current_ssa_temp_index_ = index;
  }

  intptr_t max_vreg() const {
    return current_ssa_temp_index() * kMaxLocationCount;
  }

  enum class ToCheck { kNoCheck, kCheckNull, kCheckCid };

  // Uses CHA to determine if the called method can be overridden.
  // Return value indicates that the call needs no check at all,
  // just a null check, or a full class check.
  ToCheck CheckForInstanceCall(InstanceCallInstr* call,
                               UntaggedFunction::Kind kind) const;

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

  intptr_t max_block_id() const { return max_block_id_; }
  void set_max_block_id(intptr_t id) { max_block_id_ = id; }
  intptr_t allocate_block_id() { return ++max_block_id_; }

  GraphEntryInstr* graph_entry() const { return graph_entry_; }

  ConstantInstr* constant_null() const { return constant_null_; }

  ConstantInstr* constant_dead() const { return constant_dead_; }

  void AllocateSSAIndex(Definition* def) {
    def->set_ssa_temp_index(current_ssa_temp_index_);
    current_ssa_temp_index_++;
  }

  intptr_t InstructionCount() const;

  // Returns the definition for the object from the constant pool if
  // one exists, otherwise returns nullptr.
  ConstantInstr* GetExistingConstant(
      const Object& object,
      Representation representation = kTagged) const;

  // Always returns a definition for the object from the constant pool,
  // allocating one if it doesn't already exist.
  ConstantInstr* GetConstant(const Object& object,
                             Representation representation = kTagged);

  void AddToGraphInitialDefinitions(Definition* defn);
  void AddToInitialDefinitions(BlockEntryWithInitialDefs* entry,
                               Definition* defn);

  // Tries to create a constant definition with the given value which can be
  // used to replace the given operation. Ensures that the representation of
  // the replacement matches the representation of the original definition.
  // If the given value can't be represented using matching representation
  // then returns op itself.
  Definition* TryCreateConstantReplacementFor(Definition* op,
                                              const Object& value);

  // Returns true if the given constant value can be represented in the given
  // representation.
  static bool IsConstantRepresentable(const Object& value,
                                      Representation target_rep,
                                      bool tagged_value_must_be_smi);

  enum UseKind { kEffect, kValue };

  void InsertBefore(Instruction* next,
                    Instruction* instr,
                    Environment* env,
                    UseKind use_kind) {
    InsertAfter(next->previous(), instr, env, use_kind);
  }
  void InsertSpeculativeBefore(Instruction* next,
                               Instruction* instr,
                               Environment* env,
                               UseKind use_kind) {
    InsertSpeculativeAfter(next->previous(), instr, env, use_kind);
  }
  void InsertAfter(Instruction* prev,
                   Instruction* instr,
                   Environment* env,
                   UseKind use_kind);

  // Inserts a speculative [instr] after existing [prev] instruction.
  //
  // If the inserted [instr] deopts eagerly or lazily we will always continue in
  // unoptimized code at before-call using the given [env].
  //
  // This is mainly used during inlining / call specializing when replacing
  // calls with N specialized instructions where the inserted [1..N[
  // instructions cannot continue in unoptimized code after-call since they
  // would miss instructions following the one that lazy-deopted.
  //
  // For example specializing an instance call to an implicit field setter
  //
  //     InstanceCall:<id>(v0, set:<name>, args = [v1])
  //
  // to
  //
  //     v2 <- AssertAssignable:<id>(v1, ...)
  //     StoreField(v0, v2)
  //
  // If the [AssertAssignable] causes a lazy-deopt on return, we'll have to
  // *re-try* the implicit setter call in unoptimized mode, i.e. lazy deopt to
  // before-call (otherwise - if we continued after-call - the
  // StoreField would not be performed).
  void InsertSpeculativeAfter(Instruction* prev,
                              Instruction* instr,
                              Environment* env,
                              UseKind use_kind);
  Instruction* AppendTo(Instruction* prev,
                        Instruction* instr,
                        Environment* env,
                        UseKind use_kind);
  Instruction* AppendSpeculativeTo(Instruction* prev,
                                   Instruction* instr,
                                   Environment* env,
                                   UseKind use_kind);

  // Operations on the flow graph.
  void ComputeSSA(ZoneGrowableArray<Definition*>* inlining_parameters);

  // Verification method for debugging.
  bool VerifyRedefinitions();

  void DiscoverBlocks();

  void MergeBlocks();

  // Insert a redefinition of an original definition after prev and rename all
  // dominated uses of the original.  If an equivalent redefinition is already
  // present, nothing is inserted.
  // Returns the redefinition, if a redefinition was inserted, nullptr
  // otherwise.
  RedefinitionInstr* EnsureRedefinition(Instruction* prev,
                                        Definition* original,
                                        CompileType compile_type);

  // Remove the redefinition instructions inserted to inhibit code motion.
  void RemoveRedefinitions(bool keep_checks = false);

  // Insert MoveArgument instructions and remove explicit def-use
  // relations between calls and their arguments.
  //
  // Compute the maximum number of arguments.
  void InsertMoveArguments();

  // Copy deoptimization target from one instruction to another if we still
  // have to keep deoptimization environment at gotos for LICM purposes.
  void CopyDeoptTarget(Instruction* to, Instruction* from) {
    if (is_licm_allowed()) {
      to->InheritDeoptTarget(zone(), from);
    }
  }

  // Returns true if every Goto in the graph is expected to have a
  // deoptimization environment and can be used as deoptimization target
  // for hoisted instructions.
  bool is_licm_allowed() const { return licm_allowed_; }

  // Stop preserving environments on Goto instructions. LICM is not allowed
  // after this point.
  void disallow_licm() { licm_allowed_ = false; }

  // Returns true if mismatch in input/output representations is allowed.
  bool unmatched_representations_allowed() const {
    return unmatched_representations_allowed_;
  }

  // After the last SelectRepresentations pass all further transformations
  // should maintain matching input/output representations.
  void disallow_unmatched_representations() {
    unmatched_representations_allowed_ = false;
  }

  // Returns true if this flow graph was built for a huge method
  // and certain optimizations should be disabled.
  bool is_huge_method() const { return huge_method_; }
  // Mark this flow graph as huge and disable certain optimizations.
  void mark_huge_method() { huge_method_ = true; }

  PrologueInfo prologue_info() const { return prologue_info_; }

  // Computes the loop hierarchy of the flow graph on demand.
  const LoopHierarchy& GetLoopHierarchy() {
    if (loop_hierarchy_ == nullptr) {
      loop_hierarchy_ = ComputeLoops();
    }
    return loop_hierarchy();
  }

  const LoopHierarchy& loop_hierarchy() const { return *loop_hierarchy_; }

  // Resets the loop hierarchy of the flow graph. Use this to
  // force a recomputation of loop detection by the next call
  // to GetLoopHierarchy() (note that this does not immediately
  // reset the loop_info fields of block entries, although
  // these will be overwritten by that next call).
  void ResetLoopHierarchy() {
    loop_hierarchy_ = nullptr;
    loop_invariant_loads_ = nullptr;
  }

  // Per loop header invariant loads sets. Each set contains load id for
  // those loads that are not affected by anything in the loop and can be
  // hoisted out. Sets are computed by LoadOptimizer.
  ZoneGrowableArray<BitVector*>* loop_invariant_loads() const {
    return loop_invariant_loads_;
  }
  void set_loop_invariant_loads(
      ZoneGrowableArray<BitVector*>* loop_invariant_loads) {
    loop_invariant_loads_ = loop_invariant_loads;
  }

  bool IsCompiledForOsr() const { return graph_entry()->IsCompiledForOsr(); }

  BitVector* captured_parameters() const { return captured_parameters_; }

  intptr_t inlining_id() const { return inlining_id_; }
  void set_inlining_id(intptr_t value) { inlining_id_ = value; }

  InliningInfo& inlining_info() { return inlining_info_; }
  const InliningInfo& inlining_info() const { return inlining_info_; }

  // Returns true if any instructions were canonicalized away.
  bool Canonicalize();

  // Attaches new ICData's to static/instance calls which don't already have
  // them.
  void PopulateWithICData(const Function& function);

  void SelectRepresentations();

  // Remove environments from the instructions which do not deoptimize.
  void EliminateEnvironments();

  // Extract typed data payloads prior to any LoadIndexed, StoreIndexed, or
  // MemoryCopy instruction where the incoming typed data array(s) are not
  // proven to be internal typed data objects at compile time.
  //
  // Once this is done, no intra-block code motion should be performed.
  void ExtractNonInternalTypedDataPayloads();

  // Insert calls informing the sanitizers about loads and stores as separate IL
  // instructions. Separate IL instructions allow for much less spilling via the
  // regular register alloctor compared to internal spilling within the load or
  // store instructions. This transformation must happen very late in the
  // pipeline, after no more loads or store can be added or moved around.
  void AddAsanMsanInstrumentation();
  void AddTsanInstrumentation();

  bool IsReceiver(Definition* def) const;

  // Optimize (a << b) & c pattern: if c is a positive Smi or zero, then the
  // shift can be a truncating Smi shift-left and result is always Smi.
  // Merge instructions (only per basic-block).
  void TryOptimizePatterns();

  // Replaces uses that are dominated by dom of 'def' with 'other'.
  // Note: uses that occur at instruction dom itself are not dominated by it.
  static void RenameDominatedUses(Definition* def,
                                  Instruction* dom,
                                  Definition* other);

  // Renames uses of redefined values to make sure that uses of redefined
  // values that are dominated by a redefinition are renamed.
  void RenameUsesDominatedByRedefinitions();

  bool should_print() const { return should_print_; }
  const uint8_t* compiler_pass_filters() const {
    return compiler_pass_filters_;
  }

  bool should_reorder_blocks() const { return should_reorder_blocks_; }

  bool should_omit_check_bounds() const { return should_omit_check_bounds_; }

  //
  // High-level utilities.
  //

  // Logical-AND (for use in short-circuit diamond).
  struct LogicalAnd {
    LogicalAnd(ConditionInstr* x, ConditionInstr* y) : oper1(x), oper2(y) {}
    ConditionInstr* oper1;
    ConditionInstr* oper2;
  };

  // Constructs a diamond control flow at the instruction, inheriting
  // properties from inherit and using the given compare. Returns the
  // join (and true/false blocks in out parameters). Updates dominance
  // relation, but not the succ/pred ordering on block.
  JoinEntryInstr* NewDiamond(Instruction* instruction,
                             Instruction* inherit,
                             ConditionInstr* condition,
                             TargetEntryInstr** block_true,
                             TargetEntryInstr** block_false);

  // As above, but with a short-circuit on two comparisons.
  JoinEntryInstr* NewDiamond(Instruction* instruction,
                             Instruction* inherit,
                             const LogicalAnd& condition,
                             TargetEntryInstr** block_true,
                             TargetEntryInstr** block_false);

  // Adds a 2-way phi.
  PhiInstr* AddPhi(JoinEntryInstr* join, Definition* d1, Definition* d2);

  // SSA transformation methods and fields.
  void ComputeDominators(GrowableArray<BitVector*>* dominance_frontier);

  void CreateCommonConstants();

  const TypedData& coverage_array() const { return *coverage_array_; }
  void set_coverage_array(const TypedData& array) { coverage_array_ = &array; }

  // Renumbers SSA values and basic blocks to make numbering dense.
  // Preserves order among block ids.
  //
  // Also collects definitions which are detached from the flow graph
  // but still referenced (currently only MaterializeObject instructions
  // can be detached).
  void CompactSSA(ZoneGrowableArray<Definition*>* detached_defs = nullptr);

  // Maximum number of word-sized slots needed for outgoing arguments.
  intptr_t max_argument_slot_count() const {
    RELEASE_ASSERT(max_argument_slot_count_ >= 0);
    return max_argument_slot_count_;
  }
  void set_max_argument_slot_count(intptr_t count) {
    RELEASE_ASSERT(max_argument_slot_count_ == -1);
    max_argument_slot_count_ = count;
  }

  const std::pair<Location, Representation>& GetDirectParameterInfoAt(
      intptr_t i) {
    return direct_parameter_locations_[i];
  }

  static intptr_t ComputeLocationsOfFixedParameters(
      Zone* zone,
      const Function& function,
      bool should_assign_stack_locations = false,
      compiler::ParameterInfoArray* parameter_info = nullptr);

  static intptr_t ComputeArgumentsSizeInWords(const Function& function,
                                              intptr_t arguments_count);

  static constexpr CompilationMode CompilationModeFrom(bool is_optimizing) {
    return is_optimizing ? CompilationMode::kOptimized
                         : CompilationMode::kUnoptimized;
  }

  // If either IsExternalPayloadClassId([cid]) or
  // IsExternalPayloadClassId(array()->Type()->ToCid()) is true and
  // [array] (an input of [instr]) is tagged, inserts a load of the array
  // payload as an untagged pointer and rebinds [array] to the new load.
  //
  // Otherwise does not change the flow graph.
  //
  // Returns whether any changes were made to the flow graph.
  bool ExtractExternalUntaggedPayload(Instruction* instr,
                                      Value* array,
                                      classid_t cid);

 private:
  friend class FlowGraphCompiler;  // TODO(ajcbik): restructure
  friend class FlowGraphChecker;
  friend class IfConverter;
  friend class BranchSimplifier;
  friend class ConstantPropagator;
  friend class DeadCodeElimination;
  friend class compiler::GraphIntrinsifier;

  void CompressPath(intptr_t start_index,
                    intptr_t current_index,
                    GrowableArray<intptr_t>* parent,
                    GrowableArray<intptr_t>* label);

  void AddSyntheticPhis(BlockEntryInstr* block);

  void Rename(GrowableArray<PhiInstr*>* live_phis,
              VariableLivenessAnalysis* variable_liveness,
              ZoneGrowableArray<Definition*>* inlining_parameters);
  void RenameRecursive(BlockEntryInstr* block_entry,
                       GrowableArray<Definition*>* env,
                       GrowableArray<PhiInstr*>* live_phis,
                       VariableLivenessAnalysis* variable_liveness,
                       ZoneGrowableArray<Definition*>* inlining_parameters);
#if defined(DEBUG)
  // Validates no phis are missing on join entry instructions.
  void ValidatePhis();
#endif  // defined(DEBUG)

  void PopulateEnvironmentFromFunctionEntry(
      FunctionEntryInstr* function_entry,
      GrowableArray<Definition*>* env,
      GrowableArray<PhiInstr*>* live_phis,
      VariableLivenessAnalysis* variable_liveness,
      ZoneGrowableArray<Definition*>* inlining_parameters);

  void PopulateEnvironmentFromOsrEntry(OsrEntryInstr* osr_entry,
                                       GrowableArray<Definition*>* env);

  void PopulateEnvironmentFromCatchEntry(CatchBlockEntryInstr* catch_entry,
                                         GrowableArray<Definition*>* env);

  void AttachEnvironment(Instruction* instr, GrowableArray<Definition*>* env);

  void InsertPhis(const GrowableArray<BlockEntryInstr*>& preorder,
                  VariableLivenessAnalysis& variable_liveness,
                  const GrowableArray<BitVector*>& dom_frontier,
                  GrowableArray<PhiInstr*>* live_phis);
  void AddCatchEntryParameter(intptr_t var_index,
                              CatchBlockEntryInstr* catch_entry);
  void InsertCatchBlockParams(const GrowableArray<BlockEntryInstr*>& preorder,
                              VariableLivenessAnalysis& variable_liveness);

  void RemoveDeadPhis(GrowableArray<PhiInstr*>* live_phis);

  void ReplacePredecessor(BlockEntryInstr* old_block,
                          BlockEntryInstr* new_block);

  // Finds the blocks in the natural loop for the back edge m->n. The
  // algorithm is described in "Advanced Compiler Design & Implementation"
  // (Muchnick) p192. Returns a BitVector indexed by block pre-order
  // number where each bit indicates membership in the loop.
  BitVector* FindLoopBlocks(BlockEntryInstr* m, BlockEntryInstr* n) const;

  // Finds the natural loops in the flow graph and attaches the loop
  // information to each entry block. Returns the loop hierarchy.
  LoopHierarchy* ComputeLoops() const;

  void InsertConversionsFor(Definition* def);
  void ConvertUse(Value* use, Representation from);
  void InsertConversion(Representation from,
                        Representation to,
                        Value* use,
                        bool is_environment_use);

  // Insert allocation of a record instance for [def]
  // which returns an unboxed record.
  void InsertRecordBoxing(Definition* def);

  void ComputeIsReceiver(PhiInstr* phi) const;
  void ComputeIsReceiverRecursive(PhiInstr* phi,
                                  GrowableArray<PhiInstr*>* unmark) const;

  void OptimizeLeftShiftBitAndSmiOp(
      ForwardInstructionIterator* current_iterator,
      Definition* bit_and_instr,
      Definition* left_instr,
      Definition* right_instr);

  void TryMergeTruncDivMod(GrowableArray<BinarySmiOpInstr*>* merge_candidates);

  void AppendExtractNthOutputForMerged(Definition* instr,
                                       intptr_t ix,
                                       Representation rep,
                                       intptr_t cid);

  void ExtractUntaggedPayload(Instruction* instr,
                              Value* array,
                              const Slot& slot,
                              InnerPointerAccess access);

  void ExtractNonInternalTypedDataPayload(Instruction* instr,
                                          Value* array,
                                          classid_t cid);

  Thread* thread_;

  // DiscoverBlocks computes parent_ and assigned_vars_ which are then used
  // if/when computing SSA.
  GrowableArray<intptr_t> parent_;

  intptr_t current_ssa_temp_index_;
  intptr_t max_block_id_;

  // Flow graph fields.
  const ParsedFunction& parsed_function_;
  intptr_t num_direct_parameters_;
  compiler::ParameterInfoArray direct_parameter_locations_;
  GraphEntryInstr* graph_entry_;
  GrowableArray<BlockEntryInstr*> preorder_;
  GrowableArray<BlockEntryInstr*> postorder_;
  GrowableArray<BlockEntryInstr*> reverse_postorder_;
  GrowableArray<BlockEntryInstr*> optimized_block_order_;
  // Try entries indexed by try-index
  GrowableArray<TryEntryInstr*> try_entries_;
  intptr_t max_try_index_ = -1;
  ConstantInstr* constant_null_;
  ConstantInstr* constant_dead_;

  bool licm_allowed_;
  bool unmatched_representations_allowed_ = true;
  bool huge_method_ = false;
  const bool should_reorder_blocks_;

  const PrologueInfo prologue_info_;

  // Loop related fields.
  LoopHierarchy* loop_hierarchy_;
  ZoneGrowableArray<BitVector*>* loop_invariant_loads_;

  DirectChainedHashMap<ConstantPoolTrait> constant_instr_pool_;
  BitVector* captured_parameters_;

  // Inlining related fields.
  intptr_t inlining_id_;
  InliningInfo inlining_info_;

  bool should_print_;
  const bool should_omit_check_bounds_;
  uint8_t* compiler_pass_filters_ = nullptr;

  intptr_t max_argument_slot_count_ = -1;

  const TypedData* coverage_array_ = &TypedData::empty_coverage_array();
};

class LivenessAnalysis : public ValueObject {
 public:
  LivenessAnalysis(const FlowGraph* flow_graph, const intptr_t variable_count);

  void Analyze();

  virtual ~LivenessAnalysis() {}

  BitVector* GetLiveInSetAt(intptr_t postorder_number) const {
    return live_in_[postorder_number];
  }

  BitVector* GetLiveOutSetAt(intptr_t postorder_number) const {
    return live_out_[postorder_number];
  }

  BitVector* GetLiveInSet(BlockEntryInstr* block) const {
    return GetLiveInSetAt(block->postorder_number());
  }

  BitVector* GetKillSet(BlockEntryInstr* block) const {
    return kill_[block->postorder_number()];
  }

  BitVector* GetLiveOutSet(BlockEntryInstr* block) const {
    return GetLiveOutSetAt(block->postorder_number());
  }

  // Print results of liveness analysis.
  void Dump();

 protected:
  // Compute initial values for live-out, kill and live-in sets.
  //
  // It's also responsible for populating [does_block_have_throw] set.
  virtual void ComputeInitialSets() = 0;

  // Update live-out set for the given block: live-out should contain
  // all values that are live-in for block's successors.
  // Returns true if live-out set was changed.
  bool UpdateLiveOut(const BlockEntryInstr& instr);

  // Update live-in set for the given block: live-in should contain
  // all values that are live-out from the block and are not defined
  // by this block.
  // Returns true if live-in set was changed.
  bool UpdateLiveIn(const BlockEntryInstr& instr);

  // Perform fix-point iteration updating live-out and live-in sets
  // for blocks until they stop changing.
  void ComputeLiveInAndLiveOutSets();

  Zone* zone() const { return zone_; }

  Zone* zone_;

  const FlowGraph* flow_graph_;

  const intptr_t variable_count_;

  const GrowableArray<BlockEntryInstr*>& postorder_;

  // Live-out sets for each block.  They contain indices of variables
  // that are live out from this block. That is values that were (1) either
  // defined in this block or live into it, and (2) that are used in some
  // successor block.
  GrowableArray<BitVector*> live_out_;

  // Kill sets for each block.  They contain indices of variables that
  // are defined by this block.
  GrowableArray<BitVector*> kill_;

  // Live-in sets for each block.  They contain indices of variables
  // that are used by this block or its successors.
  GrowableArray<BitVector*> live_in_;

  // Given block's postoroder index indicates whether block has any
  // instructions that throw.
  BitVector does_block_have_throw;
};

template <typename T, typename E>
class Worklist : public ValueObject {
 public:
  Worklist(FlowGraph* flow_graph, intptr_t initial_capacity)
      : elements_(initial_capacity),
        contains_vector_(
            new BitVector(flow_graph->zone(), T::FirstUnusedId(flow_graph))) {}

  void Add(E* e) {
    if (!Contains(e)) {
      elements_.Add(e);
      contains_vector_->Add(T::IdOf(e));
    }
  }

  bool Contains(E* e) const {
    const intptr_t id = T::IdOf(e);
    return id >= 0 && contains_vector_->Contains(id);
  }

  bool IsEmpty() const { return elements_.is_empty(); }

  E* RemoveLast() {
    E* e = elements_.RemoveLast();
    contains_vector_->Remove(T::IdOf(e));
    return e;
  }

  BitVector* contains_vector() const { return contains_vector_; }

  void Clear() {
    elements_.TruncateTo(0);
    contains_vector_->Clear();
  }

 protected:
  GrowableArray<E*> elements_;
  BitVector* contains_vector_;
};

class DefinitionWorklist : public Worklist<DefinitionWorklist, Definition> {
 public:
  DefinitionWorklist(FlowGraph* flow_graph, intptr_t initial_capacity)
      : Worklist(flow_graph, initial_capacity) {}

  const GrowableArray<Definition*>& definitions() const { return elements_; }

  static intptr_t FirstUnusedId(FlowGraph* flow_graph) {
    return flow_graph->current_ssa_temp_index();
  }

  static intptr_t IdOf(Definition* defn) { return defn->ssa_temp_index(); }
};

class BlockEntryWorklist
    : public Worklist<BlockEntryWorklist, BlockEntryInstr> {
 public:
  BlockEntryWorklist(FlowGraph* flow_graph, intptr_t initial_capacity)
      : Worklist(flow_graph, initial_capacity) {}

  const GrowableArray<BlockEntryInstr*>& blocks() const { return elements_; }

  static intptr_t FirstUnusedId(FlowGraph* flow_graph) {
    return flow_graph->preorder().length();
  }

  static intptr_t IdOf(BlockEntryInstr* block) {
    return block->preorder_number();
  }
};

}  // namespace dart

#endif  // RUNTIME_VM_COMPILER_BACKEND_FLOW_GRAPH_H_
