///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// DxilDeleteRedundantDebugValues.cpp                                        //
// Copyright (C) Microsoft Corporation. All rights reserved.                 //
// This file is distributed under the University of Illinois Open Source     //
// License. See LICENSE.TXT for details.                                     //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
//
// Removes as many dbg.value's as possible:
//
// 1. Search for all scopes (and their parent scopes) that have any real
// (non-debug)
//    instructions at all.
//
// 2. For each dbg.value, if it's refering to a variable from a scope not in
//    the set of scopes from step 1, then delete it.
//
// 3. In any contiguous series of dbg.value instructions, if there are
// dbg.value's
//    that point to the same variable+fragment, then delete all but the last
//    one, since it would be the only authentic mapping for that variable
//    fragment.

#include "dxc/HLSL/DxilGenerationPass.h"
#include "llvm/IR/DebugInfo.h"
#include "llvm/IR/DebugInfoMetadata.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Module.h"
#include "llvm/Pass.h"

#include <unordered_set>

using namespace llvm;

namespace {
class DxilDeleteRedundantDebugValues : public ModulePass {
public:
  static char ID;

  explicit DxilDeleteRedundantDebugValues() : ModulePass(ID) {
    initializeDxilDeleteRedundantDebugValuesPass(
        *PassRegistry::getPassRegistry());
  }

  bool runOnModule(Module &M) override;
};
char DxilDeleteRedundantDebugValues::ID;
} // namespace

bool DxilDeleteRedundantDebugValues::runOnModule(Module &M) {
  if (!llvm::hasDebugInfo(M))
    return false;

  bool Changed = false;

  std::unordered_set<DILocalScope *> SeenScopes;
  typedef std::pair<DILocalVariable *, DIExpression *> VarPair;
  SmallDenseMap<VarPair, DbgValueInst *> SeenVar;

  for (Function &F : M) {
    SeenScopes.clear();
    SeenVar.clear();

    // Collect a set of all scopes that are attached to non-debug instructions
    // There's no need to keep debug info for any variables in any scopes that
    // don't have any real instructions anyway.
    for (BasicBlock &BB : F) {
      for (Instruction &I : BB) {
        if (isa<DbgInfoIntrinsic>(I))
          continue;
        DebugLoc DL = I.getDebugLoc();
        if (!DL)
          continue;

        DILocalScope *Scope = cast_or_null<DILocalScope>(DL.getScope());
        if (!Scope)
          continue;

        while (Scope) {
          SeenScopes.insert(Scope);
          if (DILexicalBlockBase *LB = dyn_cast<DILexicalBlockBase>(Scope)) {
            Scope = LB->getScope();
          } else {
            Scope = nullptr;
          }
        }
      }
    }

    for (BasicBlock &BB : F) {
      for (auto it = BB.begin(), end = BB.end(); it != end;) {
        Instruction &I = *(it++);
        if (!isa<DbgInfoIntrinsic>(I)) {
          SeenVar.clear();
          continue;
        }

        DbgValueInst *DI = dyn_cast<DbgValueInst>(&I);
        if (!DI)
          continue;
        DILocalVariable *Var = DI->getVariable();
        DIExpression *Expr = DI->getExpression();
        VarPair Pair = VarPair(Var, Expr);

        if (!SeenScopes.count(Var->getScope())) {
          Changed = true;
          DI->eraseFromParent();
          continue;
        }

        auto findIt = SeenVar.find(Pair);
        if (findIt != SeenVar.end()) {
          findIt->second->eraseFromParent();
          findIt->second = DI;
          Changed = true;
        } else {
          SeenVar[Pair] = DI;
        }
      }
    }
  }

  return Changed;
}

ModulePass *llvm::createDxilDeleteRedundantDebugValuesPass() {
  return new DxilDeleteRedundantDebugValues();
}

INITIALIZE_PASS(DxilDeleteRedundantDebugValues,
                "dxil-delete-redundant-debug-values",
                "Dxil Delete Redundant Debug Values", false, false)
