/*========================== begin_copyright_notice ============================

Copyright (C) 2017-2021 Intel Corporation

SPDX-License-Identifier: MIT

============================= end_copyright_notice ===========================*/

#include "Compiler/Optimizer/OpenCLPasses/DeviceEnqueueFuncs/DeviceEnqueue.hpp"
#include "Compiler/Optimizer/OCLBIUtils.h"
#include "Compiler/IGCPassSupport.h"
#include "Probe/Assertion.h"

using namespace llvm;
using namespace IGC;

// Register pass to igc-opt
#define PASS_FLAG "igc-device-enqueue-func-analysis"
#define PASS_DESCRIPTION "Analyzes device enqueue functions"
#define PASS_CFG_ONLY false
#define PASS_ANALYSIS false
IGC_INITIALIZE_PASS_BEGIN(DeviceEnqueueFuncsAnalysisLPM, PASS_FLAG, PASS_DESCRIPTION, PASS_CFG_ONLY, PASS_ANALYSIS)
IGC_INITIALIZE_PASS_DEPENDENCY(MetaDataUtilsWrapper)
IGC_INITIALIZE_PASS_DEPENDENCY(CodeGenContextWrapper)
IGC_INITIALIZE_PASS_END(DeviceEnqueueFuncsAnalysisLPM, PASS_FLAG, PASS_DESCRIPTION, PASS_CFG_ONLY, PASS_ANALYSIS)

// Register pass to igc-opt
#define PASS_FLAG2 "igc-device-enqueue-func-resolution"
#define PASS_DESCRIPTION2 "Resolve device enqueue functions"
#define PASS_CFG_ONLY2 false
#define PASS_ANALYSIS2 false
IGC_INITIALIZE_PASS_BEGIN(DeviceEnqueueFuncsResolutionLPM, PASS_FLAG2, PASS_DESCRIPTION2, PASS_CFG_ONLY2,
                          PASS_ANALYSIS2)
IGC_INITIALIZE_PASS_DEPENDENCY(MetaDataUtilsWrapper)
IGC_INITIALIZE_PASS_DEPENDENCY(CodeGenContextWrapper)
IGC_INITIALIZE_PASS_END(DeviceEnqueueFuncsResolutionLPM, PASS_FLAG2, PASS_DESCRIPTION2, PASS_CFG_ONLY2, PASS_ANALYSIS2)

char DeviceEnqueueFuncsAnalysisLPM::ID = 0;

const llvm::StringRef GET_DEFAULT_DEVICE_QUEUE = "__builtin_IB_get_default_device_queue";
const llvm::StringRef GET_EVENT_POOL = "__builtin_IB_get_event_pool";
const llvm::StringRef GET_MAX_WORKGROUP_SIZE = "__builtin_IB_get_max_workgroup_size";
const llvm::StringRef GET_PARENT_EVENT = "__builtin_IB_get_parent_event";
const llvm::StringRef GET_PREFERED_WORKGROUP_MULTIPLE = "__builtin_IB_get_prefered_workgroup_multiple";
const llvm::StringRef GET_OBJECT_ID = "__builtin_IB_get_object_id";
const llvm::StringRef GET_BLOCK_SIMD_SIZE = "__builtin_IB_get_block_simd_size";

DeviceEnqueueFuncsAnalysisLPM::DeviceEnqueueFuncsAnalysisLPM() : ModulePass(ID) {
  initializeDeviceEnqueueFuncsAnalysisLPMPass(*PassRegistry::getPassRegistry());
}

bool DeviceEnqueueFuncsAnalysis::run(Module &M, IGCMD::MetaDataUtils *pMdUtils, IGC::ModuleMetaData *pModMD) {
  bool changed = false;
  m_pMDUtils = pMdUtils;
  m_modMD = pModMD;
  // Run on all functions defined in this module
  for (Module::iterator I = M.begin(), E = M.end(); I != E; ++I) {
    Function *pFunc = &(*I);
    if (pFunc->isDeclaration()) {
      continue;
    }
    if (runOnFunction(*pFunc)) {
      changed = true;
    }
  }

  if (changed)
    m_pMDUtils->save(M.getContext());

  return changed;
}

bool DeviceEnqueueFuncsAnalysis::runOnFunction(Function &F) {

  m_newImplicitArgs.clear();
  m_newNumberedImplicitArgs.clear();

  // Visit the function
  visit(F);

  ImplicitArgs::addImplicitArgs(F, m_newImplicitArgs, m_pMDUtils, m_modMD);
  ImplicitArgs::addNumberedArgs(F, m_newNumberedImplicitArgs, m_pMDUtils, m_modMD);

  return m_hasDeviceEnqueue;
}

void DeviceEnqueueFuncsAnalysis::visitCallInst(CallInst &CI) {
  if (!CI.getCalledFunction()) {
    return;
  }

  StringRef funcName = CI.getCalledFunction()->getName();

  // Check for OpenCL image dimension function calls
  ImplicitArg::ArgType argType = ImplicitArg::NUM_IMPLICIT_ARGS;

  if (funcName == GET_DEFAULT_DEVICE_QUEUE) {
    argType = ImplicitArg::DEVICE_ENQUEUE_DEFAULT_DEVICE_QUEUE;
  } else if (funcName == GET_EVENT_POOL) {
    argType = ImplicitArg::DEVICE_ENQUEUE_EVENT_POOL;
  } else if (funcName == GET_MAX_WORKGROUP_SIZE) {
    argType = ImplicitArg::DEVICE_ENQUEUE_MAX_WORKGROUP_SIZE;
  } else if (funcName == GET_PARENT_EVENT) {
    argType = ImplicitArg::DEVICE_ENQUEUE_PARENT_EVENT;
  } else if (funcName == GET_PREFERED_WORKGROUP_MULTIPLE) {
    argType = ImplicitArg::DEVICE_ENQUEUE_PREFERED_WORKGROUP_MULTIPLE;
  } else if (funcName == GET_OBJECT_ID) {
    // Extract the arg num and add it to the appropriate data structure
    IGC_ASSERT_MESSAGE(IGCLLVM::getNumArgOperands(&CI) == 1,
                       "get_object_id function is expected to have only one argument");

    // We support only compile-time constants as arguments of get_object_id()
    ConstantInt *callArg = dyn_cast<ConstantInt>(CI.getArgOperand(0));
    IGC_ASSERT_MESSAGE(callArg != NULL, "get_object_id function is expected to have only conatnt argument");

    m_newNumberedImplicitArgs[ImplicitArg::GET_OBJECT_ID].insert((int)callArg->getZExtValue());
    m_hasDeviceEnqueue = true;
  } else if (funcName == GET_BLOCK_SIMD_SIZE) {
    // Extract the arg num and add it to the appropriate data structure
    IGC_ASSERT_MESSAGE(IGCLLVM::getNumArgOperands(&CI) == 1,
                       "get_block_simd_size function is expected to have only one argument");

    // We support only compile-time constants as arguments of get_object_id()
    ConstantInt *callArg = dyn_cast<ConstantInt>(CI.getArgOperand(0));
    IGC_ASSERT_MESSAGE(callArg != NULL, "get_block_simd_size function is expected to have only constant argument");

    m_newNumberedImplicitArgs[ImplicitArg::GET_BLOCK_SIMD_SIZE].insert((int)callArg->getZExtValue());
    m_hasDeviceEnqueue = true;
  }

  if (argType != ImplicitArg::NUM_IMPLICIT_ARGS) {

    if (std::find(m_newImplicitArgs.begin(), m_newImplicitArgs.end(), argType) == m_newImplicitArgs.end()) {
      m_newImplicitArgs.push_back(argType);
    }

    m_hasDeviceEnqueue = true;
  }
}

char DeviceEnqueueFuncsResolutionLPM::ID = 0;

DeviceEnqueueFuncsResolutionLPM::DeviceEnqueueFuncsResolutionLPM() : FunctionPass(ID) {
  initializeDeviceEnqueueFuncsResolutionLPMPass(*PassRegistry::getPassRegistry());
}

bool DeviceEnqueueFuncsResolution::runOnFunction(Function &F, IGCMD::MetaDataUtils *pMdUtils,
                                                 IGC::ModuleMetaData *pModMD) {
  m_Changed = false;

  m_implicitArgs = ImplicitArgs(F, pMdUtils, pModMD);

  visit(F);

  return m_Changed;
}

void DeviceEnqueueFuncsResolution::visitCallInst(CallInst &CI) {
  if (!CI.getCalledFunction()) {
    return;
  }

  StringRef funcName = CI.getCalledFunction()->getName();
  Function &F = *(CI.getParent()->getParent());

  // Check for OpenCL image dimension function calls
  Value *enqueueResource = NULL;

  if (funcName == GET_DEFAULT_DEVICE_QUEUE) {
    enqueueResource = m_implicitArgs.getImplicitArg(F, ImplicitArg::DEVICE_ENQUEUE_DEFAULT_DEVICE_QUEUE);
    IGC_ASSERT(enqueueResource != NULL);
  } else if (funcName == GET_EVENT_POOL) {
    enqueueResource = m_implicitArgs.getImplicitArg(F, ImplicitArg::DEVICE_ENQUEUE_EVENT_POOL);
    IGC_ASSERT(enqueueResource != NULL);
  } else if (funcName == GET_MAX_WORKGROUP_SIZE) {
    enqueueResource = m_implicitArgs.getImplicitArg(F, ImplicitArg::DEVICE_ENQUEUE_MAX_WORKGROUP_SIZE);
    IGC_ASSERT(enqueueResource != NULL);
  } else if (funcName == GET_PARENT_EVENT) {
    enqueueResource = m_implicitArgs.getImplicitArg(F, ImplicitArg::DEVICE_ENQUEUE_PARENT_EVENT);
    IGC_ASSERT(enqueueResource != NULL);
  } else if (funcName == GET_PREFERED_WORKGROUP_MULTIPLE) {
    enqueueResource = m_implicitArgs.getImplicitArg(F, ImplicitArg::DEVICE_ENQUEUE_PREFERED_WORKGROUP_MULTIPLE);
    IGC_ASSERT(enqueueResource != NULL);
  } else if (funcName == GET_OBJECT_ID) {
    enqueueResource = m_implicitArgs.getNumberedImplicitArg(
        F, ImplicitArg::GET_OBJECT_ID, (int)(cast<ConstantInt>(CI.getArgOperand(0))->getZExtValue()));
    IGC_ASSERT(enqueueResource != NULL);
  } else if (funcName == GET_BLOCK_SIMD_SIZE) {
    enqueueResource = m_implicitArgs.getNumberedImplicitArg(
        F, ImplicitArg::GET_BLOCK_SIMD_SIZE, (int)(cast<ConstantInt>(CI.getArgOperand(0))->getZExtValue()));
    IGC_ASSERT(enqueueResource != NULL);
  }

  if (enqueueResource != NULL) {
    CI.replaceAllUsesWith(enqueueResource);
    CI.eraseFromParent();

    m_Changed = true;
  }
}

#if LLVM_VERSION_MAJOR >= 16
PreservedAnalyses DeviceEnqueueFuncsAnalysisNPM::run(Module &M, ModuleAnalysisManager &AM) {
  bool changed = DeviceEnqueueFuncsAnalysis().run(M, AM.getResult<MetaDataUtilsAnalysis>(M).MdUtils,
                                                  AM.getResult<MetaDataUtilsAnalysis>(M).ModMD);
  return changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
}

PreservedAnalyses DeviceEnqueueFuncsResolutionNPM::run(Module &M, ModuleAnalysisManager &AM) {
  auto *pMdUtils = AM.getResult<MetaDataUtilsAnalysis>(M).MdUtils;
  auto *pModMD = AM.getResult<MetaDataUtilsAnalysis>(M).ModMD;
  DeviceEnqueueFuncsResolution impl;
  bool changed = false;
  for (Function &F : M) {
    if (F.isDeclaration())
      continue;
    changed |= impl.runOnFunction(F, pMdUtils, pModMD);
  }
  return changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
}
#endif // LLVM_VERSION_MAJOR >= 16
