//===-- R600KernelParameters.cpp - Lower kernel function arguments --------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This pass lowers kernel function arguments to loads from the vertex buffer.
//
// Kernel arguemnts are stored in the vertex buffer at an offset of 9 dwords,
// so arg0 needs to be loaded from VTX_BUFFER[9] and arg1 is loaded from
// VTX_BUFFER[10], etc.
//
//===----------------------------------------------------------------------===//

#include "AMDGPU.h"
#include "AMDIL.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/Constants.h"
#include "llvm/Function.h"
#include "llvm/Intrinsics.h"
#include "llvm/Metadata.h"
#include "llvm/Module.h"
#include "llvm/Target/TargetData.h"
#include "llvm/Support/IRBuilder.h"
#include "llvm/Support/TypeBuilder.h"

#include <map>
#include <set>

using namespace llvm;

namespace {

#define CONSTANT_CACHE_SIZE_DW 127

class R600KernelParameters : public FunctionPass {
  const TargetData *TD;
  LLVMContext* Context;
  Module *Mod;

  struct Param {
    Param() : Val(NULL), PtrVal(NULL), OffsetInDW(0), SizeInDW(0),
              IsIndirect(true), SpecialID(0) {}

    Value* Val;
    Value* PtrVal;
    int OffsetInDW;
    int SizeInDW;

    bool IsIndirect;

    std::string SpecialType;
    int SpecialID;

    int End() { return OffsetInDW + SizeInDW; }
    // The first 9 dwords are reserved for the grid sizes.
    int getRatOffset() { return 9 + OffsetInDW; }
  };

  std::vector<Param> Params;

  bool IsOpenCLKernel(const Function *Fun);
  int getLastSpecialID(const std::string& TypeName);

  int getListSize();
  void AddParam(Argument *Arg);
  int CalculateArgumentSize(Argument *Arg);
  void RunAna(Function *Fun);
  void Replace(Function *Fun);
  bool IsIndirect(Value *Val, std::set<Value*> &Visited);
  void Propagate(Function* Fun);
  void Propagate(Value *V, const Twine &Name, bool IsIndirect = true);
  Value* ConstantRead(Function *Fun, Param &P);
  Value* handleSpecial(Function *Fun, Param &P);
  bool IsSpecialType(Type *T);
  std::string getSpecialTypeName(Type *T);
public:
  static char ID;
  R600KernelParameters() : FunctionPass(ID) {};
  R600KernelParameters(const TargetData* TD) : FunctionPass(ID), TD(TD) {}
  bool runOnFunction (Function &F);
  void getAnalysisUsage(AnalysisUsage &AU) const;
  const char *getPassName() const;
  bool doInitialization(Module &M);
  bool doFinalization(Module &M);
};

char R600KernelParameters::ID = 0;

static RegisterPass<R600KernelParameters> X("kerparam",
                            "OpenCL Kernel Parameter conversion", false, false);

bool R600KernelParameters::IsOpenCLKernel(const Function* Fun) {
  Module *Mod = const_cast<Function*>(Fun)->getParent();
  NamedMDNode * MD = Mod->getOrInsertNamedMetadata("opencl.kernels");

  if (!MD || !MD->getNumOperands()) {
    return false;
  }

  for (int i = 0; i < int(MD->getNumOperands()); i++) {
    if (!MD->getOperand(i) || !MD->getOperand(i)->getOperand(0)) {
      continue;
    }

    assert(MD->getOperand(i)->getNumOperands() == 1);

    if (MD->getOperand(i)->getOperand(0)->getName() == Fun->getName()) {
      return true;
    }
  }

  return false;
}

int R600KernelParameters::getLastSpecialID(const std::string &TypeName) {
  int LastID = -1;

  for (std::vector<Param>::iterator i = Params.begin(); i != Params.end(); i++) {
    if (i->SpecialType == TypeName) {
      LastID = i->SpecialID;
    }
  }

  return LastID;
}

int R600KernelParameters::getListSize() {
  if (Params.size() == 0) {
    return 0;
  }

  return Params.back().End();
}

bool R600KernelParameters::IsIndirect(Value *Val, std::set<Value*> &Visited) {
  //XXX Direct parameters are not supported yet, so return true here.
  return true;
#if 0
  if (isa<LoadInst>(Val)) {
    return false;
  }

  if (isa<IntegerType>(Val->getType())) {
    assert(0 && "Internal error");
    return false;
  }

  if (Visited.count(Val)) {
    return false;
  }

  Visited.insert(Val);

  if (isa<getElementPtrInst>(Val)) {
    getElementPtrInst* GEP = dyn_cast<getElementPtrInst>(Val);
    getElementPtrInst::op_iterator I = GEP->op_begin();

    for (++I; I != GEP->op_end(); ++I) {
      if (!isa<Constant>(*I)) {
        return true;
      }
    }
  }

  for (Value::use_iterator I = Val->use_begin(); i != Val->use_end(); ++I) {
    Value* V2 = dyn_cast<Value>(*I);

    if (V2) {
      if (IsIndirect(V2, Visited)) {
        return true;
      }
    }
  }

  return false;
#endif
}

void R600KernelParameters::AddParam(Argument *Arg) {
  Param P;

  P.Val = dyn_cast<Value>(Arg);
  P.OffsetInDW = getListSize();
  P.SizeInDW = CalculateArgumentSize(Arg);

  if (isa<PointerType>(Arg->getType()) && Arg->hasByValAttr()) {
    std::set<Value*> Visited;
    P.IsIndirect = IsIndirect(P.Val, Visited);
  }

  Params.push_back(P);
}

int R600KernelParameters::CalculateArgumentSize(Argument *Arg) {
  Type* T = Arg->getType();

  if (Arg->hasByValAttr() && dyn_cast<PointerType>(T)) {
    T = dyn_cast<PointerType>(T)->getElementType();
  }

  int StoreSizeInDW = (TD->getTypeStoreSize(T) + 3)/4;

  assert(StoreSizeInDW);

  return StoreSizeInDW;
}


void R600KernelParameters::RunAna(Function* Fun) {
  assert(IsOpenCLKernel(Fun));

  for (Function::arg_iterator I = Fun->arg_begin(); I != Fun->arg_end(); ++I) {
    AddParam(I);
  }

}

void R600KernelParameters::Replace(Function* Fun) {
  for (std::vector<Param>::iterator I = Params.begin(); I != Params.end(); ++I) {
    Value *NewVal;

    if (IsSpecialType(I->Val->getType())) {
      NewVal = handleSpecial(Fun, *I);
    } else {
      NewVal = ConstantRead(Fun, *I);
    }
    if (NewVal) {
      I->Val->replaceAllUsesWith(NewVal);
    }
  }
}

void R600KernelParameters::Propagate(Function* Fun) {
  for (std::vector<Param>::iterator I = Params.begin(); I != Params.end(); ++I) {
    if (I->PtrVal) {
      Propagate(I->PtrVal, I->Val->getName(), I->IsIndirect);
    }
  }
}

void R600KernelParameters::Propagate(Value* V, const Twine& Name, bool IsIndirect) {
  LoadInst* Load = dyn_cast<LoadInst>(V);
  GetElementPtrInst *GEP = dyn_cast<GetElementPtrInst>(V);

  unsigned Addrspace;

  if (IsIndirect) {
    Addrspace = AMDGPUAS::PARAM_I_ADDRESS;
  }  else {
    Addrspace = AMDGPUAS::PARAM_D_ADDRESS;
  }

  if (GEP && GEP->getType()->getAddressSpace() != Addrspace) {
    Value *Op = GEP->getPointerOperand();

    if (dyn_cast<PointerType>(Op->getType())->getAddressSpace() != Addrspace) {
      Op = new BitCastInst(Op, PointerType::get(dyn_cast<PointerType>(
                           Op->getType())->getElementType(), Addrspace),
                           Name, dyn_cast<Instruction>(V));
    }

    std::vector<Value*> Params(GEP->idx_begin(), GEP->idx_end());

    GetElementPtrInst* GEP2 = GetElementPtrInst::Create(Op, Params, Name,
                                                      dyn_cast<Instruction>(V));
    GEP2->setIsInBounds(GEP->isInBounds());
    V = dyn_cast<Value>(GEP2);
    GEP->replaceAllUsesWith(GEP2);
    GEP->eraseFromParent();
    Load = NULL;
  }

  if (Load) {
    ///normally at this point we have the right address space
    if (Load->getPointerAddressSpace() != Addrspace) {
      Value *OrigPtr = Load->getPointerOperand();
      PointerType *OrigPtrType = dyn_cast<PointerType>(OrigPtr->getType());

      Type* NewPtrType = PointerType::get(OrigPtrType->getElementType(),
                                            Addrspace);

      Value* NewPtr = OrigPtr;

      if (OrigPtr->getType() != NewPtrType) {
        NewPtr = new BitCastInst(OrigPtr, NewPtrType, "prop_cast", Load);
      }

      Value* new_Load = new LoadInst(NewPtr, Name, Load);
      Load->replaceAllUsesWith(new_Load);
      Load->eraseFromParent();
    }

    return;
  }

  std::vector<User*> Users(V->use_begin(), V->use_end());

  for (int i = 0; i < int(Users.size()); i++) {
    Value* V2 = dyn_cast<Value>(Users[i]);

    if (V2) {
      Propagate(V2, Name, IsIndirect);
    }
  }
}

Value* R600KernelParameters::ConstantRead(Function *Fun, Param &P) {
  assert(Fun->front().begin() != Fun->front().end());

  Instruction *FirstInst = Fun->front().begin();
  IRBuilder <> Builder (FirstInst);
/* First 3 dwords are reserved for the dimmension info */

  if (!P.Val->hasNUsesOrMore(1)) {
    return NULL;
  }
  unsigned Addrspace;

  if (P.IsIndirect) {
    Addrspace = AMDGPUAS::PARAM_I_ADDRESS;
  } else {
    Addrspace = AMDGPUAS::PARAM_D_ADDRESS;
  }

  Argument *Arg = dyn_cast<Argument>(P.Val);
  Type * ArgType = P.Val->getType();
  PointerType * ArgPtrType = dyn_cast<PointerType>(P.Val->getType());

  if (ArgPtrType && Arg->hasByValAttr()) {
    Value* ParamAddrSpacePtr = ConstantPointerNull::get(
                                    PointerType::get(Type::getInt32Ty(*Context),
                                    Addrspace));
    Value* ParamPtr = GetElementPtrInst::Create(ParamAddrSpacePtr,
                                    ConstantInt::get(Type::getInt32Ty(*Context),
                                    P.getRatOffset()), Arg->getName(),
                                    FirstInst);
    ParamPtr = new BitCastInst(ParamPtr,
                                PointerType::get(ArgPtrType->getElementType(),
                                                 Addrspace),
                                Arg->getName(), FirstInst);
    P.PtrVal = ParamPtr;
    return ParamPtr;
  } else {
    Value *ParamAddrSpacePtr = ConstantPointerNull::get(PointerType::get(
                                                        ArgType, Addrspace));

    Value *ParamPtr = Builder.CreateGEP(ParamAddrSpacePtr,
             ConstantInt::get(Type::getInt32Ty(*Context), P.getRatOffset()),
                              Arg->getName());

    Value *Param_Value = Builder.CreateLoad(ParamPtr, Arg->getName());

    return Param_Value;
  }
}

Value* R600KernelParameters::handleSpecial(Function* Fun, Param& P) {
  std::string Name = getSpecialTypeName(P.Val->getType());
  int ID;

  assert(!Name.empty());

  if (Name == "image2d_t" || Name == "image3d_t") {
    int LastID = std::max(getLastSpecialID("image2d_t"),
                     getLastSpecialID("image3d_t"));

    if (LastID == -1) {
      ID = 2; ///ID0 and ID1 are used internally by the driver
    } else {
      ID = LastID + 1;
    }
  } else if (Name == "sampler_t") {
    int LastID = getLastSpecialID("sampler_t");

    if (LastID == -1) {
      ID = 0;
    } else {
      ID = LastID + 1;
    }
  } else {
    ///TODO: give some error message
    return NULL;
  }

  P.SpecialType = Name;
  P.SpecialID = ID;

  Instruction *FirstInst = Fun->front().begin();

  return new IntToPtrInst(ConstantInt::get(Type::getInt32Ty(*Context),
                                           P.SpecialID), P.Val->getType(),
                                           "resourceID", FirstInst);
}


bool R600KernelParameters::IsSpecialType(Type* T) {
  return !getSpecialTypeName(T).empty();
}

std::string R600KernelParameters::getSpecialTypeName(Type* T) {
  PointerType *PT = dyn_cast<PointerType>(T);
  StructType *ST = NULL;

  if (PT) {
    ST = dyn_cast<StructType>(PT->getElementType());
  }

  if (ST) {
    std::string Prefix = "struct.opencl_builtin_type_";

    std::string Name = ST->getName().str();

    if (Name.substr(0, Prefix.length()) == Prefix) {
      return Name.substr(Prefix.length(), Name.length());
    }
  }

  return "";
}


bool R600KernelParameters::runOnFunction (Function &F) {
  if (!IsOpenCLKernel(&F)) {
    return false;
  }

  RunAna(&F);
  Replace(&F);
  Propagate(&F);

  return false;
}

void R600KernelParameters::getAnalysisUsage(AnalysisUsage &AU) const {
  FunctionPass::getAnalysisUsage(AU);
  AU.setPreservesAll();
}

const char *R600KernelParameters::getPassName() const {
  return "OpenCL Kernel parameter conversion to memory";
}

bool R600KernelParameters::doInitialization(Module &M) {
  Context = &M.getContext();
  Mod = &M;

  return false;
}

bool R600KernelParameters::doFinalization(Module &M) {
  return false;
}

} // End anonymous namespace

FunctionPass* llvm::createR600KernelParametersPass(const TargetData* TD) {
  return new R600KernelParameters(TD);
}
