//===------ PhaseManager.cpp ------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "polly/Pass/PhaseManager.h"
#include "polly/CodeGen/CodeGeneration.h"
#include "polly/CodeGen/IslAst.h"
#include "polly/CodePreparation.h"
#include "polly/DeLICM.h"
#include "polly/DeadCodeElimination.h"
#include "polly/DependenceInfo.h"
#include "polly/FlattenSchedule.h"
#include "polly/ForwardOpTree.h"
#include "polly/JSONExporter.h"
#include "polly/MaximalStaticExpansion.h"
#include "polly/PruneUnprofitable.h"
#include "polly/ScheduleOptimizer.h"
#include "polly/ScopDetection.h"
#include "polly/ScopDetectionDiagnostic.h"
#include "polly/ScopGraphPrinter.h"
#include "polly/ScopInfo.h"
#include "polly/Simplify.h"
#include "polly/Support/PollyDebug.h"
#include "llvm/ADT/PriorityWorklist.h"
#include "llvm/Analysis/AssumptionCache.h"
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/IR/Module.h"

#define DEBUG_TYPE "polly-pass"

using namespace polly;
using namespace llvm;

namespace {

/// Recurse through all subregions and all regions and add them to RQ.
static void addRegionIntoQueue(Region &R, SmallVector<Region *> &RQ) {
  RQ.push_back(&R);
  for (const auto &E : R)
    addRegionIntoQueue(*E, RQ);
}

/// The phase pipeline of Polly to be embedded into another pass manager than
/// runs passes on functions.
///
/// Polly holds state besides LLVM-IR (RegionInfo and ScopInfo) between phases
/// that LLVM pass managers do not consider when scheduling analyses and passes.
/// That is, the ScopInfo must persist between phases that a pass manager must
/// not invalidate to recompute later.
class PhaseManager {
private:
  Function &F;
  FunctionAnalysisManager &FAM;
  PollyPassOptions Opts;

public:
  PhaseManager(Function &F, FunctionAnalysisManager &FAM, PollyPassOptions Opts)
      : F(F), FAM(FAM), Opts(std::move(Opts)) {}

  /// Execute Polly's phases as indicated by the options.
  bool run() {
    // Get analyses from the function pass manager.
    // These must be preserved during all phases so that if processing one SCoP
    // has finished, the next SCoP can still use them. Recomputing is not an
    // option because ScopDetection stores references to the old results.
    // TODO: CodePreparation doesn't actually need these analysis, it just keeps
    // them up-to-date. If they are not computed yet, can also compute after the
    // prepare phase.
    LoopInfo &LI = FAM.getResult<LoopAnalysis>(F);
    DominatorTree &DT = FAM.getResult<DominatorTreeAnalysis>(F);
    bool ModifiedIR = false;

    // Phase: prepare
    // TODO: Setting ModifiedIR will invalidate any analysis, even if DT, LI are
    // preserved.
    if (Opts.isPhaseEnabled(PassPhase::Prepare)) {
      if (runCodePreparation(F, &DT, &LI, nullptr)) {
        PreservedAnalyses PA;
        PA.preserve<DominatorTreeAnalysis>();
        PA.preserve<LoopAnalysis>();
        FAM.invalidate(F, PA);
        ModifiedIR = true;
      }
    }

    // Can't do anything without detection
    if (!Opts.isPhaseEnabled(PassPhase::Detection))
      return false;

    AAResults &AA = FAM.getResult<AAManager>(F);
    ScalarEvolution &SE = FAM.getResult<ScalarEvolutionAnalysis>(F);
    OptimizationRemarkEmitter &ORE =
        FAM.getResult<OptimizationRemarkEmitterAnalysis>(F);

    // ScopDetection is modifying RegionInfo, do not cache it, nor use a cached
    // version.
    RegionInfo RI = RegionInfoAnalysis().run(F, FAM);

    // Phase: detection
    ScopDetection SD(DT, SE, LI, RI, AA, ORE);
    SD.detect(F);
    if (Opts.isPhaseEnabled(PassPhase::PrintDetect)) {
      outs() << "Detected Scops in Function " << F.getName() << "\n";
      for (const Region *R : SD.ValidRegions)
        outs() << "Valid Region for Scop: " << R->getNameStr() << '\n';
      outs() << "\n";
    }

    if (Opts.isPhaseEnabled(PassPhase::DotScops))
      printGraphForFunction(F, &SD, "scops", false);
    if (Opts.isPhaseEnabled(PassPhase::DotScopsOnly))
      printGraphForFunction(F, &SD, "scopsonly", true);

    auto ViewScops = [&](const char *Name, bool IsSimply) {
      if (Opts.ViewFilter.empty() && !F.getName().count(Opts.ViewFilter))
        return;

      if (Opts.ViewAll || std::distance(SD.begin(), SD.end()) > 0)
        viewGraphForFunction(F, &SD, Name, IsSimply);
    };
    if (Opts.isPhaseEnabled(PassPhase::ViewScops))
      ViewScops("scops", false);
    if (Opts.isPhaseEnabled(PassPhase::ViewScopsOnly))
      ViewScops("scopsonly", true);

    // Phase: scops
    AssumptionCache &AC = FAM.getResult<AssumptionAnalysis>(F);
    const DataLayout &DL = F.getParent()->getDataLayout();
    ScopInfo Info(DL, SD, SE, LI, AA, DT, AC, ORE);
    if (Opts.isPhaseEnabled(PassPhase::PrintScopInfo)) {
      if (Region *TLR = RI.getTopLevelRegion()) {
        SmallVector<Region *> Regions;
        addRegionIntoQueue(*TLR, Regions);

        // reverse iteration because the regression tests expect it.
        for (Region *R : reverse(Regions)) {
          Scop *S = Info.getScop(R);
          outs() << "Printing analysis 'Polly - Create polyhedral "
                    "description of Scops' for region: '"
                 << R->getNameStr() << "' in function '" << F.getName()
                 << "':\n";
          if (S)
            outs() << *S;
          else
            outs() << "Invalid Scop!\n";
        }
      }
    }

    SmallPriorityWorklist<Region *, 4> Worklist;
    for (auto &[R, S] : Info)
      if (S)
        Worklist.insert(R);

    TargetTransformInfo &TTI = FAM.getResult<TargetIRAnalysis>(F);
    while (!Worklist.empty()) {
      Region *R = Worklist.pop_back_val();
      Scop *S = Info.getScop(R);
      if (!S) {
        // This can happen if codegenning of a previous SCoP made this region
        // not-a-SCoP anymore.
        POLLY_DEBUG(dbgs() << "SCoP in Region '" << *R << "' disappeared");
        continue;
      }

      if (!SD.isMaxRegionInScop(*R, /*Verify=*/false))
        continue;

      // Phase: flatten
      if (Opts.isPhaseEnabled(PassPhase::Flatten))
        runFlattenSchedulePass(*S);

      // Phase: deps
      // Actual analysis runs on-demand, so it does not matter whether the phase
      // is actually enabled, but use this location to print dependencies.
      DependenceAnalysis::Result DA = runDependenceAnalysis(*S);
      if (Opts.isPhaseEnabled(PassPhase::PrintDependences)) {
        assert(Opts.isPhaseEnabled(PassPhase::Dependences));
        const Dependences &D = DA.getDependences(Opts.PrintDepsAnalysisLevel);
        D.print(outs());
      }

      // Phase: import-jscop
      if (Opts.isPhaseEnabled(PassPhase::ImportJScop))
        runImportJSON(*S, DA);

      // Phase: simplify-0
      bool ModifiedSinceSimplify = true;
      if (Opts.isPhaseEnabled(PassPhase::Simplify0)) {
        runSimplify(*S, 0);
        ModifiedSinceSimplify = false;
      }

      // Phase: optree
      if (Opts.isPhaseEnabled(PassPhase::Optree)) {
        bool ModifiedByOptree = runForwardOpTree(*S);
        ModifiedSinceSimplify |= ModifiedByOptree;
      }

      // Phase: delicm
      if (Opts.isPhaseEnabled(PassPhase::DeLICM)) {
        bool ModifiedByDelicm = runDeLICM(*S);
        ModifiedSinceSimplify |= ModifiedByDelicm;
      }

      // Phase: simplify-1
      // If we have already run simplify-0, do not re-run it if the SCoP has not
      // changed since then.
      if (ModifiedSinceSimplify && Opts.isPhaseEnabled(PassPhase::Simplify1)) {
        runSimplify(*S, 1);
        ModifiedSinceSimplify = false;
      }

      // Phase: dce
      if (Opts.isPhaseEnabled(PassPhase::DeadCodeElimination))
        runDeadCodeElim(*S, DA);

      // Phase: mse
      if (Opts.isPhaseEnabled(PassPhase::MaximumStaticExtension))
        runMaximalStaticExpansion(*S, DA);

      // Phase: prune
      if (Opts.isPhaseEnabled(PassPhase::PruneUnprofitable))
        runPruneUnprofitable(*S);

      // Phase: opt-isl
      if (Opts.isPhaseEnabled(PassPhase::Optimization))
        runIslScheduleOptimizer(*S, &TTI, DA);

      // Phase: import-jscop
      if (Opts.isPhaseEnabled(PassPhase::ExportJScop))
        runExportJSON(*S);

      // Phase: ast
      // Cannot run codegen unless ast is enabled
      if (!Opts.isPhaseEnabled(PassPhase::AstGen))
        continue;
      std::unique_ptr<IslAstInfo> IslAst = runIslAstGen(*S, DA);

      // Phase: codegen
      if (!Opts.isPhaseEnabled(PassPhase::CodeGen))
        continue;
      bool ModifiedByCodeGen = runCodeGeneration(*S, RI, *IslAst);
      if (ModifiedByCodeGen) {
        ModifiedIR = true;

        // For all regions, create new polly::Scop objects because the old ones
        // refere to invalidated LLVM-IR.
        // FIXME: Adds all SCoPs again to statistics
        Info.recompute();
      }
    }

    return ModifiedIR;
  }
};
} // namespace

StringRef polly::getPhaseName(PassPhase Phase) {
  switch (Phase) {
  case PassPhase::Prepare:
    return "prepare";
  case PassPhase::Detection:
    return "detect";
  case PassPhase::PrintDetect:
    return "print-detect";
  case PassPhase::DotScops:
    return "dot-scops";
  case PassPhase::DotScopsOnly:
    return "dot-scops-only";
  case PassPhase::ViewScops:
    return "view-scops";
  case PassPhase::ViewScopsOnly:
    return "view-scops-only";
  case PassPhase::ScopInfo:
    return "scops";
  case PassPhase::PrintScopInfo:
    return "print-scops";
  case PassPhase::Flatten:
    return "flatten";
  case PassPhase::Dependences:
    return "deps";
  case PassPhase::PrintDependences:
    return "print-deps";
  case PassPhase::ImportJScop:
    return "import-jscop";
  case PassPhase::Simplify0:
    return "simplify-0";
  case PassPhase::Optree:
    return "optree";
  case PassPhase::DeLICM:
    return "delicm";
  case PassPhase::Simplify1:
    return "simplify-1";
  case PassPhase::DeadCodeElimination:
    return "dce";
  case PassPhase::MaximumStaticExtension:
    return "mse";
  case PassPhase::PruneUnprofitable:
    return "prune";
  case PassPhase::Optimization:
    return "opt-isl"; // "opt" would conflict with the llvm executable
  case PassPhase::ExportJScop:
    return "export-jscop";
  case PassPhase::AstGen:
    return "ast";
  case PassPhase::CodeGen:
    return "codegen";
  default:
    llvm_unreachable("Unexpected phase");
  }
}

PassPhase polly::parsePhase(StringRef Name) {
  return StringSwitch<PassPhase>(Name)
      .Case("prepare", PassPhase::Prepare)
      .Case("detect", PassPhase::Detection)
      .Case("print-detect", PassPhase::PrintDetect)
      .Case("dot-scops", PassPhase::DotScops)
      .Case("dot-scops-only", PassPhase::DotScopsOnly)
      .Case("view-scops", PassPhase::ViewScops)
      .Case("view-scops-only", PassPhase::ViewScopsOnly)
      .Case("scops", PassPhase::ScopInfo)
      .Case("print-scops", PassPhase::PrintScopInfo)
      .Case("flatten", PassPhase::Flatten)
      .Case("deps", PassPhase::Dependences)
      .Case("print-deps", PassPhase::PrintDependences)
      .Case("import-jscop", PassPhase::ImportJScop)
      .Case("simplify-0", PassPhase::Simplify0)
      .Case("optree", PassPhase::Optree)
      .Case("delicm", PassPhase::DeLICM)
      .Case("simplify-1", PassPhase::Simplify1)
      .Case("dce", PassPhase::DeadCodeElimination)
      .Case("mse", PassPhase::MaximumStaticExtension)
      .Case("prune", PassPhase::PruneUnprofitable)
      .Case("opt-isl", PassPhase::Optimization)
      .Case("export-jscop", PassPhase::ExportJScop)
      .Case("ast", PassPhase::AstGen)
      .Case("codegen", PassPhase::CodeGen)
      .Default(PassPhase::None);
}

bool polly::dependsOnDependenceInfo(PassPhase Phase) {
  // Nothing before dep phase can depend on it
  if (static_cast<size_t>(Phase) <= static_cast<size_t>(PassPhase::Dependences))
    return false;

  switch (Phase) {
  case PassPhase::Simplify0:
  case PassPhase::Optree:
  case PassPhase::DeLICM:
  case PassPhase::Simplify1:
  case PassPhase::PruneUnprofitable:
  case PassPhase::ImportJScop:
  case PassPhase::ExportJScop:
  case PassPhase::AstGen: // transitively through codegen
  case PassPhase::CodeGen:
    return false;
  default:
    return true;
  }
}

void PollyPassOptions::enableEnd2End() {
  setPhaseEnabled(PassPhase::Detection);
  setPhaseEnabled(PassPhase::ScopInfo);
  setPhaseEnabled(PassPhase::Dependences);
  setPhaseEnabled(PassPhase::AstGen);
  setPhaseEnabled(PassPhase::CodeGen);
}

void PollyPassOptions::enableDefaultOpts() {
  setPhaseEnabled(PassPhase::Prepare);
  setPhaseEnabled(PassPhase::Simplify0);
  setPhaseEnabled(PassPhase::Optree);
  setPhaseEnabled(PassPhase::DeLICM);
  setPhaseEnabled(PassPhase::Simplify1);
  setPhaseEnabled(PassPhase::PruneUnprofitable);
  setPhaseEnabled(PassPhase::Optimization);
}

void PollyPassOptions::disableAfter(PassPhase Phase) {
  assert(Phase != PassPhase::None);
  for (PassPhase P : enum_seq_inclusive(Phase, PassPhase::PassPhaseLast)) {
    if (P == Phase)
      continue;
    setPhaseEnabled(P, false);
  }
}

Error PollyPassOptions::checkConsistency() const {
  for (PassPhase P : enum_seq_inclusive(PassPhase::PassPhaseFirst,
                                        PassPhase::PassPhaseLast)) {
    if (!isPhaseEnabled(P))
      continue;

    // Prepare and Detection have no requirements
    if (P == PassPhase::Prepare || P == PassPhase::Detection)
      continue;

    if (!isPhaseEnabled(PassPhase::Detection))
      return make_error<StringError>(
          formatv("'{0}' requires 'detect' to be enabled", getPhaseName(P))
              .str(),
          inconvertibleErrorCode());

    if (static_cast<size_t>(P) < static_cast<size_t>(PassPhase::ScopInfo))
      continue;

    if (!isPhaseEnabled(PassPhase::ScopInfo))
      return make_error<StringError>(
          formatv("'{0}' requires 'scops' to be enabled", getPhaseName(P))
              .str(),
          inconvertibleErrorCode());

    if (dependsOnDependenceInfo(P) && !isPhaseEnabled(PassPhase::Dependences))
      return make_error<StringError>(
          formatv("'{0}' requires 'deps' to be enabled", getPhaseName(P)).str(),
          inconvertibleErrorCode());
  }

  if (isPhaseEnabled(PassPhase::CodeGen) && !isPhaseEnabled(PassPhase::AstGen))
    return make_error<StringError>("'codegen' requires 'ast' to be enabled",
                                   inconvertibleErrorCode());

  return Error::success();
}

bool polly::runPollyPass(Function &F, FunctionAnalysisManager &FAM,
                         PollyPassOptions Opts) {
  return PhaseManager(F, FAM, std::move(Opts)).run();
}