//===-- AMDGPURuntimeMD.cpp - Generates runtime metadata ------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
/// \file
///
/// Generates AMDGPU runtime metadata for YAML mapping.
//
//===----------------------------------------------------------------------===//
//

#include "AMDGPU.h"
#include "AMDGPURuntimeMetadata.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/YAMLTraits.h"
#include <vector>
#include "AMDGPURuntimeMD.h"

using namespace llvm;
using namespace ::AMDGPU::RuntimeMD;

static cl::opt<bool>
DumpRuntimeMD("amdgpu-dump-rtmd",
              cl::desc("Dump AMDGPU runtime metadata"));

static cl::opt<bool>
CheckRuntimeMDParser("amdgpu-check-rtmd-parser", cl::Hidden,
                     cl::desc("Check AMDGPU runtime metadata YAML parser"));

LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(uint32_t)
LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(std::string)
LLVM_YAML_IS_SEQUENCE_VECTOR(Kernel::Metadata)
LLVM_YAML_IS_SEQUENCE_VECTOR(KernelArg::Metadata)

namespace llvm {
namespace yaml {

template <> struct MappingTraits<KernelArg::Metadata> {
  static void mapping(IO &YamlIO, KernelArg::Metadata &A) {
    YamlIO.mapRequired(KeyName::ArgSize, A.Size);
    YamlIO.mapRequired(KeyName::ArgAlign, A.Align);
    YamlIO.mapOptional(KeyName::ArgPointeeAlign, A.PointeeAlign, 0U);
    YamlIO.mapRequired(KeyName::ArgKind, A.Kind);
    YamlIO.mapRequired(KeyName::ArgValueType, A.ValueType);
    YamlIO.mapOptional(KeyName::ArgTypeName, A.TypeName, std::string());
    YamlIO.mapOptional(KeyName::ArgName, A.Name, std::string());
    YamlIO.mapOptional(KeyName::ArgAddrQual, A.AddrQual, INVALID_ADDR_QUAL);
    YamlIO.mapOptional(KeyName::ArgAccQual, A.AccQual, INVALID_ACC_QUAL);
    YamlIO.mapOptional(KeyName::ArgIsVolatile, A.IsVolatile, uint8_t(0));
    YamlIO.mapOptional(KeyName::ArgIsConst, A.IsConst, uint8_t(0));
    YamlIO.mapOptional(KeyName::ArgIsRestrict, A.IsRestrict, uint8_t(0));
    YamlIO.mapOptional(KeyName::ArgIsPipe, A.IsPipe, uint8_t(0));
  }
  static const bool flow = true;
};

template <> struct MappingTraits<Kernel::Metadata> {
  static void mapping(IO &YamlIO, Kernel::Metadata &K) {
    YamlIO.mapRequired(KeyName::KernelName, K.Name);
    YamlIO.mapOptional(KeyName::Language, K.Language, std::string());
    YamlIO.mapOptional(KeyName::LanguageVersion, K.LanguageVersion);
    YamlIO.mapOptional(KeyName::ReqdWorkGroupSize, K.ReqdWorkGroupSize);
    YamlIO.mapOptional(KeyName::WorkGroupSizeHint, K.WorkGroupSizeHint);
    YamlIO.mapOptional(KeyName::VecTypeHint, K.VecTypeHint, std::string());
    YamlIO.mapOptional(KeyName::KernelIndex, K.KernelIndex,
        INVALID_KERNEL_INDEX);
    YamlIO.mapOptional(KeyName::NoPartialWorkGroups, K.NoPartialWorkGroups,
        uint8_t(0));
    YamlIO.mapRequired(KeyName::Args, K.Args);
  }
  static const bool flow = true;
};

template <> struct MappingTraits<Program::Metadata> {
  static void mapping(IO &YamlIO, Program::Metadata &Prog) {
    YamlIO.mapRequired(KeyName::MDVersion, Prog.MDVersionSeq);
    YamlIO.mapOptional(KeyName::PrintfInfo, Prog.PrintfInfo);
    YamlIO.mapOptional(KeyName::Kernels, Prog.Kernels);
  }
  static const bool flow = true;
};

} // end namespace yaml
} // end namespace llvm

// Get a vector of three integer values from MDNode \p Node;
static std::vector<uint32_t> getThreeInt32(MDNode *Node) {
  assert(Node->getNumOperands() == 3);
  std::vector<uint32_t> V;
  for (const MDOperand &Op : Node->operands()) {
    const ConstantInt *CI = mdconst::extract<ConstantInt>(Op);
    V.push_back(CI->getZExtValue());
  }
  return V;
}

static std::string getOCLTypeName(Type *Ty, bool Signed) {
  switch (Ty->getTypeID()) {
  case Type::HalfTyID:
    return "half";
  case Type::FloatTyID:
    return "float";
  case Type::DoubleTyID:
    return "double";
  case Type::IntegerTyID: {
    if (!Signed)
      return (Twine('u') + getOCLTypeName(Ty, true)).str();
    unsigned BW = Ty->getIntegerBitWidth();
    switch (BW) {
    case 8:
      return "char";
    case 16:
      return "short";
    case 32:
      return "int";
    case 64:
      return "long";
    default:
      return (Twine('i') + Twine(BW)).str();
    }
  }
  case Type::VectorTyID: {
    VectorType *VecTy = cast<VectorType>(Ty);
    Type *EleTy = VecTy->getElementType();
    unsigned Size = VecTy->getVectorNumElements();
    return (Twine(getOCLTypeName(EleTy, Signed)) + Twine(Size)).str();
  }
  default:
    return "unknown";
  }
}

static KernelArg::ValueType getRuntimeMDValueType(
  Type *Ty, StringRef TypeName) {
  switch (Ty->getTypeID()) {
  case Type::HalfTyID:
    return KernelArg::F16;
  case Type::FloatTyID:
    return KernelArg::F32;
  case Type::DoubleTyID:
    return KernelArg::F64;
  case Type::IntegerTyID: {
    bool Signed = !TypeName.startswith("u");
    switch (Ty->getIntegerBitWidth()) {
    case 8:
      return Signed ? KernelArg::I8 : KernelArg::U8;
    case 16:
      return Signed ? KernelArg::I16 : KernelArg::U16;
    case 32:
      return Signed ? KernelArg::I32 : KernelArg::U32;
    case 64:
      return Signed ? KernelArg::I64 : KernelArg::U64;
    default:
      // Runtime does not recognize other integer types. Report as struct type.
      return KernelArg::Struct;
    }
  }
  case Type::VectorTyID:
    return getRuntimeMDValueType(Ty->getVectorElementType(), TypeName);
  case Type::PointerTyID:
    return getRuntimeMDValueType(Ty->getPointerElementType(), TypeName);
  default:
    return KernelArg::Struct;
  }
}

static KernelArg::AddressSpaceQualifer getRuntimeAddrSpace(
    AMDGPUAS::AddressSpaces A) {
  switch (A) {
  case AMDGPUAS::GLOBAL_ADDRESS:
    return KernelArg::Global;
  case AMDGPUAS::CONSTANT_ADDRESS:
    return KernelArg::Constant;
  case AMDGPUAS::LOCAL_ADDRESS:
    return KernelArg::Local;
  case AMDGPUAS::FLAT_ADDRESS:
    return KernelArg::Generic;
  case AMDGPUAS::REGION_ADDRESS:
    return KernelArg::Region;
  default:
    return KernelArg::Private;
  }
}

static KernelArg::Metadata getRuntimeMDForKernelArg(const DataLayout &DL,
    Type *T, KernelArg::Kind Kind, StringRef BaseTypeName = "",
    StringRef TypeName = "", StringRef ArgName = "", StringRef TypeQual = "",
    StringRef AccQual = "") {

  KernelArg::Metadata Arg;

  // Set ArgSize and ArgAlign.
  Arg.Size = DL.getTypeAllocSize(T);
  Arg.Align = DL.getABITypeAlignment(T);
  if (auto PT = dyn_cast<PointerType>(T)) {
    auto ET = PT->getElementType();
    if (PT->getAddressSpace() == AMDGPUAS::LOCAL_ADDRESS && ET->isSized())
      Arg.PointeeAlign = DL.getABITypeAlignment(ET);
  }

  // Set ArgTypeName.
  Arg.TypeName = TypeName;

  // Set ArgName.
  Arg.Name = ArgName;

  // Set ArgIsVolatile, ArgIsRestrict, ArgIsConst and ArgIsPipe.
  SmallVector<StringRef, 1> SplitQ;
  TypeQual.split(SplitQ, " ", -1, false /* Drop empty entry */);

  for (StringRef KeyName : SplitQ) {
    auto *P = StringSwitch<uint8_t *>(KeyName)
      .Case("volatile", &Arg.IsVolatile)
      .Case("restrict", &Arg.IsRestrict)
      .Case("const",    &Arg.IsConst)
      .Case("pipe",     &Arg.IsPipe)
      .Default(nullptr);
    if (P)
      *P = 1;
  }

  // Set ArgKind.
  Arg.Kind = Kind;

  // Set ArgValueType.
  Arg.ValueType = getRuntimeMDValueType(T, BaseTypeName);

  // Set ArgAccQual.
  if (!AccQual.empty()) {
    Arg.AccQual = StringSwitch<KernelArg::AccessQualifer>(AccQual)
      .Case("read_only",  KernelArg::ReadOnly)
      .Case("write_only", KernelArg::WriteOnly)
      .Case("read_write", KernelArg::ReadWrite)
      .Default(KernelArg::AccNone);
  }

  // Set ArgAddrQual.
  if (auto *PT = dyn_cast<PointerType>(T)) {
    Arg.AddrQual = getRuntimeAddrSpace(static_cast<AMDGPUAS::AddressSpaces>(
        PT->getAddressSpace()));
  }

  return Arg;
}

static Kernel::Metadata getRuntimeMDForKernel(const Function &F) {
  Kernel::Metadata Kernel;
  Kernel.Name = F.getName();
  auto &M = *F.getParent();

  // Set Language and LanguageVersion.
  if (auto MD = M.getNamedMetadata("opencl.ocl.version")) {
    if (MD->getNumOperands() != 0) {
      auto Node = MD->getOperand(0);
      if (Node->getNumOperands() > 1) {
        Kernel.Language = "OpenCL C";
        uint32_t Major = mdconst::extract<ConstantInt>(Node->getOperand(0))
                         ->getZExtValue();
        uint32_t Minor = mdconst::extract<ConstantInt>(Node->getOperand(1))
                         ->getZExtValue();
        Kernel.LanguageVersion.push_back(Major);
        Kernel.LanguageVersion.push_back(Minor);
      }
    }
  }

  const DataLayout &DL = F.getParent()->getDataLayout();
  for (auto &Arg : F.args()) {
    unsigned I = Arg.getArgNo();
    Type *T = Arg.getType();
    auto TypeName = dyn_cast<MDString>(F.getMetadata(
        "kernel_arg_type")->getOperand(I))->getString();
    auto BaseTypeName = cast<MDString>(F.getMetadata(
        "kernel_arg_base_type")->getOperand(I))->getString();
    StringRef ArgName;
    if (auto ArgNameMD = F.getMetadata("kernel_arg_name"))
      ArgName = cast<MDString>(ArgNameMD->getOperand(I))->getString();
    auto TypeQual = cast<MDString>(F.getMetadata(
        "kernel_arg_type_qual")->getOperand(I))->getString();
    auto AccQual = cast<MDString>(F.getMetadata(
        "kernel_arg_access_qual")->getOperand(I))->getString();
    KernelArg::Kind Kind;
    if (TypeQual.find("pipe") != StringRef::npos)
      Kind = KernelArg::Pipe;
    else Kind = StringSwitch<KernelArg::Kind>(BaseTypeName)
      .Case("sampler_t", KernelArg::Sampler)
      .Case("queue_t",   KernelArg::Queue)
      .Cases("image1d_t", "image1d_array_t", "image1d_buffer_t",
             "image2d_t" , "image2d_array_t",  KernelArg::Image)
      .Cases("image2d_depth_t", "image2d_array_depth_t",
             "image2d_msaa_t", "image2d_array_msaa_t",
             "image2d_msaa_depth_t",  KernelArg::Image)
      .Cases("image2d_array_msaa_depth_t", "image3d_t",
             KernelArg::Image)
      .Default(isa<PointerType>(T) ?
                   (T->getPointerAddressSpace() == AMDGPUAS::LOCAL_ADDRESS ?
                   KernelArg::DynamicSharedPointer :
                   KernelArg::GlobalBuffer) :
                   KernelArg::ByValue);
    Kernel.Args.emplace_back(getRuntimeMDForKernelArg(DL, T, Kind,
        BaseTypeName, TypeName, ArgName, TypeQual, AccQual));
  }

  // Emit hidden kernel arguments for OpenCL kernels.
  if (F.getParent()->getNamedMetadata("opencl.ocl.version")) {
    auto Int64T = Type::getInt64Ty(F.getContext());
    Kernel.Args.emplace_back(getRuntimeMDForKernelArg(DL, Int64T,
        KernelArg::HiddenGlobalOffsetX));
    Kernel.Args.emplace_back(getRuntimeMDForKernelArg(DL, Int64T,
        KernelArg::HiddenGlobalOffsetY));
    Kernel.Args.emplace_back(getRuntimeMDForKernelArg(DL, Int64T,
        KernelArg::HiddenGlobalOffsetZ));
    if (F.getParent()->getNamedMetadata("llvm.printf.fmts")) {
      auto Int8PtrT = Type::getInt8PtrTy(F.getContext(),
          KernelArg::Global);
      Kernel.Args.emplace_back(getRuntimeMDForKernelArg(DL, Int8PtrT,
          KernelArg::HiddenPrintfBuffer));
    }
  }

  // Set ReqdWorkGroupSize, WorkGroupSizeHint, and VecTypeHint.
  if (auto RWGS = F.getMetadata("reqd_work_group_size"))
    Kernel.ReqdWorkGroupSize = getThreeInt32(RWGS);

  if (auto WGSH = F.getMetadata("work_group_size_hint"))
    Kernel.WorkGroupSizeHint = getThreeInt32(WGSH);

  if (auto VTH = F.getMetadata("vec_type_hint"))
    Kernel.VecTypeHint = getOCLTypeName(cast<ValueAsMetadata>(
      VTH->getOperand(0))->getType(), mdconst::extract<ConstantInt>(
      VTH->getOperand(1))->getZExtValue());

  return Kernel;
}

Program::Metadata::Metadata(const std::string &YAML) {
  yaml::Input Input(YAML);
  Input >> *this;
}

std::string Program::Metadata::toYAML(void) {
  std::string Text;
  raw_string_ostream Stream(Text);
  yaml::Output Output(Stream, nullptr, INT_MAX /* do not wrap line */);
  Output << *this;
  return Stream.str();
}

Program::Metadata Program::Metadata::fromYAML(const std::string &S) {
  return Program::Metadata(S);
}

// Check if the YAML string can be parsed.
static void checkRuntimeMDYAMLString(const std::string &YAML) {
  auto P = Program::Metadata::fromYAML(YAML);
  auto S = P.toYAML();
  llvm::errs() << "AMDGPU runtime metadata parser test "
               << (YAML == S ? "passes" : "fails") << ".\n";
  if (YAML != S) {
    llvm::errs() << "First output: " << YAML << '\n'
                 << "Second output: " << S << '\n';
  }
}

std::string llvm::getRuntimeMDYAMLString(Module &M) {
  Program::Metadata Prog;
  Prog.MDVersionSeq.push_back(MDVersion);
  Prog.MDVersionSeq.push_back(MDRevision);

  // Set PrintfInfo.
  if (auto MD = M.getNamedMetadata("llvm.printf.fmts")) {
    for (unsigned I = 0; I < MD->getNumOperands(); ++I) {
      auto Node = MD->getOperand(I);
      if (Node->getNumOperands() > 0)
        Prog.PrintfInfo.push_back(cast<MDString>(Node->getOperand(0))
            ->getString());
    }
  }

  // Set Kernels.
  for (auto &F: M.functions()) {
    if (!F.getMetadata("kernel_arg_type"))
      continue;
    Prog.Kernels.emplace_back(getRuntimeMDForKernel(F));
  }

  auto YAML = Prog.toYAML();

  if (DumpRuntimeMD)
    llvm::errs() << "AMDGPU runtime metadata:\n" << YAML << '\n';

  if (CheckRuntimeMDParser)
    checkRuntimeMDYAMLString(YAML);

  return YAML;
}
