#include "ir_legalize.h"

namespace dxbc_spv::ir {

void legalizeIr(ir::Builder& builder, const CompileOptions& options) {
  /* Convert to structured control flow */
  ir::ConvertControlFlowPass::runPass(builder);
  ir::CleanupControlFlowPass::runPass(builder);

  ir::FunctionCleanupPass::runResolveSharedTempPass(builder);
  ir::SsaConstructionPass::runPass(builder);

  /* Lower min-precision types */
  ir::LowerMin16Pass::runPass(builder, options.min16Options);

  /* Scalarize and eliminate redundant composite / consume instructions */
  ir::ScalarizePass::runPass(builder, options.scalarizeOptions);

  while (true) {
    bool progress = false;
    progress |= ir::LowerConsumePass::runResolveCastChainsPass(builder);
    progress |= ir::ScalarizePass::runResolveRedundantCompositesPass(builder);

    if (!progress)
      break;
  }

  /* Optimize various scratch array patterns since drivers tend to have issues
   * in this area. This may not be ideal since we haven't run CSE yet, but in
   * practice this should detect problematic patterns. Must be run before consume
   * instructions are lowered, and should be run before constant buffer types are
   * inferred to not disrupt array optimizations. */
  if (ir::CleanupScratchPass::runPass(builder, options.scratchOptions)) {
    bool progress;

    do {
      progress = false;
      progress |= ir::ScalarizePass::runResolveRedundantCompositesPass(builder);
      progress |= ir::LowerConsumePass::runResolveCastChainsPass(builder);
    } while (progress);
  }

  /* Run type propagation for expressions */
  ir::PropagateTypesPass::runPass(builder);

  /* Run type propagation for resources */
  ir::PropagateResourceTypesPass::runPass(builder, options.resourceOptions);
  ir::ConvertBufferKindPass::runPass(builder, options.bufferOptions);

  /* Do a first round of removing unused code that may have been
   * generated by previous passes */
  ir::RemoveUnusedPass::runPass(builder);

  /* Lower ConsumeAs instructions to actual type casts and conversions */
  ir::LowerConsumePass::runLowerConsumePass(builder);

  /* Lower instructions that the target backend does not natively understand */
  ir::ArithmeticPass::runPropagateInvariancePass(builder);
  ir::ArithmeticPass::runEarlyLoweringPasses(builder, options.arithmeticOptions);

  /* Run some basic code transforms */
  bool derivativePassRun = false;

  while (true) {
    bool progress = false;

    progress |= ir::ArithmeticPass::runPass(builder, options.arithmeticOptions);
    progress |= ir::CsePass::runPass(builder, options.cseOptions);
    progress |= ir::CleanupControlFlowPass::runPass(builder);
    progress |= ir::SsaConstructionPass::runResolveTrivialPhiPass(builder);

    /* Derivative hoisting may feed into CSE and control flow */
    if (!progress && !std::exchange(derivativePassRun, true)) {
      ir::SsaConstructionPass::runInsertExitPhiPass(builder);

      progress |= ir::DescriptorIndexingPass::runPass(builder, options.descriptorIndexing);

      /* In some rare cases there may be feedback loops between derivative
       * hoisting and CSE. Resolve these right away to avoid having to run
       * the derivative pass more than once. */
      while (ir::DerivativePass::runPass(builder, options.derivativeOptions)) {
        progress = true;

        if (!ir::CsePass::runPass(builder, options.cseOptions))
          break;
      }

      ir::SsaConstructionPass::runResolveTrivialPhiPass(builder);
    }

    if (!progress)
      break;

    ir::RemoveUnusedPass::runPass(builder);
  }

  ir::ArithmeticPass::runLateLoweringPasses(builder, options.arithmeticOptions);

  /* Remove unused function parameters and function returns. */
  while (ir::FunctionCleanupPass::runRemoveParameterPass(builder))
    continue;

  /* Do a last round of dead code elimination and also get rid of any FP mode
   * declarations for unused FP modes since those types may be unsupported. */
  ir::RemoveUnusedPass::runPass(builder);
  ir::RemoveUnusedPass::runRemoveUnusedFloatModePass(builder);

  /* Run synchronization fix-up pass last, as this somewhat relies on CSE. */
  ir::SyncPass::runPass(builder, options.syncOptions);
}

}
