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

Copyright (C) 2022 Intel Corporation

SPDX-License-Identifier: MIT

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

#include "PromoteBools.h"
#include "Compiler/IGCPassSupport.h"
#include "common/LLVMWarningsPush.hpp"
#include "llvmWrapper/IR/DerivedTypes.h"
#include "llvmWrapper/IR/Instructions.h"
#include "llvmWrapper/IR/Type.h"
#include "llvmWrapper/Support/Alignment.h"
#include "llvmWrapper/Transforms/Utils/Cloning.h"
#include <llvm/IR/Module.h>
#include <llvm/IR/Function.h>
#include <llvm/IR/InlineAsm.h>
#include <llvm/IR/Instructions.h>
#include <llvm/Demangle/Demangle.h>
#include <llvm/IR/Mangler.h>
#include <llvm/Support/Regex.h>
#include "common/LLVMWarningsPop.hpp"
#include "PreprocessSPVIR.h"

using namespace llvm;
using namespace IGC;

static void swapNames(Value* value1, Value* value2)
{
    if (value1->getType()->isVoidTy()
        || value2->getType()->isVoidTy()
        || (!value1->hasName() && !value2->hasName()))
    {
        return;
    }

    auto name1 = value1->getName().str();
    auto name2 = value2->getName().str();
    value1->setName(name2);
    value2->setName(name1);
}

// Register pass to igc-opt
#define PASS_FLAG "igc-promote-bools"
#define PASS_DESCRIPTION "Promote bools"
#define PASS_CFG_ONLY false
#define PASS_ANALYSIS false
IGC_INITIALIZE_PASS_BEGIN(PromoteBools, PASS_FLAG, PASS_DESCRIPTION, PASS_CFG_ONLY, PASS_ANALYSIS)
IGC_INITIALIZE_PASS_END(PromoteBools, PASS_FLAG, PASS_DESCRIPTION, PASS_CFG_ONLY, PASS_ANALYSIS)

char PromoteBools::ID = 0;

PromoteBools::PromoteBools() : ModulePass(ID)
{
    initializePromoteBoolsPass(*PassRegistry::getPassRegistry());
}

bool PromoteBools::runOnModule(Module& module)
{
    changed = false;

    // Promote functions
    for (auto& function : module.functions())
    {
        changed |= getOrCreatePromotedValue(&function) != &function;
    }

    // Promote global variables
    for (auto& globalVariable : module.globals())
    {
        changed |= getOrCreatePromotedValue(&globalVariable) != &globalVariable;
    }

    visit(module);

    while (!promotionQueue.empty())
    {
        auto value = promotionQueue.front();
        promotionQueue.pop();
        if (!wasPromoted(value))
        {
            if (auto inst = dyn_cast<Instruction>(value))
            {
                if (wasPromoted(inst->getFunction()))
                {
                    continue;
                }
            }
            getOrCreatePromotedValue(value);
        }
    }

    cleanUp(module);

    return changed;
}

Value* PromoteBools::convertI1ToI8(Value* value, Instruction* insertBefore)
{
    if (!value->getType()->isIntegerTy(1))
    {
        return value;
    }

    auto trunc = dyn_cast<TruncInst>(value);
    if (trunc && trunc->getSrcTy()->isIntegerTy(8))
    {
        return trunc->getOperand(0);
    }

    IRBuilder<> builder(insertBefore);
    auto zext = builder.CreateZExt(value, Type::getInt8Ty(value->getContext()));
    if (isa<Instruction>(zext) && isa<Instruction>(value))
    {
        dyn_cast<Instruction>(zext)->setDebugLoc(dyn_cast<Instruction>(value)->getDebugLoc());
    }
    return zext;
}

Value* PromoteBools::convertI8ToI1(Value* value, Instruction* insertBefore)
{
    if (!value->getType()->isIntegerTy(8))
    {
        return value;
    }

    auto zext = dyn_cast<ZExtInst>(value);
    if (zext && zext->getSrcTy()->isIntegerTy(1))
    {
        return zext->getOperand(0);
    }

    IRBuilder<> builder(insertBefore);
    auto trunc = builder.CreateTrunc(value, Type::getInt1Ty(value->getContext()));
    if (isa<Instruction>(trunc) && isa<Instruction>(value))
    {
        dyn_cast<Instruction>(trunc)->setDebugLoc(dyn_cast<Instruction>(value)->getDebugLoc());
    }
    return trunc;
}

Value* PromoteBools::castTo(Value* value, Type* desiredType, Instruction* insertBefore)
{
    if (value->getType() == desiredType)
    {
        return value;
    }

    if (value->getType()->isIntegerTy(8) && desiredType->isIntegerTy(1))
    {
        return convertI8ToI1(value, insertBefore);
    }
    else if (value->getType()->isIntegerTy(1) && desiredType->isIntegerTy(8))
    {
        return convertI1ToI8(value, insertBefore);
    }

    IRBuilder<> builder(insertBefore);
    return builder.CreateBitCast(value, desiredType);
}

void PromoteBools::cleanUp(Module& module)
{
    auto erase = [](auto v) {
        // Replace all v uses by undef. It allows us not to worry about
        // the order in which we delete unpromoted values.
        v->replaceAllUsesWith(UndefValue::get(v->getType()));
        v->eraseFromParent();
    };

    for (auto& it : promotedValuesCache)
    {
        // Workaround. If there's a ConstantExpr cast of a function/globalVariable
        // and the function/globalVariable has been promoted (so the cast has beed promoted too)
        // and the function/globalVariable is processed in this loop before the cast
        // then the unpromoted cast will be broken during processing the function/globalVariable
        // by replaceAllUsesWith in erase function. When the loop reaches the cast
        // it.first is broken and crash will occur.
        if (isa<ConstantExpr>(it.second))
        {
            continue;
        }

        if (auto function = dyn_cast<Function>(it.first))
        {
            swapNames(function, it.second);
            erase(function);
        }
        else if (auto globalVariable = dyn_cast<GlobalVariable>(it.first))
        {
            swapNames(globalVariable, it.second);
            erase(globalVariable);
        }
        else if (auto instruction = dyn_cast<Instruction>(it.first))
        {
            erase(instruction);
        }
    }

    SmallVector<Instruction*, 8> deadInstructions;
    for (auto& function : module)
    {
        for (auto& basicBlock : function)
        {
            for (auto it = basicBlock.rbegin(); it != basicBlock.rend(); ++it)
            {
                Instruction* instruction = &*it;
                if (isa<TruncInst>(instruction) || isa<ZExtInst>(instruction))
                {
                    if (instruction->hasNUses(0))
                    {
                        deadInstructions.push_back(instruction);
                    }
                }
            }
        }
    }

    for (auto& instruction : deadInstructions)
    {
        erase(instruction);
    }
}

//------------------------------------------------------------------------------
//
// Checking if type needs promotion
//
//------------------------------------------------------------------------------
bool PromoteBools::typeNeedsPromotion(Type* type, DenseSet<Type*> visitedTypes)
{
    if (!type || visitedTypes.count(type))
    {
        return false;
    }

    visitedTypes.insert(type);

    if (type->isIntegerTy(1))
    {
        return true;
    }
    else if (auto vectorType = dyn_cast<VectorType>(type))
    {
        return typeNeedsPromotion(vectorType->getElementType(), visitedTypes);
    }
    else if (auto pointerType = dyn_cast<PointerType>(type))
    {
        return typeNeedsPromotion(IGCLLVM::getNonOpaquePtrEltTy(type), visitedTypes);
    }
    else if (auto arrayType = dyn_cast<ArrayType>(type))
    {
        return typeNeedsPromotion(arrayType->getElementType(), visitedTypes);
    }
    else if (auto structType = dyn_cast<StructType>(type))
    {
        return std::any_of(structType->element_begin(), structType->element_end(), [this, &visitedTypes](const auto& element) {
            return typeNeedsPromotion(element, visitedTypes);
        });
    }
    else if (auto functionType = dyn_cast<FunctionType>(type))
    {
        if (typeNeedsPromotion(functionType->getReturnType(), visitedTypes))
        {
            return true;
        }
        return std::any_of(functionType->param_begin(), functionType->param_end(), [this, &visitedTypes](const auto& element) {
            return typeNeedsPromotion(element, visitedTypes);
        });
    }

    return false;
}

//------------------------------------------------------------------------------
//
// Promoting types
//
//------------------------------------------------------------------------------
Type* PromoteBools::getOrCreatePromotedType(Type* type)
{
    if (promotedTypesCache.count(type))
    {
        return promotedTypesCache[type];
    }

    Type* newType = type;
    if (type->isIntegerTy(1))
    {
        newType = Type::getInt8Ty(type->getContext());
    }
    else if (auto vectorType = dyn_cast<VectorType>(type))
    {
        newType = VectorType::get(
            getOrCreatePromotedType(vectorType->getElementType()),
            vectorType->getElementCount());
    }
    else if (auto pointerType = dyn_cast<PointerType>(type))
    {
        newType = PointerType::get(
            getOrCreatePromotedType(IGCLLVM::getNonOpaquePtrEltTy(type)),
            pointerType->getAddressSpace());
    }
    else if (auto arrayType = dyn_cast<ArrayType>(type))
    {
        newType = ArrayType::get(
            getOrCreatePromotedType(arrayType->getElementType()),
            arrayType->getNumElements());
    }
    else if (auto structType = dyn_cast<StructType>(type))
    {
        // Check if promotion is needed because otherwise new structures
        // will be created even if promotion is not needed. The other data
        // types do not have this problem.
        if (typeNeedsPromotion(structType))
        {
            // Create an opaque type to handle recursive types
            auto name = structType->hasName() ? structType->getName().str() : "";
            structType->setName(name + ".unpromoted");

            auto newStructType = StructType::create(type->getContext(), name);
            promotedTypesCache[type] = newStructType;

            // Promote and update struct elements
            SmallVector<Type*, 8> elements;
            for (const auto& element : structType->elements())
            {
                elements.push_back(getOrCreatePromotedType(element));
            }
            newStructType->setBody(elements, structType->isPacked());

            newType = newStructType;
        }
    }
    else if (auto functionType = dyn_cast<FunctionType>(type))
    {
        auto returnType = getOrCreatePromotedType(functionType->getReturnType());
        SmallVector<Type*, 8> arguments;
        for (auto& type : functionType->params())
        {
            arguments.push_back(getOrCreatePromotedType(type));
        }
        newType = FunctionType::get(returnType, arguments, functionType->isVarArg());
    }

    if (newType != type)
    {
        promotedTypesCache[type] = newType;
    }
    return newType;
}

//------------------------------------------------------------------------------
//
// Promoting values
//
//------------------------------------------------------------------------------
bool PromoteBools::wasPromoted(llvm::Value* value)
{
    return promotedValuesCache.count(value);
}

Value* PromoteBools::getOrCreatePromotedValue(Value* value)
{
    if (wasPromoted(value))
    {
        return promotedValuesCache[value];
    }

    Value* newValue = value;
    if (auto function = dyn_cast<Function>(value))
    {
        newValue = promoteFunction(function);
    }
    else if (auto globalVariable = dyn_cast<GlobalVariable>(value))
    {
        newValue = promoteGlobalVariable(globalVariable);
    }
    else if (auto constant = dyn_cast<Constant>(value))
    {
        newValue = promoteConstant(constant);
    }
    else if (auto addrSpaceCast = dyn_cast<AddrSpaceCastInst>(value))
    {
        newValue = promoteAddrSpaceCast(addrSpaceCast);
    }
    else if (auto alloca = dyn_cast<AllocaInst>(value))
    {
        newValue = promoteAlloca(alloca);
    }
    else if (auto bitcast = dyn_cast<BitCastInst>(value))
    {
        newValue = promoteBitCast(bitcast);
    }
    else if (auto call = dyn_cast<CallInst>(value))
    {
        newValue = promoteCall(call);
    }
    else if (auto extractValue = dyn_cast<ExtractValueInst>(value))
    {
        newValue = promoteExtractValue(extractValue);
    }
    else if (auto getElementPtr = dyn_cast<GetElementPtrInst>(value))
    {
        newValue = promoteGetElementPtr(getElementPtr);
    }
    else if (auto icmp = dyn_cast<ICmpInst>(value))
    {
        newValue = promoteICmp(icmp);
    }
    else if (auto inlineAsm = dyn_cast<InlineAsm>(value))
    {
        newValue = promoteInlineAsm(inlineAsm);
    }
    else if (auto insertValue = dyn_cast<InsertValueInst>(value))
    {
        newValue = promoteInsertValue(insertValue);
    }
    else if (auto load = dyn_cast<LoadInst>(value))
    {
        newValue = promoteLoad(load);
    }
    else if (auto phi = dyn_cast<PHINode>(value))
    {
        newValue = promotePHI(phi);
    }
    else if (auto store = dyn_cast<StoreInst>(value))
    {
        newValue = promoteStore(store);
    }
    else if (auto inttoptr = dyn_cast<IntToPtrInst>(value))
    {
        newValue = promoteIntToPtr(inttoptr);
    }
    else if (auto instruction = dyn_cast<Instruction>(value))
    {
        for (auto& operand : instruction->operands())
        {
            if (wasPromoted(operand))
            {
                auto promoted = getOrCreatePromotedValue(operand);
                if (auto zext = dyn_cast<ZExtInst>(promoted))
                {
                    instruction->replaceUsesOfWith(operand, zext->getOperand(0));
                }
                else
                {
                    auto insertBefore = instruction;
                    if (auto operandInst = dyn_cast<Instruction>(operand))
                    {
                        insertBefore = operandInst->getNextNode();
                        while (isa_and_nonnull<PHINode>(insertBefore))
                        {
                            insertBefore = insertBefore->getNextNode();
                        }
                        if (!insertBefore)
                        {
                            insertBefore = operandInst->getParent()->getTerminator();
                        }
                    }
                    auto trunc = convertI8ToI1(promoted, insertBefore);
                    if (isa<Instruction>(trunc) && isa<Instruction>(promoted))
                    {
                        cast<Instruction>(trunc)->setDebugLoc(cast<Instruction>(promoted)->getDebugLoc());
                    }
                    instruction->replaceUsesOfWith(operand, trunc);
                }
            }
        }

        if (value->getType()->isIntegerTy(1))
        {
            auto clone = instruction->clone();
            clone->insertBefore(instruction);
            instruction->replaceAllUsesWith(clone);
            newValue = convertI1ToI8(clone, instruction);
        }
    }

    if (newValue != value)
    {
        promotedValuesCache[value] = newValue;
        if (value->getType() == newValue->getType())
        {
            value->replaceAllUsesWith(newValue);
        }
        else
        {
            for (const auto& user : value->users())
            {
                if (!wasPromoted(user))
                {
                    promotionQueue.push(user);
                }
            }
        }
    }
    return newValue;
}

Function* PromoteBools::promoteFunction(Function* function)
{
    if (!function
        || function->isIntrinsic()
        || PreprocessSPVIR::isSPVIR(function->getName())
        || !typeNeedsPromotion(function->getFunctionType()))
    {
        return function;
    }

    auto newFunction = Function::Create(
        dyn_cast<FunctionType>(getOrCreatePromotedType(function->getFunctionType())),
        function->getLinkage(),
        function->getName() + ".promoted",
        function->getParent()
    );
    newFunction->setCallingConv(function->getCallingConv());
    newFunction->setAttributes(function->getAttributes());

    // Clone and fix function body
    if (!function->isDeclaration())
    {
        // Clone body
        SmallVector<ReturnInst*, 8> returns;
        ValueToValueMapTy argsMap;
        auto functionArgIt = function->arg_begin();
        auto newFunctionArgIt = newFunction->arg_begin();
        while (functionArgIt != function->arg_end())
        {
            newFunctionArgIt->setName(functionArgIt->getName());
            argsMap[&*functionArgIt++] = newFunctionArgIt++;
        }
        IGCLLVM::CloneFunctionInto(newFunction, function, argsMap,
            IGCLLVM::CloneFunctionChangeType::GlobalChanges, returns);

        // Fix body
        for (auto& arg : function->args())
        {
            if (!typeNeedsPromotion(arg.getType()) || arg.hasNUses(0))
            {
                continue;
            }

            auto newArg = argsMap[&arg];

            SmallVector<Instruction*, 8> userInstructions;
            for (auto user : newArg->users())
            {
                if (auto instruction = dyn_cast<Instruction>(user))
                {
                    userInstructions.push_back(instruction);
                }
            }

            auto cast = castTo(newArg, arg.getType(), newFunction->getEntryBlock().getFirstNonPHI());
            for (auto& instruction : userInstructions)
            {
                instruction->replaceUsesOfWith(newArg, cast);
            }
        }

        // Fix ret statements
        if (function->getReturnType() != newFunction->getReturnType())
        {
            for (auto& basicBlock : *newFunction)
            {
                auto terminator = basicBlock.getTerminator();
                if (auto ret = dyn_cast<ReturnInst>(terminator))
                {
                    auto cast = castTo(ret->getReturnValue(), newFunction->getReturnType(), ret);
                    IRBuilder<> builder(ret);
                    auto newRet = builder.CreateRet(cast);
                    if (auto inst = dyn_cast<Instruction>(cast))
                    {
                        newRet->setDebugLoc(inst->getDebugLoc());
                    }
                    ret->replaceAllUsesWith(newRet);
                    ret->eraseFromParent();
                }
            }
        }
    }

    return newFunction;
}

GlobalVariable* PromoteBools::promoteGlobalVariable(GlobalVariable* globalVariable)
{
    if (!globalVariable || !typeNeedsPromotion(globalVariable->getType()))
    {
        return globalVariable;
    }

    auto newGlobalVariable = new GlobalVariable(
        *globalVariable->getParent(),
        getOrCreatePromotedType(IGCLLVM::getNonOpaquePtrEltTy(globalVariable->getType())),
        globalVariable->isConstant(),
        globalVariable->getLinkage(),
        promoteConstant(globalVariable->getInitializer()),
        globalVariable->getName() + ".promoted",
        nullptr,
        GlobalValue::ThreadLocalMode::NotThreadLocal,
        globalVariable->getType()->getPointerAddressSpace());

    // Clone metadatas
    SmallVector<std::pair<unsigned, MDNode*>, 8> metadatas;
    globalVariable->getAllMetadata(metadatas);
    for (const auto& metadata : metadatas)
    {
        newGlobalVariable->addMetadata(metadata.first, *metadata.second);
    }
    return newGlobalVariable;
}

Constant* PromoteBools::promoteConstant(Constant* constant)
{
    if (!constant)
    {
        return nullptr;
    }

    if (isa<UndefValue>(constant))
    {
        return UndefValue::get(getOrCreatePromotedType(constant->getType()));
    }
    else if (isa<ConstantAggregateZero>(constant))
    {
        return ConstantAggregateZero::get(getOrCreatePromotedType(constant->getType()));
    }
    else if (auto constantInteger = dyn_cast<ConstantInt>(constant))
    {
        if (!typeNeedsPromotion(constantInteger->getType()))
        {
            return constant;
        }

        return ConstantInt::get(Type::getInt8Ty(constant->getContext()), constant->isOneValue() ? 1 : 0);
    }
    else if (auto constantPointerNull = dyn_cast<ConstantPointerNull>(constant))
    {
        if (!typeNeedsPromotion(IGCLLVM::getNonOpaquePtrEltTy(constantPointerNull->getType())))
        {
            return constant;
        }

        auto newPointerElementType = getOrCreatePromotedType(IGCLLVM::getNonOpaquePtrEltTy(constantPointerNull->getType()));
        return ConstantPointerNull::get(PointerType::get(newPointerElementType, constantPointerNull->getType()->getAddressSpace()));
    }
    else if (auto constantVector = dyn_cast<ConstantVector>(constant))
    {
        if (!typeNeedsPromotion(constantVector->getType()))
        {
            return constant;
        }

        SmallVector<Constant*, 8> values;
        for (unsigned i = 0; i < constantVector->getType()->getNumElements(); ++i)
        {
            values.push_back(promoteConstant(constantVector->getAggregateElement(i)));
        }
        return ConstantVector::get(values);
    }
    else if (auto constantArray = dyn_cast<ConstantArray>(constant))
    {
        if (!typeNeedsPromotion(constantArray->getType()))
        {
            return constant;
        }

        SmallVector<Constant*, 8> values;
        for (unsigned i = 0; i < constantArray->getType()->getNumElements(); ++i)
        {
            values.push_back(promoteConstant(constantArray->getAggregateElement(i)));
        }

        auto newType = getOrCreatePromotedType(constantArray->getType());
        return ConstantArray::get(dyn_cast<ArrayType>(newType), values);
    }
    else if (auto constantStruct = dyn_cast<ConstantStruct>(constant))
    {
        if (!typeNeedsPromotion(constantStruct->getType()))
        {
            return constant;
        }

        SmallVector<Constant*, 8> values;
        for (unsigned i = 0; i < constantStruct->getType()->getNumElements(); ++i)
        {
            values.push_back(promoteConstant(constantStruct->getAggregateElement(i)));
        }

        auto newType = getOrCreatePromotedType(constantStruct->getType());
        return ConstantStruct::get(dyn_cast<StructType>(newType), values);
    }
    else if (auto constantExpr = dyn_cast<ConstantExpr>(constant))
    {
        if (constantExpr->isCast() && isa<GlobalValue>(constantExpr->getOperand(0)))
        {
            return ConstantExpr::getCast(
                constantExpr->getOpcode(),
                dyn_cast<Constant>(getOrCreatePromotedValue(constantExpr->getOperand(0))),
                constantExpr->getType()
            );
        }
        return constantExpr;
    }

    return constant;
}

AddrSpaceCastInst* PromoteBools::promoteAddrSpaceCast(AddrSpaceCastInst* addrSpaceCast)
{
    if (!addrSpaceCast || (!wasPromotedAnyOf(addrSpaceCast->operands()) && !typeNeedsPromotion(addrSpaceCast->getDestTy())))
    {
        return addrSpaceCast;
    }

    auto newAddrSpaceCast = new AddrSpaceCastInst(
        getOrCreatePromotedValue(addrSpaceCast->getOperand(0)),
        getOrCreatePromotedType(addrSpaceCast->getDestTy()),
        "",
        addrSpaceCast
    );
    newAddrSpaceCast->setDebugLoc(addrSpaceCast->getDebugLoc());
    return newAddrSpaceCast;
}

AllocaInst* PromoteBools::promoteAlloca(AllocaInst* alloca)
{
    if (!alloca || !typeNeedsPromotion(alloca->getAllocatedType()))
    {
        return alloca;
    }

    auto newAlloca = new AllocaInst(
        getOrCreatePromotedType(alloca->getAllocatedType()),
        alloca->getType()->getAddressSpace(),
        alloca->isArrayAllocation() ? alloca->getArraySize() : nullptr,
        "",
        alloca
    );
    newAlloca->setAlignment(IGCLLVM::getAlign(*alloca));
    newAlloca->setDebugLoc(alloca->getDebugLoc());
    return newAlloca;
}

Value* PromoteBools::promoteBitCast(BitCastInst* bitcast)
{
    if (!bitcast || (!wasPromotedAnyOf(bitcast->operands()) && !typeNeedsPromotion(bitcast->getDestTy())))
    {
        return bitcast;
    }

    auto newType = getOrCreatePromotedType(bitcast->getDestTy());
    if (bitcast->getSrcTy() == newType)
    {
        return bitcast->getOperand(0);
    }

    auto newBitcast = new BitCastInst(
        getOrCreatePromotedValue(bitcast->getOperand(0)),
        getOrCreatePromotedType(bitcast->getDestTy()),
        "",
        bitcast
    );
    newBitcast->setDebugLoc(bitcast->getDebugLoc());
    return newBitcast;
}

CallInst* PromoteBools::promoteIndirectCallOrInlineAsm(CallInst* call)
{
    IGC_ASSERT(call->isIndirectCall() || call->isInlineAsm());
    auto operand = call->getCalledOperand();
    auto functionType = call->getFunctionType();

    if (!wasPromotedAnyOf(call->args()) && !typeNeedsPromotion(functionType))
    {
        return call;
    }

    SmallVector<Value*, 8> newCallArguments;
    for (auto& arg : call->args())
    {
        newCallArguments.push_back(convertI1ToI8(getOrCreatePromotedValue(arg), call));
    }

    auto newCall = CallInst::Create(
        llvm::cast<llvm::FunctionType>(getOrCreatePromotedType(functionType)),
        getOrCreatePromotedValue(operand),
        newCallArguments,
        "",
        call
    );
    newCall->setCallingConv(call->getCallingConv());
    newCall->setAttributes(call->getAttributes());
    newCall->setDebugLoc(call->getDebugLoc());
    return newCall;
}

CallInst* PromoteBools::promoteCall(CallInst* call)
{
    if (!call)
    {
        return nullptr;
    }

    if (call->isIndirectCall() || call->isInlineAsm())
    {
        return promoteIndirectCallOrInlineAsm(call);
    }

    auto function = call->getCalledFunction();
    auto functionType = call->getFunctionType();

    if (!function || (!wasPromotedAnyOf(call->args()) && !typeNeedsPromotion(functionType)))
    {
        return call;
    }

    SmallVector<Value*, 8> newCallArguments;
    auto newFunction = function;

    // If the function wasn't promoted and therefore the signature is not changed
    // then we must convert promoted operands to the original types.
    if (!wasPromoted(function))
    {
        if (!wasPromotedAnyOf(call->args()))
        {
            return call;
        }

        for (auto& arg : call->args())
        {
            Value* newArg = arg;
            if (wasPromoted(newArg))
            {
                newArg = getOrCreatePromotedValue(newArg);
                if (arg->getType()->isIntegerTy(1) && !newArg->getType()->isIntegerTy(1))
                {
                    newArg = convertI8ToI1(newArg, call);
                }
            }

            newCallArguments.push_back(newArg);
        }
    }
    else
    {
        if (wasPromoted(newFunction) || typeNeedsPromotion(functionType))
        {
            newFunction = dyn_cast<Function>(getOrCreatePromotedValue(function));
        }

        for (auto& arg : call->args())
        {
            newCallArguments.push_back(convertI1ToI8(getOrCreatePromotedValue(arg), call));
        }
    }

    auto newCall = CallInst::Create(
        newFunction->getFunctionType(),
        newFunction,
        newCallArguments,
        "",
        call
    );
    newCall->setCallingConv(call->getCallingConv());
    newCall->setAttributes(call->getAttributes());
    newCall->setDebugLoc(call->getDebugLoc());
    return newCall;
}

ExtractValueInst* PromoteBools::promoteExtractValue(ExtractValueInst* extractValue)
{
    if (!extractValue)
    {
        return nullptr;
    }

    auto aggregateOp = extractValue->getAggregateOperand();
    if (!wasPromotedAnyOf(extractValue->operands()) && !typeNeedsPromotion(aggregateOp->getType()))
    {
        return extractValue;
    }

    auto newExtractValue = ExtractValueInst::Create(
        getOrCreatePromotedValue(aggregateOp),
        extractValue->getIndices(),
        "",
        extractValue
    );
    newExtractValue->setDebugLoc(extractValue->getDebugLoc());
    return newExtractValue;
}

GetElementPtrInst* PromoteBools::promoteGetElementPtr(GetElementPtrInst* getElementPtr)
{
    if (!getElementPtr || (!wasPromotedAnyOf(getElementPtr->operands()) && !typeNeedsPromotion(getElementPtr->getResultElementType())))
    {
        return getElementPtr;
    }

    auto promotedOperand = getOrCreatePromotedValue(getElementPtr->getPointerOperand());
    auto indices = SmallVector<Value*, 8>(getElementPtr->idx_begin(), getElementPtr->idx_end());

    auto newGetElementPtr = GetElementPtrInst::Create(
        IGCLLVM::getNonOpaquePtrEltTy(promotedOperand->getType()),
        promotedOperand,
        indices,
        "",
        getElementPtr
    );
    newGetElementPtr->setDebugLoc(getElementPtr->getDebugLoc());
    newGetElementPtr->setIsInBounds(getElementPtr->isInBounds());
    return newGetElementPtr;
}

ICmpInst* PromoteBools::promoteICmp(ICmpInst* icmp)
{
    if (!icmp)
    {
        return nullptr;
    }

    auto op0 = icmp->getOperand(0);
    auto op1 = icmp->getOperand(1);

    if (!wasPromotedAnyOf(icmp->operands()) && !typeNeedsPromotion(op0->getType()))
    {
        return icmp;
    }

    auto promotedOp0 = convertI1ToI8(getOrCreatePromotedValue(op0), icmp);
    auto promotedOp1 = convertI1ToI8(getOrCreatePromotedValue(op1), icmp);

    auto newICmp = new ICmpInst(
        icmp,
        icmp->getPredicate(),
        promotedOp0,
        promotedOp1,
        ""
    );
    newICmp->setDebugLoc(icmp->getDebugLoc());
    return newICmp;
}

InlineAsm* PromoteBools::promoteInlineAsm(InlineAsm* inlineAsm)
{
    if (!inlineAsm || !typeNeedsPromotion(inlineAsm->getFunctionType()))
    {
        return inlineAsm;
    }

    return InlineAsm::get(
        dyn_cast<FunctionType>(getOrCreatePromotedType(inlineAsm->getFunctionType())),
        inlineAsm->getAsmString(),
        inlineAsm->getConstraintString(),
        inlineAsm->hasSideEffects(),
        inlineAsm->isAlignStack(),
        inlineAsm->getDialect()
    );
}

InsertValueInst* PromoteBools::promoteInsertValue(InsertValueInst* insertValue)
{
    if (!insertValue)
    {
        return nullptr;
    }

    auto aggregateOp = insertValue->getAggregateOperand();
    auto insertedValueOp = insertValue->getInsertedValueOperand();
    if (!wasPromotedAnyOf(insertValue->operands()) && !typeNeedsPromotion(aggregateOp->getType()) && !typeNeedsPromotion(insertedValueOp->getType()))
    {
        return insertValue;
    }

    auto newAggregateOp = typeNeedsPromotion(aggregateOp->getType()) ? getOrCreatePromotedValue(aggregateOp) : aggregateOp;
    auto newInsertedValueOp = typeNeedsPromotion(insertedValueOp->getType()) ? getOrCreatePromotedValue(insertedValueOp) : insertedValueOp;

    auto newInsertValue = InsertValueInst::Create(
        newAggregateOp,
        newInsertedValueOp,
        insertValue->getIndices(),
        "",
        insertValue
    );
    newInsertValue->setDebugLoc(insertValue->getDebugLoc());
    return newInsertValue;
}

LoadInst* PromoteBools::promoteLoad(LoadInst* load)
{
    if (!load)
    {
        return nullptr;
    }

    auto src = load->getOperand(0);

    if (!wasPromotedAnyOf(load->operands()) && !typeNeedsPromotion(src->getType()))
    {
        return load;
    }

    auto newSrc = getOrCreatePromotedValue(src);
    auto newLoad = new LoadInst(
        IGCLLVM::getNonOpaquePtrEltTy(newSrc->getType()),
        newSrc,
        "",
        load
    );
    newLoad->setAlignment(IGCLLVM::getAlign(*load));
    newLoad->setDebugLoc(load->getDebugLoc());
    return newLoad;
}

llvm::PHINode* PromoteBools::promotePHI(llvm::PHINode* phi)
{
    if (!phi || visitedPHINodes.count(phi) || (!wasPromotedAnyOf(phi->operands()) && !typeNeedsPromotion(phi->getType())))
    {
        return phi;
    }

    visitedPHINodes.insert(phi);

    auto newPhi = PHINode::Create(
        getOrCreatePromotedType(phi->getType()),
        phi->getNumIncomingValues(),
        "",
        phi
    );

    for (unsigned i = 0; i < phi->getNumIncomingValues(); ++i)
    {
        auto newIncomingValue = phi->getIncomingValue(i);
        if (wasPromoted(newIncomingValue) || typeNeedsPromotion(newIncomingValue->getType()))
        {
            newIncomingValue = getOrCreatePromotedValue(newIncomingValue);
            if (newIncomingValue->getType()->isIntegerTy(1))
            {
                newIncomingValue = convertI1ToI8(newIncomingValue, phi->getIncomingBlock(i)->getTerminator());
            }
        }

        newPhi->addIncoming(
            newIncomingValue,
            phi->getIncomingBlock(i)
        );
    }
    newPhi->setDebugLoc(phi->getDebugLoc());
    return newPhi;
}

StoreInst* PromoteBools::promoteStore(StoreInst* store)
{
    if (!store)
    {
        return nullptr;
    }

    auto src = store->getOperand(0);
    auto dst = store->getOperand(1);

    if (!wasPromotedAnyOf(store->operands()) && !typeNeedsPromotion(src->getType()) && !typeNeedsPromotion(dst->getType()))
    {
        return store;
    }

    auto newStore = new StoreInst(
        getOrCreatePromotedValue(src),
        getOrCreatePromotedValue(dst),
        store->isVolatile(),
        store
    );
    newStore->setAlignment(IGCLLVM::getAlign(*store));
    newStore->setDebugLoc(store->getDebugLoc());
    return newStore;
}

IntToPtrInst* PromoteBools::promoteIntToPtr(IntToPtrInst* inttoptr)
{
    if (!inttoptr || (!wasPromotedAnyOf(inttoptr->operands()) && !typeNeedsPromotion(inttoptr->getDestTy())))
    {
        return inttoptr;
    }

    auto newIntToPtr = new IntToPtrInst(
        getOrCreatePromotedValue(inttoptr->getOperand(0)),
        getOrCreatePromotedType(inttoptr->getDestTy()),
        "",
        inttoptr
    );
    newIntToPtr->setDebugLoc(inttoptr->getDebugLoc());
    return newIntToPtr;
}
