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

Copyright (c) 2017 Intel Corporation

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


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

#include "AdaptorCommon/ImplicitArgs.hpp"
#include "Compiler/Optimizer/OpenCLPasses/OpenCLPrintf/OpenCLPrintfResolution.hpp"
#include "Compiler/Optimizer/OpenCLPasses/OpenCLPrintf/OpenCLPrintfAnalysis.hpp"
#include "Compiler/IGCPassSupport.h"

#include "common/LLVMWarningsPush.hpp"

#include "llvmWrapper/IR/Attributes.h"

#include <llvm/IR/Module.h>
#include <llvm/IR/Instruction.h>
#include <llvm/IR/Instructions.h>
#include <llvm/IR/InstIterator.h>
#include "common/LLVMWarningsPop.hpp"

using namespace llvm;
using namespace IGC;
using namespace IGC::IGCMD;

// Register pass to igc-opt
#define PASS_FLAG "igc-opencl-printf-resolution"
#define PASS_DESCRIPTION "Resolves OpenCL printf calls"
#define PASS_CFG_ONLY false
#define PASS_ANALYSIS false
IGC_INITIALIZE_PASS_BEGIN(OpenCLPrintfResolution, PASS_FLAG, PASS_DESCRIPTION, PASS_CFG_ONLY, PASS_ANALYSIS)
IGC_INITIALIZE_PASS_DEPENDENCY(MetaDataUtilsWrapper)
IGC_INITIALIZE_PASS_END(OpenCLPrintfResolution, PASS_FLAG, PASS_DESCRIPTION, PASS_CFG_ONLY, PASS_ANALYSIS)

char OpenCLPrintfResolution::ID = 0;

#define KB                                  ( 1024 )
#define MB                                  ( 1024 * KB )
const unsigned int  PrintfBufferSize = 4 * MB;

//
// FORMAT OF PRINTF OUTPUT BUFFER:
// ================================
//
//======================================================================
//| DWORD  bufferSize             Size of the buffer in bytes          |  <-- This value is incremented by atomic_add
//|====================================================================|
//| DWORD  stringIndex_ch_0       Index of format string for channel 0 |  \
//|--------------------------------------------------------------------|  |
//| DWORD  data0Type              Type identifier                      |  |
//|--------------------------------------------------------------------|  |
//| DWORD  data0                  Data for channel 0                   |  |
//|--------------------------------------------------------------------|  | Channel 0 data
//| . . .  . . .                                                       |  |
//|--------------------------------------------------------------------|  |
//| DWORD  data1Type              Type identifier                      |  |
//|--------------------------------------------------------------------|  |
//| DWORD  data1                  Data for channel 0                   |  /
//|====================================================================|  
//| DWORD  stringIndex_ch_1       Index of format string for channel 1 |  \
//|--------------------------------------------------------------------|  |
//| DWORD  data0Type              Type identifier                      |  |
//|--------------------------------------------------------------------|  |
//| DWORD  data0                  Data for channel 1                   |  |
//|--------------------------------------------------------------------|  |  Channel 1 data
//| . . .  . . .                                                       |  |
//|--------------------------------------------------------------------|  |
//| DWORD  data0Type              Type identifier                      |  |
//|--------------------------------------------------------------------|  |
//| DWORD  data1                  Data for channel 1                   |  /
//|====================================================================|  
//| . . .  . . .                                                       |
//| . . .  . . .                                                       |
//| . . .  . . .                                                       |
//|====================================================================|  
//| DWORD  stringIndex_ch_N       Index of format string for channel N |  \
//|--------------------------------------------------------------------|  |
//| DWORD  data0Type              Type identifier                      |  |
//|--------------------------------------------------------------------|  |
//| DWORD  data0                  Data for channel N                   |  |
//|--------------------------------------------------------------------|  |  Channel N data
//| . . .  . . .                                                       |  |
//|--------------------------------------------------------------------|  |
//| DWORD  data0Type              Type identifier                      |  |
//|--------------------------------------------------------------------|  |
//| DWORD  data1                  Data for channel N                   |  /
//|--------------------------------------------------------------------|


// For vector arguments, 2 type identifiers are used: 1st is SHADER_PRINTF_VECTOR_*  and 2nd is the vector length.
// These 2 type identifiers are followed by the elements of the vector.
// Example: float4
//
// |------------------------------|
// |  SHADER_PRINTF_VECTOR_FLOAT  |
// |------------------------------|
// |             0x4              |
// |------------------------------|
// |      < vec_element_0 >       |
// |------------------------------|
// |      < vec_element_1 >       |
// |------------------------------|
// |      < vec_element_2 >       |
// |------------------------------|
// |      < vec_element_3 >       |
// |------------------------------|


// Looks for a GlobalVariable related with given value.
// Returns nullptr if on the way to the global variable 
// found anything that is not :
// * a CastInst
// * a GEP with non-zero indices
inline GlobalVariable * getGlobalVariable(Value * const v)
{
    Value * curr = v;
    while(nullptr != curr)
    {
        if(isa<GlobalVariable>(curr))
        {
            break;
        }

        if(CastInst *castInst = dyn_cast<CastInst>(curr))
        {
            curr = castInst->getOperand(0);
        }
        else if(GetElementPtrInst *getElemPtrInst = dyn_cast<GetElementPtrInst>(curr))
        {
            curr = getElemPtrInst->hasAllZeroIndices() ? getElemPtrInst->getPointerOperand() : nullptr;
        }
        else
        {
            // Unhandled value type
            assert((false == isa<ConstantExpr>(curr)));
            curr = nullptr;
        }
    }

    return dyn_cast_or_null<GlobalVariable>(curr);
}

OpenCLPrintfResolution::OpenCLPrintfResolution() : FunctionPass(ID), m_atomicAddFunc(nullptr)
{
    initializeOpenCLPrintfResolutionPass(*PassRegistry::getPassRegistry());
}

bool IGC::OpenCLPrintfResolution::doInitialization(Module &M)
{
    m_module = &M;
    m_context = &M.getContext();
    m_CGContext = nullptr;
    m_stringIndex = 0;
    m_ptrSizeIntType = M.getDataLayout().getIntPtrType(*m_context);
    m_int32Type = Type::getInt32Ty(*m_context);

    return FunctionPass::doInitialization(M);
}

bool OpenCLPrintfResolution::runOnFunction(Function &F)
{
    if (m_CGContext == nullptr) {
        m_CGContext = getAnalysis<CodeGenContextWrapper>().getCodeGenContext();
        m_fp64Supported = m_CGContext->platform.supportFP64();
    }

    // Gather all found printf calls into the m_printfCalls vector.
    visit(F);

    bool changed = !m_printfCalls.empty();

    // Put strings found in the printf calls into metadata.
    // Replace the printf calls with sequences of instructions that
    // writes data into printf output buffer.
    for(CallInst *printfCall : m_printfCalls)
    {
        m_DL = printfCall->getDebugLoc();
        expandPrintfCall(*printfCall, F);
    }

    m_printfCalls.clear();

    return changed;
}

void OpenCLPrintfResolution::visitCallInst(CallInst &callInst)
{
    if(!callInst.getCalledFunction())
    {
        return;
    }

    StringRef  funcName = callInst.getCalledFunction()->getName();
    if(funcName == OpenCLPrintfAnalysis::OPENCL_PRINTF_FUNCTION_NAME)
    {
        m_printfCalls.push_back(&callInst);
    }
}

std::string OpenCLPrintfResolution::getEscapedString(const ConstantDataSequential* pCDS)
{
    std::string Name;
    // This is to avoid unnecessary characters that exceed the char range
    for(unsigned i = 0, len = pCDS->getNumElements() - 1; i != len; i++)
    {
        if(isa<ConstantInt>(pCDS->getElementAsConstant(i)))
        {
            if((cast<ConstantInt>(pCDS->getElementAsConstant(i))->getZExtValue()) > 127)
            {
                Name = "";
                return Name;
            }
            unsigned char C = (char)cast<ConstantInt>(pCDS->getElementAsConstant(i))->getZExtValue();

            if(isprint(C) &&
              (C != '\\') &&
              (C != '"'))
            {
                Name.push_back(C);
            }
            else 
            {
                Name.push_back('\\');
                switch (C) 
                {
                case '\a':
                    Name.push_back('a');
                    break;
                case '\b':
                    Name.push_back('b');
                    break;
                case '\f':
                    Name.push_back('f');
                    break;
                case '\n':
                    Name.push_back('n');
                    break;
                case '\r':
                    Name.push_back('r');
                    break;
                case '\t':
                    Name.push_back('t');
                    break;
                case '\v':
                    Name.push_back('v');
                    break;
                default:
                    Name.push_back(C);
                    break;
                }
            }
        }
        else
        {
            Name = "";
            return Name;
        }
    }
    return Name;
}

int OpenCLPrintfResolution::processPrintfString(Value* printfArg, Function &F)
{
    GlobalVariable *formatString = getGlobalVariable(printfArg);

    ConstantDataArray *formatStringConst = ((nullptr != formatString) && (formatString->hasInitializer())) ? 
                                            dyn_cast<ConstantDataArray>(formatString->getInitializer()) :
                                            nullptr;

    if(nullptr == formatStringConst)
    {
        assert(0 && "Unexpected printf argument (expected string literal)");
        return 0;
    }

    // Add new metadata node and put the printf string into it.
    // The first element of metadata node is the string index,
    // the second element is the string itself.
    NamedMDNode *namedMDNode = m_module->getOrInsertNamedMetadata(getPrintfStringsMDNodeName(F));
    SmallVector<Metadata*, 2>  args;
    Metadata *stringIndexVal = ConstantAsMetadata::get(
        ConstantInt::get(m_int32Type, m_stringIndex++));

    std::string escaped_string = getEscapedString(formatStringConst);
    MDString *final_string = MDString::get(*m_context, escaped_string);

    args.push_back(stringIndexVal);
    args.push_back(final_string);

    MDNode *itemMDNode = MDNode::get(*m_context, args);
    namedMDNode->addOperand(itemMDNode);

    return m_stringIndex - 1;
}


bool OpenCLPrintfResolution::argIsString(Value *printfArg)
{
    GlobalVariable *formatString = getGlobalVariable(printfArg);
    if(nullptr == formatString)
    {
        return false;
    }

    ConstantDataArray *formatStringConst = dyn_cast<ConstantDataArray>(formatString->getInitializer());
    return ((nullptr != formatStringConst) && formatStringConst->isCString());
}

std::string OpenCLPrintfResolution::getPrintfStringsMDNodeName(Function &F)
{
    return "printf.strings";
}

static StoreInst* genStoreInternal(Value *Val, Value *Ptr, BasicBlock *InsertAtEnd, DebugLoc DL)
{
    bool isVolatile = false;
    unsigned Align = 4;
    auto SI = new llvm::StoreInst(Val, Ptr, isVolatile, Align, InsertAtEnd);
    SI->setDebugLoc(DL);
    return SI;
}

void OpenCLPrintfResolution::expandPrintfCall(CallInst &printfCall, Function &F)
{
// Replace a printf call with IR instructions that fill the rintf
// output buffer created by the Runtime:
// --------------------------------------------------------------------------
//       bufferPtr      - pointer to the printf output buffer. This pointer
//                        is an implicit kernel argument. It is loaded into
//                        GRF as part of thread payload.
//       bufferSize     - size of the printf output buffer. By agreement with
//                        Runtime, it is 4 Mb.
//       dataSize       - size of printf data for current thread.
//
// Note: we use STATELESS mode for printf buffer access.
//---------------------------------------------------------------------------
//    writeOffset = atomic_add(bufferPtr, dataSize);
//    writePtr = bufferPtr + writeOffset;
//    endOffset = writeOffset + dataSize;
//    if (endOffset < bufferSize) {                \
//        // Write the format string index         |
//        *writePtr = stringIndex;                 |
//        writePtr += 4;                           |
//                                                 |
//        // Write the argument type               |
//        *writePtr = argument[1].dataType;        |
//        writePtr += 4;                           |
//        // Write the argument value              |
//        *writePtr = argument[1].value;           |
//        writePtr += 4;                           | bblockTrue
//        . . .                                    |
//        . . .                                    |
//        // Write the argument type               |
//        *writePtr = argument[N].dataType;        |
//        writePtr += 4;                           |
//        // Write the argument value              |
//        *writePtr = argument[N].value;           |
//        writePtr += 4;                           |
//                                                 |
//        // printf returns 0 if successful        |
//        return_val = 0;                          /
//    }
//    else {                                                           \
//        // Check if the remaining output                             |
//        // buffer space is enough for writing                        |
//        //invalid string index.                                      |
//        endOffset = writeOffset + 4;                                 |
//        if (endOffset < bufferSize) {           \                    | bblockFalse
//            // Write the invalid string index.  | bblockErrorString  |
//            *writePtr = -1;                     |                    |
//        }                                       /                    |
//        // printf returns -1 if failed                               |
//        return_val = -1;                                             /
//   }
// ----------------------------------------------------------------------

    MetaDataUtils *MdUtils = getAnalysis<MetaDataUtilsWrapper>().getMetaDataUtils();
    ImplicitArgs implicitArgs(F, MdUtils);

    BasicBlock *currentBBlock = printfCall.getParent();

    // Put all printf argument into m_argDescriptors vector.
    // Scalarize vector arguments and substitute string arguments by their indices.
    preprocessPrintfArgs(printfCall);

    // writeOffset = atomic_add(bufferPtr, dataSize)
    Value* basebufferPtr = implicitArgs.getArgInFunc(F, ImplicitArg::PRINTF_BUFFER);
    Value* dataSizeVal = ConstantInt::get(m_int32Type, getTotalDataSize());
    Instruction *writeOffsetStart = genAtomicAdd(basebufferPtr, dataSizeVal, printfCall, "write_offset");
    writeOffsetStart->setDebugLoc(m_DL);

    Instruction *writeOffset = writeOffsetStart;
    Instruction *writeOffsetPtr = nullptr;

    // end_offset = write_offset + data_size
    Instruction *endOffset = BinaryOperator::CreateAdd(writeOffset, dataSizeVal, "end_offset", &printfCall);
    endOffset->setDebugLoc(m_DL);

    // The size of output printf buffer is 4 MB by agreement with Runtime.
    Value* bufferMaxSize = ConstantInt::get(m_int32Type, PrintfBufferSize);

    // write_ptr = buffer_ptr + write_offset;
    if(m_ptrSizeIntType != writeOffset->getType())
    {
        writeOffset = CastInst::Create(Instruction::CastOps::ZExt,
                                       writeOffset,
                                       m_ptrSizeIntType,
                                       "write_offset",
                                       &printfCall);
        writeOffset->setDebugLoc(m_DL);
    }
    Instruction* bufferPtr = CastInst::Create(Instruction::CastOps::PtrToInt,
                                              basebufferPtr, 
                                              m_ptrSizeIntType,
                                              "buffer_ptr",
                                              &printfCall);
    bufferPtr->setDebugLoc(m_DL);
    Instruction *writeOffsetAdd = BinaryOperator::CreateAdd(bufferPtr, 
                                                            writeOffset,
                                                            "write_offset",
                                                            &printfCall);
    writeOffsetAdd->setDebugLoc(m_DL);
    writeOffset = writeOffsetAdd;

    // if (end_offset < output_buffer_size))
    Instruction *cmp1 = CmpInst::Create(Instruction::ICmp, 
                                        CmpInst::ICMP_ULE,
                                        endOffset,
                                        bufferMaxSize,
                                        "",
                                        &printfCall);
    cmp1->setDebugLoc(m_DL);

    // Since we need to insert a branch here, the current basic block should be
    // splitted into two parts.
    BasicBlock *bblockJoin = currentBBlock->splitBasicBlock(BasicBlock::iterator(printfCall), "bblockJoin");
    
    // Create "true" and "false" branches.
    BasicBlock *bblockTrue  = BasicBlock::Create(*m_context, "write_offset_true", &F, bblockJoin);
    BasicBlock *bblockFalse = BasicBlock::Create(*m_context, "write_offset_false", &F, bblockJoin);

    currentBBlock->getTerminator()->eraseFromParent();
    BranchInst *brInst = BranchInst::Create(bblockTrue, bblockFalse, cmp1, currentBBlock);
    brInst->setDebugLoc(m_DL);

    //  ----------- Fill "true" block ----------------

    // write_offset += 4;
    Value *constVal4 = ConstantInt::get(m_ptrSizeIntType, 4);
    
    for(size_t i = 0, size = m_argDescriptors.size(); i < size; ++i)
    {
        SPrintfArgDescriptor *argDesc = &m_argDescriptors[i];
        Value *printfArg = argDesc->value;
        SHADER_PRINTF_TYPE dataType = argDesc->argType;

        // We don't store the dataType for format string (which is the first entry in m_argDescriptors).
        if (i != 0)
        {
            // *write_offset = argument[i].dataType
            Value *argTypeVal = ConstantInt::get(m_int32Type, (unsigned int)dataType);
            writeOffsetPtr = CastInst::Create(Instruction::CastOps::IntToPtr, writeOffset,
                m_int32Type->getPointerTo(ADDRESS_SPACE_GLOBAL), "write_offset_ptr", bblockTrue);
            writeOffsetPtr->setDebugLoc(m_DL);
            genStoreInternal(argTypeVal, writeOffsetPtr, bblockTrue, m_DL);

            // write_offset += 4            
            writeOffset = BinaryOperator::CreateAdd(writeOffset, constVal4, "write_offset", bblockTrue);
            writeOffset->setDebugLoc(m_DL);

            // For vector arguments, add vector size after type ID.
            if (argDesc->vecSize > 0) {
                Value *vecSizeVal = ConstantInt::get(m_int32Type, argDesc->vecSize);
                writeOffsetPtr = CastInst::Create(Instruction::CastOps::IntToPtr, writeOffset,
                    m_int32Type->getPointerTo(ADDRESS_SPACE_GLOBAL), "write_offset_ptr", bblockTrue);
                writeOffsetPtr->setDebugLoc(m_DL);
                genStoreInternal(vecSizeVal, writeOffsetPtr, bblockTrue, m_DL);

                // write_offset += 4            
                writeOffset = BinaryOperator::CreateAdd(writeOffset, constVal4, "write_offset", bblockTrue);
                writeOffset->setDebugLoc(m_DL);
            }
        }
         
        writeOffsetPtr = generateCastToPtr(argDesc, writeOffset, bblockTrue);
        writeOffsetPtr->setDebugLoc(m_DL);

        // *write_offset = argument[i].value
        genStoreInternal(printfArg, writeOffsetPtr , bblockTrue, m_DL);

        // write_offset += argument[i].size
        Value* offsetInc = ConstantInt::get(m_ptrSizeIntType, getArgTypeSize(dataType, argDesc->vecSize));
        writeOffset = BinaryOperator::CreateAdd(writeOffset, offsetInc, "write_offset", bblockTrue);
        writeOffset->setDebugLoc(m_DL);
    } // for (SPrintfArgDescriptor *argDesc : m_argDescriptors)

    brInst = BranchInst::Create(bblockJoin, bblockTrue);
    brInst->setDebugLoc(m_DL);

    //  ----------- Fill "false" block ----------------
    // end_offset = write_offset + 4
    Value *constVal4_32 = ConstantInt::get(m_int32Type, 4);
    endOffset = BinaryOperator::CreateAdd(writeOffsetStart, constVal4_32, "end_offset", bblockFalse);
    endOffset->setDebugLoc(m_DL);
    // if (end_offset < output_buffer_size)
    Instruction *cmp2 = CmpInst::Create(Instruction::ICmp,
                                        CmpInst::ICMP_ULE,
                                        endOffset,
                                        bufferMaxSize,
                                        "",
                                        bblockFalse);
    cmp2->setDebugLoc(m_DL);
    // Here, we generate code that checks if the error string index can be
    // written into the output buffer. 
    BasicBlock *bblockErrorString   = BasicBlock::Create(*m_context, "write_error_string", &F, bblockJoin);
    BasicBlock *bblockFalseJoin     = BasicBlock::Create(*m_context, "bblockFalseJoin", &F, bblockJoin);

    brInst = BranchInst::Create(bblockErrorString, bblockFalseJoin, cmp2, bblockFalse);
    brInst->setDebugLoc(m_DL);

    // *writeOffset = -1;
    Value *constValErrStringIdx = ConstantInt::get(m_int32Type, -1);
    writeOffsetPtr = CastInst::Create(Instruction::CastOps::IntToPtr,
                                      writeOffsetAdd,
                                      m_int32Type->getPointerTo(ADDRESS_SPACE_GLOBAL),
                                      "write_offset_ptr",
                                      bblockErrorString);
    writeOffsetPtr->setDebugLoc(m_DL);
    genStoreInternal(constValErrStringIdx, writeOffsetPtr, bblockErrorString, m_DL);
    brInst = BranchInst::Create(bblockFalseJoin, bblockErrorString);
    brInst->setDebugLoc(m_DL);

    // bblockFalseJoin is an empty basic block,
    // it is needed to assure bblockJoin have only 2 predecessors.
    brInst = BranchInst::Create(bblockJoin, bblockFalseJoin);
    brInst->setDebugLoc(m_DL);

    // return_val = select cmp1, 0, -1
    Value *constVal0  = ConstantInt::get(m_int32Type, 0);
    Value *constValm1 = ConstantInt::get(m_int32Type, -1);
    Instruction *returnVal  = SelectInst::Create(cmp1, constVal0, constValm1, "printf_ret_val", &printfCall);
    returnVal->setDebugLoc(m_DL);

    printfCall.replaceAllUsesWith(returnVal);
    printfCall.eraseFromParent();
    m_argDescriptors.clear();
}

Value* OpenCLPrintfResolution::fixupPrintfArg(CallInst &printfCall, Value* arg, SHADER_PRINTF_TYPE & argDataType)
{
    // For string argument, add the string to the metadata and put the string index
    // into the vector of arguments.
    switch(argDataType)
    {
    case SHADER_PRINTF_STRING_LITERAL: 
        {
            Function *F = printfCall.getParent()->getParent();
            uint stringIndex = processPrintfString(arg, *F);
            return ConstantInt::get(m_int32Type, stringIndex);
        }
        break;
    case SHADER_PRINTF_POINTER:
        {
            Instruction *tmp = CastInst::Create(Instruction::CastOps::PtrToInt,
                                                arg,
                                                m_ptrSizeIntType,
                                                "",
                                                &printfCall);
            tmp->setDebugLoc(m_DL);
            return tmp;
        }
        break;
    case SHADER_PRINTF_FLOAT:
    case SHADER_PRINTF_VECTOR_FLOAT:
    case SHADER_PRINTF_DOUBLE:
    case SHADER_PRINTF_VECTOR_DOUBLE:
        // Cast halfs back to float. Cast doubles to floats if the platform does not support double fp type.
        if ( arg->getType()->getScalarType()->isHalfTy() || (!m_fp64Supported && arg->getType()->getScalarType()->isDoubleTy()) )
        {
            if (argDataType == SHADER_PRINTF_DOUBLE)
                argDataType = SHADER_PRINTF_FLOAT;
            if (argDataType == SHADER_PRINTF_VECTOR_DOUBLE)
                argDataType = SHADER_PRINTF_VECTOR_FLOAT;

            if (ConstantFP* constVal = dyn_cast<ConstantFP>(arg))
            {
                // If this is a constant, just replace it.
                bool ignored;
                APFloat FV = constVal->getValueAPF();
                FV.convert(APFloat::IEEEsingle(), APFloat::rmNearestTiesToEven, &ignored);
                return ConstantFP::get(arg->getContext(), FV);
            }
            else if(CastInst* fpCastVal = dyn_cast<CastInst>(arg))
            {
                // If this is a fpcast, use the origin value.
                Type *srcType = fpCastVal->getSrcTy();
                if ( srcType->isFloatTy() || 
                    (srcType->isVectorTy() && srcType->getVectorElementType()->isFloatTy()) )
                {
                    return fpCastVal->getOperand(0);
                }
            }

            Type *newType = Type::getFloatTy(arg->getContext());
            if (arg->getType()->isVectorTy())
            {
                newType = VectorType::get(newType, arg->getType()->getVectorNumElements());
            }
            
            Instruction *tmp = CastInst::CreateFPCast(arg,
                                                      newType,
                                                      "to_float",
                                                      &printfCall);
            tmp->setDebugLoc(m_DL);
            return tmp;
        }
        break;

    default:
        break;
    }

    return arg;
}

void OpenCLPrintfResolution::preprocessPrintfArgs(CallInst &printfCall)
{
    for(int i = 0, numArgs = printfCall.getNumArgOperands(); i < numArgs; ++i)
    {
        Value *arg = printfCall.getOperand(i);
        Type  *argType = arg->getType();
        SHADER_PRINTF_TYPE argDataType = getPrintfArgDataType(arg);
        arg = fixupPrintfArg(printfCall, arg, argDataType);
        uint vecSize = 0;
        if (argType->isVectorTy()) {
            vecSize = argType->getVectorNumElements();
        }
        m_argDescriptors.push_back(SPrintfArgDescriptor(argDataType, arg, vecSize));
    }
}

CallInst* OpenCLPrintfResolution::genAtomicAdd(Value *outputBufferPtr,
                                               Value *dataSize,
                                               CallInst &printfCall,
                                               StringRef name)
{
    // outputBufferPtr->getType() could be "i8 addrspace(1)*", and the atomic prototype
    // requires "i32 addrspace(1)*":
    //
    //   %writeOffset = call i32 @__builtin_IB_atomic_add_global_i32(i32 addrspace(1)* <outputBufferPtr>,
    //                                                               i32 <dataSize>)
    //
    Type* bufPtrType = Type::getInt32PtrTy(*m_context, ADDRESS_SPACE_GLOBAL);
    if (outputBufferPtr->getType() != bufPtrType) {
      outputBufferPtr = CastInst::Create(Instruction::CastOps::BitCast,
                                         outputBufferPtr,
                                         bufPtrType,
                                         "ptrBC",
                                         &printfCall);
    }

    if (m_atomicAddFunc == nullptr) {
      Type* argTypes[] = { outputBufferPtr->getType(), dataSize->getType() };
      FunctionType *atomicFuncType = FunctionType::get(dataSize->getType(), argTypes, false);
      m_atomicAddFunc = cast<Function>(m_module->getOrInsertFunction("__builtin_IB_atomic_add_global_i32",
          atomicFuncType, IGCLLVM::AttributeSet()));
    }
    std::vector<Value*> args;
    args.push_back(outputBufferPtr);
    args.push_back(dataSize);
    
    return CallInst::Create(m_atomicAddFunc, args, name, &printfCall);
}

unsigned int OpenCLPrintfResolution::getArgTypeSize(SHADER_PRINTF_TYPE argType, uint vecSize)
{
    switch (argType) {
    case SHADER_PRINTF_LONG:
    case SHADER_PRINTF_DOUBLE:
    case SHADER_PRINTF_POINTER:    // Runtime expects 64 bit value for pointer regardless of its actual size.
        return 8;
    case SHADER_PRINTF_VECTOR_LONG:
    case SHADER_PRINTF_VECTOR_DOUBLE:
        return vecSize * 8;

    default:
        if (vecSize > 0) {
            return vecSize * 4;
        }
        else {
            return  4;
        }
    }
}

unsigned int OpenCLPrintfResolution::getTotalDataSize()
{
    assert(m_argDescriptors.size() > 0 && "Empty printf arguments list.");
    unsigned int dataSize = 0;
    // Add size of the format string index.
    dataSize += 4;

    // Skip 0-th operand (format string) and count total size of
    // the remaining arguments.
    for(size_t i = 1, size = m_argDescriptors.size(); i < size; ++i)
    {
        SPrintfArgDescriptor *argDesc = &m_argDescriptors[i];
        // Add size of the data type identifier.
        dataSize += 4;
        // Vector arguments require additional type identifier - number of elements.
        if (argDesc->vecSize > 0) {
            dataSize += 4;
        }
        // Add size of the data itself.
        dataSize += getArgTypeSize(argDesc->argType, argDesc->vecSize);
    }
    return dataSize;
}

SHADER_PRINTF_TYPE OpenCLPrintfResolution::getPrintfArgDataType(Value *printfArg)
{
    Type *argType = printfArg->getType();

    if (argIsString(printfArg))
    {
        return SHADER_PRINTF_STRING_LITERAL;
    }
    else if (argType->isVectorTy())
    {
        Type *elemType = argType->getVectorElementType();
        if (elemType->isFloatingPointTy())
        {
            if (elemType->isDoubleTy())
                return SHADER_PRINTF_VECTOR_DOUBLE;
            else
                return SHADER_PRINTF_VECTOR_FLOAT;
        }
        else if (elemType->isIntegerTy())
        {
            unsigned int typeSize = elemType->getScalarSizeInBits();
            switch (typeSize)
            {
            case 8:
                return SHADER_PRINTF_VECTOR_BYTE;
            case 16:
                return SHADER_PRINTF_VECTOR_SHORT;
            case 32:
                return SHADER_PRINTF_VECTOR_INT;
            case 64:
                return SHADER_PRINTF_VECTOR_LONG;
            }
        }
    }
    else
    {
        if(argType->isPointerTy())
        {
            return SHADER_PRINTF_POINTER;
        }
        else if(argType->isFloatingPointTy())
        {
            if (argType->isDoubleTy())
                return SHADER_PRINTF_DOUBLE;
            else
                return SHADER_PRINTF_FLOAT;
        }
        else if(argType->isIntegerTy())
        {
            unsigned int typeSize = argType->getScalarSizeInBits();
            switch(typeSize)
            {
            case 8:
                return SHADER_PRINTF_BYTE;
            case 16:
                return SHADER_PRINTF_SHORT;
            case 32:
                return SHADER_PRINTF_INT;
            case 64:
                return SHADER_PRINTF_LONG;
            }
        }
    }
    return SHADER_PRINTF_INVALID;
}

Instruction *OpenCLPrintfResolution::generateCastToPtr(SPrintfArgDescriptor *argDesc, 
                                                       Value *writeOffset, BasicBlock *bblock)
{
    Type *castedType = nullptr;

    switch (argDesc->argType)
    {
    case SHADER_PRINTF_BYTE:
    case SHADER_PRINTF_SHORT:
    case SHADER_PRINTF_INT:
    case SHADER_PRINTF_LONG:
    case SHADER_PRINTF_FLOAT:
    case SHADER_PRINTF_DOUBLE:
    case SHADER_PRINTF_VECTOR_BYTE:
    case SHADER_PRINTF_VECTOR_SHORT:
    case SHADER_PRINTF_VECTOR_INT:
    case SHADER_PRINTF_VECTOR_LONG:
    case SHADER_PRINTF_VECTOR_FLOAT:
    case SHADER_PRINTF_VECTOR_DOUBLE: {
        Type *origType = argDesc->value->getType();
        castedType = origType->getPointerTo(ADDRESS_SPACE_GLOBAL);
        break;
    }

    case SHADER_PRINTF_STRING_LITERAL:
        castedType = Type::getInt32PtrTy(*m_context, ADDRESS_SPACE_GLOBAL);
        break;

    case SHADER_PRINTF_POINTER:
        castedType = m_ptrSizeIntType->getPointerTo(ADDRESS_SPACE_GLOBAL);
        break;

    default:
        assert(0 && "Unexpected printf argument type");
    }

    return CastInst::Create(Instruction::CastOps::IntToPtr, 
                            writeOffset,
                            castedType,
                            "write_offset_ptr",
                            bblock);
}
