// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "vm/compiler/compiler_pass.h"

#include "vm/compiler/backend/block_scheduler.h"
#include "vm/compiler/backend/branch_optimizer.h"
#include "vm/compiler/backend/constant_propagator.h"
#include "vm/compiler/backend/flow_graph_checker.h"
#include "vm/compiler/backend/flow_graph_compiler.h"
#include "vm/compiler/backend/il_printer.h"
#include "vm/compiler/backend/inliner.h"
#include "vm/compiler/backend/linearscan.h"
#include "vm/compiler/backend/range_analysis.h"
#include "vm/compiler/backend/redundancy_elimination.h"
#include "vm/compiler/backend/type_propagator.h"
#include "vm/compiler/call_specializer.h"
#include "vm/compiler/compiler_timings.h"
#include "vm/compiler/write_barrier_elimination.h"
#if defined(DART_PRECOMPILER)
#include "vm/compiler/aot/aot_call_specializer.h"
#include "vm/compiler/aot/precompiler.h"
#endif
#include "vm/thread.h"
#include "vm/timeline.h"

#define COMPILER_PASS_REPEAT(Name, Body)                                       \
  class CompilerPass_##Name : public CompilerPass {                            \
   public:                                                                     \
    CompilerPass_##Name() : CompilerPass(k##Name, #Name) {}                    \
                                                                               \
    static bool Register() { return true; }                                    \
                                                                               \
   protected:                                                                  \
    virtual bool DoBody(CompilerPassState* state) const {                      \
      FlowGraph* flow_graph = state->flow_graph();                             \
      USE(flow_graph);                                                         \
      Body;                                                                    \
    }                                                                          \
  };                                                                           \
  static CompilerPass_##Name compiler_pass_##Name;

#define COMPILER_PASS(Name, Body)                                              \
  COMPILER_PASS_REPEAT(Name, {                                                 \
    Body;                                                                      \
    return false;                                                              \
  })

namespace dart {

CompilerPassState::CompilerPassState(Thread* thread,
                                     FlowGraph* flow_graph,
                                     Precompiler* precompiler)
    : thread(thread),
      precompiler(precompiler),
      inlining_depth(0),
      sinking(nullptr),
      call_specializer(nullptr),
      sticky_flags(0),
      flow_graph_(flow_graph) {}

CompilerPass* CompilerPass::passes_[CompilerPass::kNumPasses] = {nullptr};
uint8_t CompilerPass::flags_[CompilerPass::kNumPasses] = {0};

DEFINE_OPTION_HANDLER(CompilerPass::ParseFiltersFromFlag,
                      compiler_passes,
                      "List of comma separated compilation passes flags. "
                      "Use -Name to disable a pass, Name to print IL after it. "
                      "Do --compiler-passes=help for more information.");
DECLARE_FLAG(bool, print_flow_graph);
DECLARE_FLAG(bool, print_flow_graph_optimized);
DEFINE_FLAG(bool, test_il_serialization, false, "Test IL serialization.");

void CompilerPassState::set_flow_graph(FlowGraph* flow_graph) {
  flow_graph_ = flow_graph;
  if (call_specializer != nullptr) {
    call_specializer->set_flow_graph(flow_graph);
  }
}

static const char* kCompilerPassesUsage =
    "=== How to use --compiler-passes flag\n"
    "\n"
    "Pass the list of comma separated compiler pass filter flags.\n"
    "\n"
    "For the given pass Name the following flags are supported:\n"
    "\n"
    "     -Name          disable the pass\n"
    "     ]Name or Name  print IL after the pass\n"
    "     [Name          print IL before the pass\n"
    "     *Name          print IL before and after the pass\n"
    "     *              print IL after each pass.\n"
    "\n"
    " The flag can be followed by '+' which makes it sticky, e.g. Inlining+\n"
    " would cause IL to be printed after all passes that follow inlining and\n"
    " are not disabled.\n"
    "\n"
    "List of compiler passes:\n";

void CompilerPass::ParseFiltersFromFlag(const char* filter) {
  ParseFilters(filter, flags_);
}

uint8_t* CompilerPass::ParseFiltersFromPragma(const char* filter) {
  auto flags =
      ThreadState::Current()->zone()->Alloc<uint8_t>(CompilerPass::kNumPasses);
  ParseFilters(filter, flags);
  return flags;
}

void CompilerPass::ParseFilters(const char* filter, uint8_t* pass_flags) {
  if (filter == nullptr || *filter == 0) {
    return;
  }

  if (strcmp(filter, "help") == 0) {
    OS::PrintErr("%s", kCompilerPassesUsage);
    for (intptr_t i = 0; i < kNumPasses; i++) {
      if (passes_[i] != nullptr) {
        OS::PrintErr("  %s\n", passes_[i]->name());
      }
    }
    return;
  }

  // Clear all flags.
  memset(pass_flags, 0, CompilerPass::kNumPasses);

  for (const char *start = filter, *end = filter; *end != 0;
       start = (end + 1)) {
    // Search forward until the separator ',' or the end of filter is reached.
    end = start;
    while (*end != ',' && *end != '\0') {
      end++;
    }
    if (start == end) {
      OS::PrintErr("Ignoring empty compiler pass flag\n");
      continue;
    }

    ParseOneFilter(start, end, pass_flags);
  }
}

void CompilerPass::ParseOneFilter(const char* start,
                                  const char* end,
                                  uint8_t* pass_flags) {
  uint8_t flags = 0;
  if (*start == '-') {
    flags = kDisabled;
  } else if (*start == ']') {
    flags = kTraceAfter;
  } else if (*start == '[') {
    flags = kTraceBefore;
  } else if (*start == '*') {
    flags = kTraceBeforeOrAfter;
  }
  if (flags == 0) {
    flags |= kTraceAfter;
  } else {
    start++;  // Skip the modifier
  }

  size_t suffix = 0;
  if (end[-1] == '+') {
    if (start == (end - 1)) {
      OS::PrintErr("Sticky modifier '+' should follow pass name\n");
      return;
    }
    flags |= kSticky;
    suffix = 1;
  }

  size_t length = (end - start) - suffix;
  if (length != 0) {
    char* pass_name = Utils::StrNDup(start, length);
    CompilerPass* pass = FindPassByName(pass_name);
    if (pass != nullptr) {
      pass_flags[pass->id()] |= flags;
    } else {
      OS::PrintErr("Unknown compiler pass: %s\n", pass_name);
    }
    free(pass_name);
  } else if (flags == kTraceBeforeOrAfter) {
    for (intptr_t i = 0; i < kNumPasses; i++) {
      pass_flags[i] = kTraceAfter;
    }
  }
}

void CompilerPass::Run(CompilerPassState* state) const {
  if ((flags() & kDisabled) != 0) {
    return;
  }

  if ((flags() & kSticky) != 0) {
    state->sticky_flags |= flags();
  }

  const intptr_t kMaxRounds = 2;
  Thread* thread = state->thread;
  bool repeat = true;
  for (intptr_t round = 1; round <= kMaxRounds && repeat; round++) {
    if (round > 1) {
      Get(kCanonicalize)->Run(state);
    }

    CompilerState::Current().set_current_pass(this, state);
    PrintGraph(state, kTraceBefore, round);
    {
      TIMELINE_DURATION(thread, CompilerVerbose, name());
      {
        COMPILER_TIMINGS_PASS_TIMER_SCOPE(thread, id());
        repeat = DoBody(state);
      }
      thread->CheckForSafepoint();
    }
    PrintGraph(state, kTraceAfter, round);
#if defined(DEBUG)
    if (CompilerState::Current().is_optimizing()) {
      FlowGraphChecker(state->flow_graph()).Check(name());
    }
#endif
    CompilerState::Current().set_current_pass(nullptr, nullptr);
  }
}

void CompilerPass::PrintGraph(CompilerPassState* state,
                              Flag mask,
                              intptr_t round) const {
  FlowGraph* flow_graph = state->flow_graph();
  const uint8_t* graph_flags = flow_graph->compiler_pass_filters();
  const uint8_t current_flags =
      (graph_flags != nullptr ? graph_flags[id()] : flags()) |
      state->sticky_flags;

  if ((FLAG_print_flow_graph || FLAG_print_flow_graph_optimized) &&
      flow_graph->should_print() && ((current_flags & mask) != 0)) {
    Zone* zone = state->thread->zone();
    const char* when = mask == kTraceBefore ? "Before" : "After";
    const char* phase =
        round == 1
            ? zone->PrintToString("%s %s", when, name())
            : zone->PrintToString("%s %s (round %" Pd ")", when, name(), round);

    FlowGraphPrinter::PrintGraph(phase, flow_graph);
  }
}

#define INVOKE_PASS(Name)                                                      \
  CompilerPass::Get(CompilerPass::k##Name)->Run(pass_state);

#if defined(DART_PRECOMPILER)
#define INVOKE_PASS_AOT(Name)                                                  \
  if (mode == kAOT) {                                                          \
    INVOKE_PASS(Name);                                                         \
  }
#else
#define INVOKE_PASS_AOT(Name)
#endif

void CompilerPass::RunGraphIntrinsicPipeline(CompilerPassState* pass_state) {
  INVOKE_PASS(AllocateRegistersForGraphIntrinsic);
}

void CompilerPass::RunInliningPipeline(PipelineMode mode,
                                       CompilerPassState* pass_state) {
  INVOKE_PASS(ApplyClassIds);
  INVOKE_PASS(TypePropagation);
  INVOKE_PASS(ApplyICData);
  INVOKE_PASS(Canonicalize);
  // Run constant propagation to make sure we specialize for
  // (optional) constant arguments passed into the inlined method.
  INVOKE_PASS(ConstantPropagation);
  // Constant propagation removes unreachable basic blocks and
  // may open more opportunities for call specialization.
  // Call specialization during inlining may cause more call
  // sites to be discovered and more functions inlined.
  INVOKE_PASS_AOT(ApplyClassIds);
  // Optimize (a << b) & c patterns, merge instructions. Must occur
  // before 'SelectRepresentations' which inserts conversion nodes.
  INVOKE_PASS(TryOptimizePatterns);
}

FlowGraph* CompilerPass::RunPipeline(PipelineMode mode,
                                     CompilerPassState* pass_state,
                                     bool compute_ssa) {
  if (compute_ssa) {
    INVOKE_PASS(ComputeSSA);
  }
  INVOKE_PASS_AOT(ApplyClassIds);
  INVOKE_PASS_AOT(TypePropagation);
  INVOKE_PASS(ApplyICData);
  INVOKE_PASS(TryOptimizePatterns);
  INVOKE_PASS(SetOuterInliningId);
  INVOKE_PASS(TypePropagation);
  INVOKE_PASS(ApplyClassIds);
  INVOKE_PASS(Inlining);
  INVOKE_PASS(TypePropagation);
  INVOKE_PASS(ApplyClassIds);
  INVOKE_PASS(TypePropagation);
  INVOKE_PASS(ApplyICData);
  INVOKE_PASS(Canonicalize);
  INVOKE_PASS(BranchSimplify);
  INVOKE_PASS(IfConvert);
  INVOKE_PASS(Canonicalize);
  INVOKE_PASS(ConstantPropagation);
  INVOKE_PASS(OptimisticallySpecializeSmiPhis);
  INVOKE_PASS(TypePropagation);
  // The extra call specialization pass in AOT is able to specialize more
  // calls after ConstantPropagation, which removes unreachable code, and
  // TypePropagation, which can infer more accurate types after removing
  // unreachable code.
  INVOKE_PASS_AOT(ApplyICData);
  INVOKE_PASS_AOT(OptimizeTypedDataAccesses);
  INVOKE_PASS(SelectRepresentations);
  INVOKE_PASS(CSE);
  INVOKE_PASS(Canonicalize);
  INVOKE_PASS(LICM);
  INVOKE_PASS(TryOptimizePatterns);
  INVOKE_PASS(DSE);
  INVOKE_PASS(TypePropagation);
  INVOKE_PASS(RangeAnalysis);
  INVOKE_PASS(OptimizeBranches);
  INVOKE_PASS(TypePropagation);
  INVOKE_PASS(TryCatchOptimization);
  INVOKE_PASS(EliminateEnvironments);
  INVOKE_PASS(EliminateDeadPhis);
  // Currently DCE assumes that EliminateEnvironments has already been run,
  // so it should not be lifted earlier than that pass.
  INVOKE_PASS(DCE);
  INVOKE_PASS(Canonicalize);
  INVOKE_PASS_AOT(DelayAllocations);
  // Repeat branches optimization after DCE, as it could make more
  // empty blocks.
  INVOKE_PASS(OptimizeBranches);
  INVOKE_PASS(AllocationSinking_Sink);
  INVOKE_PASS(EliminateDeadPhis);
  INVOKE_PASS(DCE);
  INVOKE_PASS(Canonicalize);
  INVOKE_PASS(TypePropagation);
  INVOKE_PASS(SelectRepresentations_Final);
  INVOKE_PASS(UseTableDispatch);
  INVOKE_PASS(EliminateStackOverflowChecks);
  INVOKE_PASS(Canonicalize);
  INVOKE_PASS(AllocationSinking_DetachMaterializations);
  INVOKE_PASS(EliminateWriteBarriers);
  // This must be done after all other possible intra-block code motion.
  INVOKE_PASS(LoweringAfterCodeMotionDisabled);
  INVOKE_PASS(FinalizeGraph);
  INVOKE_PASS(Canonicalize);
  INVOKE_PASS(ReorderBlocks);
  INVOKE_PASS(AllocateRegisters);
  INVOKE_PASS(TestILSerialization);  // Must be last.
  return pass_state->flow_graph();
}

FlowGraph* CompilerPass::RunPipelineWithPasses(
    CompilerPassState* state,
    std::initializer_list<CompilerPass::Id> passes) {
  for (auto pass_id : passes) {
    passes_[pass_id]->Run(state);
  }
  return state->flow_graph();
}

COMPILER_PASS(ComputeSSA, {
  // Transform to SSA (no inlining arguments).
  flow_graph->ComputeSSA(nullptr);
});

COMPILER_PASS(ApplyICData, { state->call_specializer->ApplyICData(); });

COMPILER_PASS(TryOptimizePatterns, { flow_graph->TryOptimizePatterns(); });

COMPILER_PASS(SetOuterInliningId, {
  FlowGraphInliner::SetInliningIdAndTryIndex(flow_graph, 0, kInvalidTryIndex);
});

COMPILER_PASS(Inlining, {
  FlowGraphInliner inliner(flow_graph, state->precompiler);
  state->inlining_depth = inliner.Inline();
});

COMPILER_PASS(TypePropagation,
              { FlowGraphTypePropagator::Propagate(flow_graph); });

COMPILER_PASS(ApplyClassIds, { state->call_specializer->ApplyClassIds(); });

COMPILER_PASS(EliminateStackOverflowChecks, {
  if (!flow_graph->IsCompiledForOsr()) {
    CheckStackOverflowElimination::EliminateStackOverflow(flow_graph);
  }
});

COMPILER_PASS(Canonicalize, {
  // Do optimizations that depend on the propagated type information.
  if (flow_graph->Canonicalize()) {
    flow_graph->Canonicalize();
  }
});

COMPILER_PASS(BranchSimplify, { BranchSimplifier::Simplify(flow_graph); });

COMPILER_PASS(IfConvert, { IfConverter::Simplify(flow_graph); });

COMPILER_PASS_REPEAT(ConstantPropagation, {
  ConstantPropagator::Optimize(flow_graph);
  return true;
});

// Optimistically convert loop phis that have a single non-smi input
// coming from the loop pre-header into smi-phis.
COMPILER_PASS(OptimisticallySpecializeSmiPhis, {
  LICM licm(flow_graph);
  licm.OptimisticallySpecializeSmiPhis();
});

COMPILER_PASS(SelectRepresentations, {
  // Unbox doubles. Performed after constant propagation to minimize
  // interference from phis merging double values and tagged
  // values coming from dead paths.
  flow_graph->SelectRepresentations();
});

COMPILER_PASS(SelectRepresentations_Final, {
  // Final selection of representations. After this pass
  // representations of inputs/outputs should match.
  flow_graph->SelectRepresentations();
  flow_graph->disallow_unmatched_representations();
});

COMPILER_PASS(UseTableDispatch, {
  state->call_specializer->ReplaceInstanceCallsWithDispatchTableCalls();
});

COMPILER_PASS_REPEAT(CSE, { return DominatorBasedCSE::Optimize(flow_graph); });

COMPILER_PASS(LICM, {
  if (flow_graph->is_huge_method()) {
    return false;  // Runs in quadratic time.
  }

  flow_graph->RenameUsesDominatedByRedefinitions();
  DEBUG_ASSERT(flow_graph->VerifyRedefinitions());
  LICM licm(flow_graph);
  licm.Optimize();
  flow_graph->RemoveRedefinitions(/*keep_checks*/ true);
});

COMPILER_PASS(DSE, { DeadStoreElimination::Optimize(flow_graph); });

COMPILER_PASS(RangeAnalysis, {
  if (flow_graph->is_huge_method()) {
    return false;  // Runs in quadratic time.
  }

  // We have to perform range analysis after LICM because it
  // optimistically moves CheckSmi through phis into loop preheaders
  // making some phis smi.
  RangeAnalysis range_analysis(flow_graph);
  range_analysis.Analyze();
});

COMPILER_PASS(OptimizeBranches, {
  // Constant propagation can use information from range analysis to
  // find unreachable branch targets and eliminate branches that have
  // the same true- and false-target.
  ConstantPropagator::OptimizeBranches(flow_graph);
});

COMPILER_PASS(OptimizeTypedDataAccesses,
              { TypedDataSpecializer::Optimize(flow_graph); });

COMPILER_PASS(TryCatchOptimization, {
  OptimizeCatchEntryStates(flow_graph,
                           /*is_aot=*/CompilerState::Current().is_aot());
});

COMPILER_PASS(EliminateEnvironments, { flow_graph->EliminateEnvironments(); });

COMPILER_PASS(EliminateDeadPhis,
              { DeadCodeElimination::EliminateDeadPhis(flow_graph); });

COMPILER_PASS(DCE, { DeadCodeElimination::EliminateDeadCode(flow_graph); });

COMPILER_PASS(DelayAllocations, { DelayAllocations::Optimize(flow_graph); });

COMPILER_PASS(AllocationSinking_Sink, {
  // TODO(vegorov): Support allocation sinking with try-catch.
  if (flow_graph->try_entries().is_empty()) {
    state->sinking = new AllocationSinking(flow_graph);
    state->sinking->Optimize();
  }
});

COMPILER_PASS(AllocationSinking_DetachMaterializations, {
  if (state->sinking != nullptr) {
    // Remove all MaterializeObject instructions inserted by allocation
    // sinking from the flow graph and let them float on the side
    // referenced only from environments. Register allocator will consider
    // them as part of a deoptimization environment.
    state->sinking->DetachMaterializations();
  }
});

COMPILER_PASS(AllocateRegisters, {
  flow_graph->InsertMoveArguments();
  // Ensure loop hierarchy has been computed.
  flow_graph->GetLoopHierarchy();
  // Perform register allocation on the SSA graph.
  FlowGraphAllocator allocator(*flow_graph);
  allocator.AllocateRegisters();
});

COMPILER_PASS(AllocateRegistersForGraphIntrinsic, {
  flow_graph->set_max_argument_slot_count(0);
  // Ensure loop hierarchy has been computed.
  flow_graph->GetLoopHierarchy();
  // Perform register allocation on the SSA graph.
  FlowGraphAllocator allocator(*flow_graph, /*intrinsic_mode=*/true);
  allocator.AllocateRegisters();
});

COMPILER_PASS(ReorderBlocks, { BlockScheduler::ReorderBlocks(flow_graph); });

COMPILER_PASS(EliminateWriteBarriers, { EliminateWriteBarriers(flow_graph); });

COMPILER_PASS(FinalizeGraph, {
  // At the end of the pipeline, force recomputing and caching graph
  // information (instruction and call site counts) for the (assumed)
  // non-specialized case with better values, for future inlining.
  intptr_t instruction_count = 0;
  intptr_t call_site_count = 0;
  FlowGraphInliner::CollectGraphInfo(flow_graph,
                                     /*constants_count*/ 0,
                                     /*force*/ true, &instruction_count,
                                     &call_site_count);
  flow_graph->function().set_inlining_depth(state->inlining_depth);
  // Remove redefinitions for the rest of the pipeline.
  flow_graph->RemoveRedefinitions();
});

COMPILER_PASS(TestILSerialization, {
  // This is the last compiler pass.
  // Test that round-trip IL serialization works before generating code.
  if (FLAG_test_il_serialization && CompilerState::Current().is_aot()) {
    Zone* zone = flow_graph->zone();
    auto* detached_defs = new (zone) ZoneGrowableArray<Definition*>(zone, 0);
    flow_graph->CompactSSA(detached_defs);

    ZoneWriteStream write_stream(flow_graph->zone(), 1024);
    FlowGraphSerializer serializer(&write_stream);
    serializer.WriteFlowGraph(*flow_graph, *detached_defs);
    ReadStream read_stream(write_stream.buffer(), write_stream.bytes_written());
    FlowGraphDeserializer deserializer(flow_graph->parsed_function(),
                                       &read_stream);
    state->set_flow_graph(deserializer.ReadFlowGraph());
  }
});

COMPILER_PASS(LoweringAfterCodeMotionDisabled, {
  flow_graph->ExtractNonInternalTypedDataPayloads();
  flow_graph->AddAsanMsanInstrumentation();
  flow_graph->AddTsanInstrumentation();
});

COMPILER_PASS(GenerateCode, { state->graph_compiler->CompileGraph(); });

}  // namespace dart
