/*
 * Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
 *
 * SPDX-License-Identifier: MIT
 */

#include "platform/runtime.hpp"
#include "platform/program.hpp"
#include "platform/ndrange.hpp"
#include "platform/kernel_init.hpp"
#include "devkernel.hpp"
#include "utils/macros.hpp"
#include "utils/options.hpp"
#include "comgrctx.hpp"

#include <array>
#include <map>
#include <string>
#include <sstream>

namespace amd::device {

// ================================================================================================
static constexpr clk_value_type_t ClkValueMapType[6][6] = {
    {T_CHAR, T_CHAR2, T_CHAR3, T_CHAR4, T_CHAR8, T_CHAR16},
    {T_SHORT, T_SHORT2, T_SHORT3, T_SHORT4, T_SHORT8, T_SHORT16},
    {T_INT, T_INT2, T_INT3, T_INT4, T_INT8, T_INT16},
    {T_LONG, T_LONG2, T_LONG3, T_LONG4, T_LONG8, T_LONG16},
    {T_FLOAT, T_FLOAT2, T_FLOAT3, T_FLOAT4, T_FLOAT8, T_FLOAT16},
    {T_DOUBLE, T_DOUBLE2, T_DOUBLE3, T_DOUBLE4, T_DOUBLE8, T_DOUBLE16},
};

// ================================================================================================
amd_comgr_status_t getMetaBuf(const amd_comgr_metadata_node_t meta,
                   std::string* str) {
  size_t size = 0;
  amd_comgr_status_t status = amd::Comgr::get_metadata_string(meta, &size, NULL);

  if (status == AMD_COMGR_STATUS_SUCCESS) {
    str->resize(size-1);    // minus one to discount the null character
    status = amd::Comgr::get_metadata_string(meta, &size, &((*str)[0]));
  }

  return status;
}

// ================================================================================================
bool getValueFromIsaMeta(const std::string& isa, const char* key, std::string& retValue) {
  amd_comgr_metadata_node_t isaMeta;

  amd_comgr_status_t status = amd::Comgr::get_isa_metadata(isa.c_str(), &isaMeta);

  if (status != AMD_COMGR_STATUS_SUCCESS) {
    ClPrint(amd::LOG_ERROR, amd::LOG_INIT, "getIsaMeta(%s) failed!", isa.c_str());
    return false;
  }

  amd_comgr_metadata_node_t valMeta;
  size_t size = 0;
  status = amd::Comgr::metadata_lookup(isaMeta, key, &valMeta);
  if (status == AMD_COMGR_STATUS_SUCCESS) {
    status = amd::Comgr::get_metadata_string(valMeta, &size, NULL);
  }
  if (status == AMD_COMGR_STATUS_SUCCESS) {
    retValue.resize(size - 1);
    status = amd::Comgr::get_metadata_string(valMeta, &size, &(retValue[0]));
  }

  if (status == AMD_COMGR_STATUS_SUCCESS) {
    status = amd::Comgr::destroy_metadata(valMeta);
  }
  amd::Comgr::destroy_metadata(isaMeta);
  return (status == AMD_COMGR_STATUS_SUCCESS) ? true : false;
}

// ================================================================================================
static amd_comgr_status_t populateArgs(const amd_comgr_metadata_node_t key,
                                       const amd_comgr_metadata_node_t value, void* data) {
  amd_comgr_status_t status;
  amd_comgr_metadata_kind_t kind;
  std::string buf;

  // get the key of the argument field
  size_t size = 0;
  status = amd::Comgr::get_metadata_kind(key, &kind);
  if (kind == AMD_COMGR_METADATA_KIND_STRING && status == AMD_COMGR_STATUS_SUCCESS) {
    status = getMetaBuf(key, &buf);
  }

  if (status != AMD_COMGR_STATUS_SUCCESS) {
    return AMD_COMGR_STATUS_ERROR;
  }

  ArgField itArgField = amd::Kernel::FindValue<ArgField>(amd::Kernel::kArgFieldMap, buf);
  if (itArgField == ArgField::MaxSize) {
    return AMD_COMGR_STATUS_ERROR;
  }

  // get the value of the argument field
  status = getMetaBuf(value, &buf);

  amd::KernelParameterDescriptor* lcArg = static_cast<amd::KernelParameterDescriptor*>(data);

  switch (itArgField) {
    case ArgField::Name:
      lcArg->name_ = buf;
      break;
    case ArgField::TypeName:
      lcArg->typeName_ = buf;
      break;
    case ArgField::Size:
      lcArg->size_ = atoi(buf.c_str());
      break;
    case ArgField::Align:
      lcArg->alignment_ = atoi(buf.c_str());
      break;
    case ArgField::ValueKind: {
      amd::KernelParameterDescriptor::Desc itValueKind =
          amd::Kernel::FindValue<amd::KernelParameterDescriptor::Desc>(amd::Kernel::kArgValueKind,
                                                                       buf);
      if (itValueKind == amd::KernelParameterDescriptor::Desc::MaxSize) {
        lcArg->info_.hidden_ = true;
        return AMD_COMGR_STATUS_ERROR;
      }
      lcArg->info_.oclObject_ = itValueKind;
      switch (lcArg->info_.oclObject_) {
        case amd::KernelParameterDescriptor::MemoryObject:
          if (buf.compare("DynamicSharedPointer") == 0) {
            lcArg->info_.shared_ = true;
          }
          break;
        case amd::KernelParameterDescriptor::HiddenGlobalOffsetX:
        case amd::KernelParameterDescriptor::HiddenGlobalOffsetY:
        case amd::KernelParameterDescriptor::HiddenGlobalOffsetZ:
        case amd::KernelParameterDescriptor::HiddenPrintfBuffer:
        case amd::KernelParameterDescriptor::HiddenHostcallBuffer:
        case amd::KernelParameterDescriptor::HiddenDefaultQueue:
        case amd::KernelParameterDescriptor::HiddenCompletionAction:
        case amd::KernelParameterDescriptor::HiddenMultiGridSync:
        case amd::KernelParameterDescriptor::HiddenDynamicLdsSize:
        case amd::KernelParameterDescriptor::HiddenNone:
          lcArg->info_.hidden_ = true;
          break;
      }
    } break;
    case ArgField::PointeeAlign:
      lcArg->info_.arrayIndex_ = atoi(buf.c_str());
      break;
    case ArgField::AddrSpaceQual: {
      cl_int itAddrSpaceQual = amd::Kernel::FindValue(amd::Kernel::kArgAddrSpaceQual, buf);
      if (itAddrSpaceQual == static_cast<cl_int>(0)) {
        return AMD_COMGR_STATUS_ERROR;
      }
      lcArg->addressQualifier_ = itAddrSpaceQual;
    } break;
    case ArgField::AccQual: {
      cl_int itAccQual = amd::Kernel::FindValue(amd::Kernel::kArgAccQual, buf);
      if (itAccQual == static_cast<cl_int>(0)) {
        return AMD_COMGR_STATUS_ERROR;
      }
      lcArg->accessQualifier_ = itAccQual;
      lcArg->info_.readOnly_ =
          (lcArg->accessQualifier_ == CL_KERNEL_ARG_ACCESS_READ_ONLY) ? true : false;
    } break;
    case ArgField::ActualAccQual: {
      cl_int itAccQual = amd::Kernel::FindValue(amd::Kernel::kArgAccQual, buf);
      if (itAccQual == static_cast<cl_int>(0)) {
        return AMD_COMGR_STATUS_ERROR;
      }
      // lcArg->mActualAccQual = itAccQual->second;
    } break;
    case ArgField::IsConst:
      lcArg->typeQualifier_ |= (buf.compare("true") == 0) ? CL_KERNEL_ARG_TYPE_CONST : 0;
      break;
    case ArgField::IsRestrict:
      lcArg->typeQualifier_ |= (buf.compare("true") == 0) ? CL_KERNEL_ARG_TYPE_RESTRICT : 0;
      break;
    case ArgField::IsVolatile:
      lcArg->typeQualifier_ |= (buf.compare("true") == 0) ? CL_KERNEL_ARG_TYPE_VOLATILE : 0;
      break;
    case ArgField::IsPipe:
      lcArg->typeQualifier_ |= (buf.compare("true") == 0) ? CL_KERNEL_ARG_TYPE_PIPE : 0;
      break;
    default:
      return AMD_COMGR_STATUS_ERROR;
  }
  return AMD_COMGR_STATUS_SUCCESS;
}

static amd_comgr_status_t populateAttrs(const amd_comgr_metadata_node_t key,
                                        const amd_comgr_metadata_node_t value, void* data) {
  amd_comgr_status_t status;
  amd_comgr_metadata_kind_t kind;
  size_t size = 0;
  std::string buf;

  // get the key of the argument field
  status = amd::Comgr::get_metadata_kind(key, &kind);
  if (kind == AMD_COMGR_METADATA_KIND_STRING && status == AMD_COMGR_STATUS_SUCCESS) {
    status = getMetaBuf(key, &buf);
  }

  if (status != AMD_COMGR_STATUS_SUCCESS) {
    return AMD_COMGR_STATUS_ERROR;
  }

  AttrField itAttrField = amd::Kernel::FindValue<AttrField>(amd::Kernel::kAttrFieldMap, buf);
  if (itAttrField == AttrField::MaxSize) {
    return AMD_COMGR_STATUS_ERROR;
  }

  device::Kernel* kernel = static_cast<device::Kernel*>(data);
  switch (itAttrField) {
    case AttrField::ReqdWorkGroupSize: {
      status = amd::Comgr::get_metadata_list_size(value, &size);
      if (size == 3 && status == AMD_COMGR_STATUS_SUCCESS) {
        std::array<size_t, 3> wrkSize;
        for (size_t i = 0; i < size && status == AMD_COMGR_STATUS_SUCCESS; i++) {
          amd_comgr_metadata_node_t workgroupSize;
          status = amd::Comgr::index_list_metadata(value, i, &workgroupSize);

          if (status == AMD_COMGR_STATUS_SUCCESS &&
              getMetaBuf(workgroupSize, &buf) == AMD_COMGR_STATUS_SUCCESS) {
            wrkSize[i] = atoi(buf.c_str());
          }
          amd::Comgr::destroy_metadata(workgroupSize);
        }
        if (!wrkSize.empty()) {
          kernel->setReqdWorkGroupSize(wrkSize[0], wrkSize[1], wrkSize[2]);
        }
      }
    } break;
    case AttrField::WorkGroupSizeHint: {
      status = amd::Comgr::get_metadata_list_size(value, &size);
      if (status == AMD_COMGR_STATUS_SUCCESS && size == 3) {
        std::array<size_t, 3> hintSize;
        for (size_t i = 0; i < size && status == AMD_COMGR_STATUS_SUCCESS; i++) {
          amd_comgr_metadata_node_t workgroupSizeHint;
          status = amd::Comgr::index_list_metadata(value, i, &workgroupSizeHint);

          if (status == AMD_COMGR_STATUS_SUCCESS &&
              getMetaBuf(workgroupSizeHint, &buf) == AMD_COMGR_STATUS_SUCCESS) {
            hintSize[i] = atoi(buf.c_str());
          }
          amd::Comgr::destroy_metadata(workgroupSizeHint);
        }
        if (!hintSize.empty()) {
          kernel->setWorkGroupSizeHint(hintSize[0], hintSize[1], hintSize[2]);
        }
      }
    } break;
    case AttrField::VecTypeHint:
      if (getMetaBuf(value, &buf) == AMD_COMGR_STATUS_SUCCESS) {
        kernel->setVecTypeHint(buf);
      }
      break;
    case AttrField::RuntimeHandle:
      if (getMetaBuf(value, &buf) == AMD_COMGR_STATUS_SUCCESS) {
        kernel->setRuntimeHandle(buf);
      }
      break;
    default:
      return AMD_COMGR_STATUS_ERROR;
  }

  return status;
}

static amd_comgr_status_t populateCodeProps(const amd_comgr_metadata_node_t key,
                                            const amd_comgr_metadata_node_t value, void* data) {
  amd_comgr_status_t status;
  amd_comgr_metadata_kind_t kind;
  std::string buf;

  // get the key of the argument field
  status = amd::Comgr::get_metadata_kind(key, &kind);
  if (kind == AMD_COMGR_METADATA_KIND_STRING && status == AMD_COMGR_STATUS_SUCCESS) {
    status = getMetaBuf(key, &buf);
  }

  if (status != AMD_COMGR_STATUS_SUCCESS) {
    return AMD_COMGR_STATUS_ERROR;
  }

  CodePropField itCodePropField =
      amd::Kernel::FindValue<CodePropField>(amd::Kernel::kCodePropFieldMap, buf);
  if (itCodePropField == CodePropField::MaxSize) {
    return AMD_COMGR_STATUS_ERROR;
  }

  // get the value of the argument field
  if (status == AMD_COMGR_STATUS_SUCCESS) {
    status = getMetaBuf(value, &buf);
  }

  device::Kernel* kernel = static_cast<device::Kernel*>(data);
  switch (itCodePropField) {
    case CodePropField::KernargSegmentSize:
      kernel->SetKernargSegmentByteSize(atoi(buf.c_str()));
      break;
    case CodePropField::GroupSegmentFixedSize:
      kernel->SetWorkgroupGroupSegmentByteSize(atoi(buf.c_str()));
      break;
    case CodePropField::PrivateSegmentFixedSize:
      kernel->SetWorkitemPrivateSegmentByteSize(atoi(buf.c_str()));
      break;
    case CodePropField::KernargSegmentAlign:
      kernel->SetKernargSegmentAlignment(atoi(buf.c_str()));
      break;
    case CodePropField::WavefrontSize:
      kernel->workGroupInfo()->wavefrontSize_ = atoi(buf.c_str());
      break;
    case CodePropField::NumSGPRs:
      kernel->workGroupInfo()->usedSGPRs_ = atoi(buf.c_str());
      break;
    case CodePropField::NumVGPRs:
      kernel->workGroupInfo()->usedVGPRs_ = atoi(buf.c_str());
      break;
    case CodePropField::MaxFlatWorkGroupSize:
      kernel->workGroupInfo()->size_ = atoi(buf.c_str());
      break;
    case CodePropField::IsDynamicCallStack: {
      size_t mIsDynamicCallStack = (buf.compare("true") == 0);
    } break;
    case CodePropField::IsXNACKEnabled: {
      size_t mIsXNACKEnabled = (buf.compare("true") == 0);
    } break;
    case CodePropField::NumSpilledSGPRs: {
      size_t mNumSpilledSGPRs = atoi(buf.c_str());
    } break;
    case CodePropField::NumSpilledVGPRs: {
      size_t mNumSpilledVGPRs = atoi(buf.c_str());
    } break;
    default:
      return AMD_COMGR_STATUS_ERROR;
  }
  return AMD_COMGR_STATUS_SUCCESS;
}

static amd_comgr_status_t populateArgsV3(const amd_comgr_metadata_node_t key,
                                         const amd_comgr_metadata_node_t value, void* data) {
  amd_comgr_status_t status;
  amd_comgr_metadata_kind_t kind;
  std::string buf;

  // get the key of the argument field
  size_t size = 0;
  status = amd::Comgr::get_metadata_kind(key, &kind);
  if (kind == AMD_COMGR_METADATA_KIND_STRING && status == AMD_COMGR_STATUS_SUCCESS) {
    status = getMetaBuf(key, &buf);
  }

  if (status != AMD_COMGR_STATUS_SUCCESS) {
    return AMD_COMGR_STATUS_ERROR;
  }

  ArgField itArgField = amd::Kernel::FindValue<ArgField>(amd::Kernel::kArgFieldMapV3, buf);
  if (itArgField == ArgField::MaxSize) {
    return AMD_COMGR_STATUS_ERROR;
  }

  // get the value of the argument field
  status = getMetaBuf(value, &buf);

  amd::KernelParameterDescriptor* lcArg = static_cast<amd::KernelParameterDescriptor*>(data);

  switch (itArgField) {
    case ArgField::Name:
      lcArg->name_ = buf;
      break;
    case ArgField::TypeName:
      lcArg->typeName_ = buf;
      break;
    case ArgField::Size:
      lcArg->size_ = atoi(buf.c_str());
      break;
    case ArgField::Offset:
      lcArg->offset_ = atoi(buf.c_str());
      break;
    case ArgField::ValueKind: {
      amd::KernelParameterDescriptor::Desc itArgValue =
          amd::Kernel::FindValue<amd::KernelParameterDescriptor::Desc>(amd::Kernel::kArgValueKindV3,
                                                                       buf);
      if (itArgValue == amd::KernelParameterDescriptor::MaxSize) {
        LogPrintfError("Unknown Kernel arg metadata: %s", buf.c_str());
        LogError("This may be due to running HIP app that requires a new HIP runtime version");
        LogError("Please update the display driver");
        return AMD_COMGR_STATUS_ERROR;
      }
      lcArg->info_.oclObject_ = itArgValue;
      if (lcArg->info_.oclObject_ == amd::KernelParameterDescriptor::MemoryObject) {
        if (buf.compare("dynamic_shared_pointer") == 0) {
          lcArg->info_.shared_ = true;
        }
      } else if ((lcArg->info_.oclObject_ >= amd::KernelParameterDescriptor::HiddenNone) &&
                 (lcArg->info_.oclObject_ < amd::KernelParameterDescriptor::HiddenLast)) {
        lcArg->info_.hidden_ = true;
      }
    } break;
    case ArgField::PointeeAlign:
      lcArg->info_.arrayIndex_ = atoi(buf.c_str());
      break;
    case ArgField::AddrSpaceQual: {
      cl_int itAddrSpaceQual = amd::Kernel::FindValue(amd::Kernel::kArgAddrSpaceQualV3, buf);
      if (itAddrSpaceQual == static_cast<cl_int>(0)) {
        return AMD_COMGR_STATUS_ERROR;
      }
      lcArg->addressQualifier_ = itAddrSpaceQual;
    } break;
    case ArgField::AccQual: {
      cl_int itAccQual = amd::Kernel::FindValue(amd::Kernel::kArgAccQualV3, buf);
      if (itAccQual == static_cast<cl_int>(0)) {
        return AMD_COMGR_STATUS_ERROR;
      }
      lcArg->accessQualifier_ = itAccQual;
      if (!lcArg->info_.isReadOnlyByCompiler) {
        lcArg->info_.readOnly_ =
            (lcArg->accessQualifier_ == CL_KERNEL_ARG_ACCESS_READ_ONLY) ? true : false;
      }
    } break;
    case ArgField::ActualAccQual: {
      cl_int itAccQual = amd::Kernel::FindValue(amd::Kernel::kArgAccQualV3, buf);
      if (itAccQual == static_cast<cl_int>(0)) {
        return AMD_COMGR_STATUS_ERROR;
      }
      lcArg->info_.isReadOnlyByCompiler = true;
      lcArg->info_.readOnly_ = (itAccQual == CL_KERNEL_ARG_ACCESS_READ_ONLY) ? true : false;
    } break;
    case ArgField::IsConst:
      lcArg->typeQualifier_ |= (buf.compare("1") == 0) ? CL_KERNEL_ARG_TYPE_CONST : 0;
      break;
    case ArgField::IsRestrict:
      lcArg->typeQualifier_ |= (buf.compare("1") == 0) ? CL_KERNEL_ARG_TYPE_RESTRICT : 0;
      break;
    case ArgField::IsVolatile:
      lcArg->typeQualifier_ |= (buf.compare("1") == 0) ? CL_KERNEL_ARG_TYPE_VOLATILE : 0;
      break;
    case ArgField::IsPipe:
      lcArg->typeQualifier_ |= (buf.compare("1") == 0) ? CL_KERNEL_ARG_TYPE_PIPE : 0;
      break;
    default:
      return AMD_COMGR_STATUS_ERROR;
  }
  return AMD_COMGR_STATUS_SUCCESS;
}

static amd_comgr_status_t populateKernelMetaV3(const amd_comgr_metadata_node_t key,
                                               const amd_comgr_metadata_node_t value, void* data) {
  amd_comgr_status_t status;
  amd_comgr_metadata_kind_t kind;
  size_t size = 0;
  std::string buf;
  // get the key of the argument field
  status = amd::Comgr::get_metadata_kind(key, &kind);
  if (kind == AMD_COMGR_METADATA_KIND_STRING && status == AMD_COMGR_STATUS_SUCCESS) {
    status = getMetaBuf(key, &buf);
  }

  if (status != AMD_COMGR_STATUS_SUCCESS) {
    return AMD_COMGR_STATUS_ERROR;
  }

  KernelField itKernelField =
      amd::Kernel::FindValue<KernelField>(amd::Kernel::kKernelFieldMapV3, buf);
  if (itKernelField == KernelField::MaxSize) {
    return AMD_COMGR_STATUS_ERROR;
  }

  if (itKernelField != KernelField::ReqdWorkGroupSize &&
      itKernelField != KernelField::WorkGroupSizeHint &&
      itKernelField != KernelField::ClusterDims) {
       status = getMetaBuf(value, &buf);
  }
  if (status != AMD_COMGR_STATUS_SUCCESS) {
    return AMD_COMGR_STATUS_ERROR;
  }

  device::Kernel* kernel = static_cast<device::Kernel*>(data);
  switch (itKernelField) {
    case KernelField::ReqdWorkGroupSize:
      status = amd::Comgr::get_metadata_list_size(value, &size);
      if (size == 3 && status == AMD_COMGR_STATUS_SUCCESS) {
        std::array<size_t, 3> wrkSize;
        for (size_t i = 0; i < size && status == AMD_COMGR_STATUS_SUCCESS; i++) {
          amd_comgr_metadata_node_t workgroupSize;
          status = amd::Comgr::index_list_metadata(value, i, &workgroupSize);

          if (status == AMD_COMGR_STATUS_SUCCESS &&
              getMetaBuf(workgroupSize, &buf) == AMD_COMGR_STATUS_SUCCESS) {
            wrkSize[i] = atoi(buf.c_str());
          }
          amd::Comgr::destroy_metadata(workgroupSize);
        }
        if (!wrkSize.empty()) {
          kernel->setReqdWorkGroupSize(wrkSize[0], wrkSize[1], wrkSize[2]);
        }
      }
      break;
    case KernelField::WorkGroupSizeHint:
      status = amd::Comgr::get_metadata_list_size(value, &size);
      if (status == AMD_COMGR_STATUS_SUCCESS && size == 3) {
        std::array<size_t, 3> hintSize;
        for (size_t i = 0; i < size && status == AMD_COMGR_STATUS_SUCCESS; i++) {
          amd_comgr_metadata_node_t workgroupSizeHint;
          status = amd::Comgr::index_list_metadata(value, i, &workgroupSizeHint);

          if (status == AMD_COMGR_STATUS_SUCCESS &&
              getMetaBuf(workgroupSizeHint, &buf) == AMD_COMGR_STATUS_SUCCESS) {
            hintSize[i] = atoi(buf.c_str());
          }
          amd::Comgr::destroy_metadata(workgroupSizeHint);
        }
        if (!hintSize.empty()) {
          kernel->setWorkGroupSizeHint(hintSize[0], hintSize[1], hintSize[2]);
        }
      }
      break;
    case KernelField::ClusterDims:
      status = amd::Comgr::get_metadata_list_size(value, &size);
      if (size == 3 && status == AMD_COMGR_STATUS_SUCCESS) {
        std::vector<size_t> clusterSize;
        for (size_t i = 0; i < size && status == AMD_COMGR_STATUS_SUCCESS; i++) {
          amd_comgr_metadata_node_t clusterSizeNode;
          status = amd::Comgr::index_list_metadata(value, i, &clusterSizeNode);
          if (status == AMD_COMGR_STATUS_SUCCESS &&
              getMetaBuf(clusterSizeNode, &buf) == AMD_COMGR_STATUS_SUCCESS) {
            clusterSize.push_back(atoi(buf.c_str()));
          }
          amd::Comgr::destroy_metadata(clusterSizeNode);
        }
        if (!clusterSize.empty()) {
          kernel->setClusterSize(clusterSize[0], clusterSize[1], clusterSize[2]);
        }
      }
      break;
    case KernelField::VecTypeHint:
      kernel->setVecTypeHint(buf);
      break;
    case KernelField::DeviceEnqueueSymbol:
      kernel->setRuntimeHandle(buf);
      break;
    case KernelField::KernargSegmentSize:
      kernel->SetKernargSegmentByteSize(atoi(buf.c_str()));
      break;
    case KernelField::GroupSegmentFixedSize:
      kernel->SetWorkgroupGroupSegmentByteSize(atoi(buf.c_str()));
      break;
    case KernelField::PrivateSegmentFixedSize:
      kernel->SetWorkitemPrivateSegmentByteSize(atoi(buf.c_str()));
      break;
    case KernelField::KernargSegmentAlign:
      kernel->SetKernargSegmentAlignment(atoi(buf.c_str()));
      break;
    case KernelField::WavefrontSize:
      kernel->workGroupInfo()->wavefrontSize_ = atoi(buf.c_str());
      break;
    case KernelField::NumSGPRs:
      kernel->workGroupInfo()->usedSGPRs_ = atoi(buf.c_str());
      break;
    case KernelField::NumVGPRs:
      kernel->workGroupInfo()->usedVGPRs_ = atoi(buf.c_str());
      break;
    case KernelField::MaxFlatWorkGroupSize:
      kernel->workGroupInfo()->size_ = atoi(buf.c_str());
      break;
    case KernelField::NumSpilledSGPRs: {
      size_t mNumSpilledSGPRs = atoi(buf.c_str());
    } break;
    case KernelField::NumSpilledVGPRs: {
      size_t mNumSpilledVGPRs = atoi(buf.c_str());
    } break;
    case KernelField::SymbolName:
      kernel->SetSymbolName(buf);
      break;
    case KernelField::Kind:
      kernel->SetKernelKind(buf);
      break;
    case KernelField::WgpMode:
      // The compiler currently serializes this boolean field as "0"/"1" instead
      // of "false"/"true"; consider both "true" and "1" truthy values.
      kernel->SetWGPMode(buf.compare("true") == 0 || buf.compare("1") == 0);
      break;
    case KernelField::UniformWrokGroupSize:
      kernel->setUniformWorkGroupSize(buf.compare("1") == 0);
      break;
    default:
      return AMD_COMGR_STATUS_ERROR;
  }

  return status;
}

// ================================================================================================
Kernel::Kernel(const amd::Device& dev, const std::string& name, const Program& prog)
    : dev_(dev), name_(name), prog_(prog), signature_(nullptr) {
  // Instead of memset(&workGroupInfo_, '\0', sizeof(workGroupInfo_));
  // Due to std::string not being able to be memset to 0
  workGroupInfo_.size_ = 0;
  workGroupInfo_.compileSize_[0] = 0;
  workGroupInfo_.compileSize_[1] = 0;
  workGroupInfo_.compileSize_[2] = 0;
  workGroupInfo_.clusterSize_[0] = 1;
  workGroupInfo_.clusterSize_[1] = 1;
  workGroupInfo_.clusterSize_[2] = 1;
  workGroupInfo_.localMemSize_ = 0;
  workGroupInfo_.preferredSizeMultiple_ = 0;
  workGroupInfo_.privateMemSize_ = 0;
  workGroupInfo_.scratchRegs_ = 0;
  workGroupInfo_.wavefrontPerSIMD_ = 0;
  workGroupInfo_.wavefrontSize_ = 0;
  workGroupInfo_.availableGPRs_ = 0;
  workGroupInfo_.usedGPRs_ = 0;
  workGroupInfo_.availableSGPRs_ = 0;
  workGroupInfo_.usedSGPRs_ = 0;
  workGroupInfo_.availableVGPRs_ = dev.info().availableVGPRs_;
  workGroupInfo_.usedVGPRs_ = 0;
  workGroupInfo_.availableLDSSize_ = 0;
  workGroupInfo_.usedLDSSize_ = 0;
  workGroupInfo_.availableStackSize_ = 0;
  workGroupInfo_.usedStackSize_ = 0;
  workGroupInfo_.compileSizeHint_[0] = 0;
  workGroupInfo_.compileSizeHint_[1] = 0;
  workGroupInfo_.compileSizeHint_[2] = 0;
  workGroupInfo_.compileVecTypeHint_ = "";
  workGroupInfo_.isWGPMode_ = false;
  workGroupInfo_.uniformWorkGroupSize_ = false;
  workGroupInfo_.wavesPerSimdHint_ = 0;
  workGroupInfo_.constMemSize_ = 0;
  workGroupInfo_.maxDynamicSharedSizeBytes_ = 0;
  workGroupInfo_.hasClusterAttr_ = false;
}

// ================================================================================================
bool Kernel::createSignature(const parameters_t& params, uint32_t numParameters, uint32_t version) {
  std::stringstream attribs;
  if (workGroupInfo_.compileSize_[0] != 0) {
    attribs << "reqd_work_group_size(";
    for (size_t i = 0; i < 3; ++i) {
      if (i != 0) {
        attribs << ",";
      }

      attribs << workGroupInfo_.compileSize_[i];
    }
    attribs << ")";
  }
  if (workGroupInfo_.clusterSize_[0] != 0) {
    attribs << "cluster_dims(";
    for (size_t i = 0; i < 3; ++i) {
      if (i != 0) {
        attribs << ",";
      }
      attribs << workGroupInfo_.clusterSize_[i];
    }
    attribs << ")";
  }
  if (workGroupInfo_.compileSizeHint_[0] != 0) {
    attribs << " work_group_size_hint(";
    for (size_t i = 0; i < 3; ++i) {
      if (i != 0) {
        attribs << ",";
      }

      attribs << workGroupInfo_.compileSizeHint_[i];
    }
    attribs << ")";
  }

  if (!workGroupInfo_.compileVecTypeHint_.empty()) {
    attribs << " vec_type_hint(" << workGroupInfo_.compileVecTypeHint_ << ")";
  }

  // Destroy old signature if it was allocated before
  // (offline devices path)
  delete signature_;
  signature_ = new amd::KernelSignature(params, attribs.str(), numParameters, version);
  if (NULL != signature_) {
    return true;
  }
  return false;
}

// ================================================================================================
Kernel::~Kernel() { delete signature_; }

// ================================================================================================
void Kernel::FindLocalWorkSize(size_t workDim, const amd::NDRange& gblWorkSize,
                               amd::NDRange& lclWorkSize) const {
  // Initialize the default workgoup info
  // Check if the kernel has the compiled sizes
  if (workGroupInfo()->compileSize_[0] == 0) {
    // Find the default local workgroup size, if it wasn't specified
    if (lclWorkSize[0] == 0) {
      // Find threads per group
      size_t thrPerGrp = workGroupInfo()->size_;

      // Check if kernel uses images
      if (flags_.imageEna_ &&
          // and thread group is a multiple value of wavefronts
          ((thrPerGrp % workGroupInfo()->wavefrontSize_) == 0) &&
          // and it's 2 or 3-dimensional workload
          (workDim > 1) && (((gblWorkSize[0] % 16) == 0) && ((gblWorkSize[1] % 16) == 0))) {
        // Use 8x8 workgroup size if kernel has image writes
        if (flags_.imageWriteEna_ || (thrPerGrp != device().info().preferredWorkGroupSize_)) {
          lclWorkSize[0] = 8;
          lclWorkSize[1] = 8;
        } else {
          lclWorkSize[0] = 16;
          lclWorkSize[1] = 16;
        }
        if (workDim == 3) {
          lclWorkSize[2] = 1;
        }
      } else {
        size_t tmp = thrPerGrp;
        // Split the local workgroup into the most efficient way
        for (uint d = 0; d < workDim; ++d) {
          size_t div = tmp;
          for (; (gblWorkSize[d] % div) != 0; div--);
          lclWorkSize[d] = div;
          tmp /= div;
        }

        if (!workGroupInfo()->uniformWorkGroupSize_) {
          // Assuming DWORD access
          const uint cacheLineMatch = device().info().globalMemCacheLineSize_ >> 2;

          // Check if we couldn't find optimal workload
          if (((lclWorkSize.product() % workGroupInfo()->wavefrontSize_) != 0) ||
              // or size is too small for the cache line
              (lclWorkSize[0] < cacheLineMatch)) {
            size_t maxSize = 0;
            size_t maxDim = 0;
            for (uint d = 0; d < workDim; ++d) {
              if (maxSize < gblWorkSize[d]) {
                maxSize = gblWorkSize[d];
                maxDim = d;
              }
            }
            // Use X dimension as high priority. Runtime will assume that
            // X dimension is more important for the address calculation
            if ((maxDim != 0) && (gblWorkSize[0] >= (cacheLineMatch / 2))) {
              lclWorkSize[0] = cacheLineMatch;
              thrPerGrp /= cacheLineMatch;
              lclWorkSize[maxDim] = thrPerGrp;
              for (uint d = 1; d < workDim; ++d) {
                if (d != maxDim) {
                  lclWorkSize[d] = 1;
                }
              }
            } else {
              // Check if a local workgroup has the most optimal size
              if (thrPerGrp > maxSize) {
                thrPerGrp = maxSize;
              }
              lclWorkSize[maxDim] = thrPerGrp;
              for (uint d = 0; d < workDim; ++d) {
                if (d != maxDim) {
                  lclWorkSize[d] = 1;
                }
              }
            }
          }
        }
      }
    }
  } else {
    for (uint d = 0; d < workDim; ++d) {
      lclWorkSize[d] = workGroupInfo()->compileSize_[d];
    }
  }
}

// ================================================================================================
bool Kernel::GetAttrCodePropMetadata() {
  amd_comgr_metadata_node_t kernelMetaNode;
  if (!prog().getKernelMetadata(name(), &kernelMetaNode)) {
    ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_KERN,
             "Cannot get program kernel metadata for %s \n", name().c_str());
    return false;
  }

  // Set the workgroup information for the kernel
  workGroupInfo_.availableLDSSize_ = device().info().localMemSizePerCU_;
  workGroupInfo_.availableSGPRs_ = 104;
  workGroupInfo_.availableVGPRs_ = 256;

  // extract the attribute metadata if there is any
  amd_comgr_status_t status = AMD_COMGR_STATUS_SUCCESS;

  switch (codeObjectVer()) {
    case 2: {
      amd_comgr_metadata_node_t symbolName;
      status = amd::Comgr::metadata_lookup(kernelMetaNode, "SymbolName", &symbolName);
      if (status == AMD_COMGR_STATUS_SUCCESS) {
        std::string name;
        status = getMetaBuf(symbolName, &name);
        amd::Comgr::destroy_metadata(symbolName);
        SetSymbolName(name);
      }

      amd_comgr_metadata_node_t attrMeta;
      if (status == AMD_COMGR_STATUS_SUCCESS) {
        if (amd::Comgr::metadata_lookup(kernelMetaNode, "Attrs", &attrMeta) ==
            AMD_COMGR_STATUS_SUCCESS) {
          status =
              amd::Comgr::iterate_map_metadata(attrMeta, populateAttrs, static_cast<void*>(this));
          amd::Comgr::destroy_metadata(attrMeta);
        }
      }

      // extract the code properties metadata
      amd_comgr_metadata_node_t codePropsMeta;
      if (status == AMD_COMGR_STATUS_SUCCESS) {
        status = amd::Comgr::metadata_lookup(kernelMetaNode, "CodeProps", &codePropsMeta);
      }

      if (status == AMD_COMGR_STATUS_SUCCESS) {
        status = amd::Comgr::iterate_map_metadata(codePropsMeta, populateCodeProps,
                                                  static_cast<void*>(this));
        amd::Comgr::destroy_metadata(codePropsMeta);
      }
    } break;
    default:
      status = amd::Comgr::iterate_map_metadata(kernelMetaNode, populateKernelMetaV3,
                                                static_cast<void*>(this));
  }

  if (status != AMD_COMGR_STATUS_SUCCESS) {
    LogError("Comgr Api failed with Status: \n");
    return false;
  }

  InitParameters(kernelMetaNode);

  return true;
}

bool Kernel::GetPrintfStr(std::vector<std::string>* printfStr) {
  const amd_comgr_metadata_node_t programMD = prog().metadata();
  amd_comgr_metadata_node_t printfMeta;

  amd_comgr_status_t status = amd::Comgr::metadata_lookup(
      programMD, codeObjectVer() == 2 ? "Printf" : "amdhsa.printf", &printfMeta);
  if (status != AMD_COMGR_STATUS_SUCCESS) {
    return true;  // printf string metadata is not provided so just exit
  }

  // handle the printf string
  size_t printfSize = 0;
  status = amd::Comgr::get_metadata_list_size(printfMeta, &printfSize);

  if (status == AMD_COMGR_STATUS_SUCCESS) {
    size_t originalSize = printfStr->size();
    printfStr->reserve(originalSize + printfSize);
    for (size_t i = 0; i < printfSize; ++i) {
      amd_comgr_metadata_node_t str;
      status = amd::Comgr::index_list_metadata(printfMeta, i, &str);

      std::string buf;
      if (status == AMD_COMGR_STATUS_SUCCESS) {
        status = getMetaBuf(str, &buf);
        amd::Comgr::destroy_metadata(str);
      }

      if (status != AMD_COMGR_STATUS_SUCCESS) {
        ClPrint(amd::LOG_DETAIL_DEBUG, amd::LOG_COMGR,
                "Comgr API failed with status: %d \n", status);
        amd::Comgr::destroy_metadata(printfMeta);
        printfStr->resize(originalSize);  // restore the original size of the vector
        return false;
      }

      printfStr->push_back(std::move(buf));
    }
  }

  amd::Comgr::destroy_metadata(printfMeta);
  return (status == AMD_COMGR_STATUS_SUCCESS);
}

void Kernel::InitParameters(const amd_comgr_metadata_node_t kernelMD) {
  // Iterate through the arguments and insert into parameterList
  device::Kernel::parameters_t params;
  device::Kernel::parameters_t hiddenParams;
  hiddenParams.reserve(amd::KernelParameterDescriptor::Desc::MaxSize);
  size_t offset = 0;

  amd_comgr_metadata_node_t argsMeta;
  bool hsaArgsMeta = false;
  size_t argsSize = 0;

  amd_comgr_status_t status =
      amd::Comgr::metadata_lookup(kernelMD, (codeObjectVer() == 2) ? "Args" : ".args", &argsMeta);
  // Assume no arguments if lookup fails.
  if (status == AMD_COMGR_STATUS_SUCCESS) {
    hsaArgsMeta = true;
    status = amd::Comgr::get_metadata_list_size(argsMeta, &argsSize);
  }

  params.reserve(argsSize);

  for (size_t i = 0; i < argsSize; ++i) {
    amd::KernelParameterDescriptor desc = {};

    amd_comgr_metadata_node_t argsNode;
    amd_comgr_metadata_kind_t kind = AMD_COMGR_METADATA_KIND_NULL;
    bool hsaArgsNode = false;

    status = amd::Comgr::index_list_metadata(argsMeta, i, &argsNode);

    if (status == AMD_COMGR_STATUS_SUCCESS) {
      hsaArgsNode = true;
      status = amd::Comgr::get_metadata_kind(argsNode, &kind);
    }
    if (kind != AMD_COMGR_METADATA_KIND_MAP) {
      status = AMD_COMGR_STATUS_ERROR;
    }
    if (status == AMD_COMGR_STATUS_SUCCESS) {
      void* data = static_cast<void*>(&desc);
      if (codeObjectVer() == 2) {
        status = amd::Comgr::iterate_map_metadata(argsNode, populateArgs, data);
      } else if (codeObjectVer() >= 3) {
        status = amd::Comgr::iterate_map_metadata(argsNode, populateArgsV3, data);
      }
    }

    if (hsaArgsNode) {
      amd::Comgr::destroy_metadata(argsNode);
    }

    if (status != AMD_COMGR_STATUS_SUCCESS) {
      if (hsaArgsMeta) {
        amd::Comgr::destroy_metadata(argsMeta);
      }
      return;
    }

    // COMGR has unclear/undefined order of the fields filling.
    // Correct the types for the abstraciton layer after all fields are available
    if (desc.info_.oclObject_ != amd::KernelParameterDescriptor::ValueObject) {
      switch (desc.info_.oclObject_) {
        case amd::KernelParameterDescriptor::MemoryObject:
        case amd::KernelParameterDescriptor::ImageObject:
          desc.type_ = T_POINTER;
          if (desc.info_.shared_) {
            if (desc.info_.arrayIndex_ == 0) {
              LogWarning("Missing DynamicSharedPointer alignment");
              desc.info_.arrayIndex_ = 128; /* worst case alignment */
            }
          } else {
            desc.info_.arrayIndex_ = 1;
          }
          break;
        case amd::KernelParameterDescriptor::SamplerObject:
          desc.type_ = T_SAMPLER;
          desc.addressQualifier_ = CL_KERNEL_ARG_ADDRESS_PRIVATE;
          break;
        case amd::KernelParameterDescriptor::QueueObject:
          desc.type_ = T_QUEUE;
          break;
        default:
          desc.type_ = T_VOID;
          break;
      }
    }

    if ((desc.info_.oclObject_ == amd::KernelParameterDescriptor::ImageObject) ||
        (desc.typeQualifier_ & CL_KERNEL_ARG_TYPE_PIPE)) {
      // LC doesn't report correct address qualifier for images and pipes,
      // hence overwrite it
      // We will remove this when newer LC is ready
      desc.addressQualifier_ = CL_KERNEL_ARG_ADDRESS_GLOBAL;
    } else {
      // According to CL spec, otherwise must be CL_KERNEL_ARG_ACCESS_NONE,
      desc.accessQualifier_ = CL_KERNEL_ARG_ACCESS_NONE;
    }

    size_t size = desc.size_;

    // Allocate the hidden arguments, but abstraction layer will skip them
    if (desc.info_.hidden_) {
      if (desc.info_.oclObject_ == amd::KernelParameterDescriptor::HiddenCompletionAction &&
          !amd::IS_HIP) {
        setDynamicParallelFlag(true);
      }
      if (codeObjectVer() == 2) {
        desc.offset_ = amd::alignUp(offset, desc.alignment_);
        offset += size;
      }
      hiddenParams.push_back(desc);
      continue;
    }

    // These objects have forced data size to uint64_t
    if (codeObjectVer() == 2) {
      if ((desc.info_.oclObject_ == amd::KernelParameterDescriptor::ImageObject) ||
          (desc.info_.oclObject_ == amd::KernelParameterDescriptor::SamplerObject) ||
          (desc.info_.oclObject_ == amd::KernelParameterDescriptor::QueueObject)) {
        offset = amd::alignUp(offset, sizeof(uint64_t));
        desc.offset_ = offset;
        offset += sizeof(uint64_t);
      } else {
        offset = amd::alignUp(offset, desc.alignment_);
        desc.offset_ = offset;
        offset += size;
      }
    }

    params.push_back(desc);

    if (desc.info_.oclObject_ == amd::KernelParameterDescriptor::ImageObject) {
      flags_.imageEna_ = true;
      if (desc.accessQualifier_ != CL_KERNEL_ARG_ACCESS_READ_ONLY) {
        flags_.imageWriteEna_ = true;
      }
    }
  }

  if (hsaArgsMeta) {
    amd::Comgr::destroy_metadata(argsMeta);
  }

  // Save the number of OCL arguments
  uint32_t numParams = params.size();
  // Append the hidden arguments to the OCL arguments
  params.insert(params.end(), hiddenParams.begin(), hiddenParams.end());
  createSignature(params, numParams, amd::KernelSignature::ABIVersion_LC);
}

// ================================================================================================
void Kernel::InitPrintf(const std::vector<std::string>& printfInfoStrings) {
  size_t HIPPrintfInfoID = 0;
  for (auto str : printfInfoStrings) {
    std::vector<std::string> tokens;

    size_t end, pos = 0;
    do {
      end = str.find_first_of(':', pos);
      tokens.push_back(str.substr(pos, end - pos));
      pos = end + 1;
    } while (end != std::string::npos);

    if (tokens.size() < 2) {
      LogPrintfError("Invalid PrintInfo string: \"%s\"", str.c_str());
      continue;
    }

    pos = 0;
    size_t printfInfoID;

    if (amd::IS_HIP) {
      printfInfoID = HIPPrintfInfoID++;
      printf_.resize(HIPPrintfInfoID);
      pos++;
    } else {
      printfInfoID = std::stoi(tokens[pos++]);
      if (printf_.size() <= printfInfoID) {
        printf_.resize(printfInfoID + 1);
      }
    }

    PrintfInfo& info = printf_[printfInfoID];

    size_t numSizes = std::stoi(tokens[pos++]);
    end = pos + numSizes;

    // ensure that we have the correct number of tokens
    if (tokens.size() < end + 1 /*last token is the fmtString*/) {
      LogPrintfError("Invalid PrintInfo string: \"%s\"", str.c_str());
      continue;
    }

    // push the argument sizes
    while (pos < end) {
      info.arguments_.push_back(std::stoi(tokens[pos++]));
    }

    // FIXME: We should not need this! [
    std::string fmt;
    // Format string itself might contain ':' characters
    for (int i = 0; pos < tokens.size(); i++) {
      if (i) fmt += ':';
      fmt += tokens[pos++];
    }

    bool need_nl = true;

    for (pos = 0; pos < fmt.size(); ++pos) {
      char symbol = fmt[pos];
      need_nl = true;
      if (symbol == '\\') {
        switch (fmt[pos + 1]) {
          case 'a':
            pos++;
            symbol = '\a';
            break;
          case 'b':
            pos++;
            symbol = '\b';
            break;
          case 'f':
            pos++;
            symbol = '\f';
            break;
          case 'n':
            pos++;
            symbol = '\n';
            need_nl = false;
            break;
          case 'r':
            pos++;
            symbol = '\r';
            break;
          case 'v':
            pos++;
            symbol = '\v';
            break;
          case '7':
            if (fmt[pos + 2] == '2') {
              pos += 2;
              symbol = '\72';
            }
            break;
          default:
            break;
        }
      }
      info.fmtString_.push_back(symbol);
    }
    if (need_nl && !amd::IS_HIP) {
      info.fmtString_ += "\n";
    }
    // ]
  }
}
}  // namespace amd::device
