//===- SPIRVToOCL.cpp - Transform SPIR-V builtins to OCL builtins------===//
//
//                     The LLVM/SPIRV Translator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
// Copyright (c) 2014 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal with 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:
//
// Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimers.
// Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimers in the documentation
// and/or other materials provided with the distribution.
// Neither the names of Advanced Micro Devices, Inc., nor the names of its
// contributors may be used to endorse or promote products derived from this
// Software without specific prior written permission.
// 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
// CONTRIBUTORS 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 WITH
// THE SOFTWARE.
//
//===----------------------------------------------------------------------===//
//
// This file implements common transform of SPIR-V builtins to OCL builtins.
//
// Some of the visit functions are translations to OCL2.0 builtins, but they
// are currently used also for OCL1.2, so theirs implementations are placed
// in this pass as a common functionality for both versions.
//
//===----------------------------------------------------------------------===//

#include "SPIRVToOCL.h"
#include "llvm/IR/TypedPointerType.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Support/CommandLine.h"

#define DEBUG_TYPE "spvtocl"

namespace SPIRV {

void SPIRVToOCLBase::visitCallInst(CallInst &CI) {
  LLVM_DEBUG(dbgs() << "[visistCallInst] " << CI << '\n');
  auto *F = CI.getCalledFunction();
  if (!F)
    return;

  OCLExtOpKind ExtOp;
  if (isSPIRVOCLExtInst(&CI, &ExtOp)) {
    switch (ExtOp) {
    case OpenCLLIB::Vloadn:
    case OpenCLLIB::Vloada_halfn:
    case OpenCLLIB::Vload_halfn:
      visitCallSPIRVVLoadn(&CI, ExtOp);
      break;
    case OpenCLLIB::Vstoren:
    case OpenCLLIB::Vstore_halfn:
    case OpenCLLIB::Vstorea_halfn:
    case OpenCLLIB::Vstore_half_r:
    case OpenCLLIB::Vstore_halfn_r:
    case OpenCLLIB::Vstorea_halfn_r:
      visitCallSPIRVVStore(&CI, ExtOp);
      break;
    case OpenCLLIB::Printf: {
      // TODO: Lower the printf instruction with the non-constant address space
      // format string to suitable for OpenCL representation
      auto *PT = dyn_cast<PointerType>(CI.getOperand(0)->getType());
      if (PT && PT->getAddressSpace() == SPIR::TypeAttributeEnum::ATTR_CONST)
        visitCallSPIRVPrintf(&CI, ExtOp);
      break;
    }
    default:
      visitCallSPIRVOCLExt(&CI, ExtOp);
      break;
    }
    return;
  }

  auto MangledName = F->getName();
  StringRef DemangledName;
  Op OC = OpNop;
  SPIRVBuiltinVariableKind BuiltinKind = SPIRVBuiltinVariableKind::BuiltInMax;
  if (!oclIsBuiltin(MangledName, DemangledName) ||
      ((OC = getSPIRVFuncOC(DemangledName)) == OpNop &&
       !getSPIRVBuiltin(DemangledName.str(), BuiltinKind)))
    return;
  LLVM_DEBUG(dbgs() << "DemangledName = " << DemangledName.str() << '\n'
                    << "OpCode = " << OC << '\n'
                    << "BuiltinKind = " << BuiltinKind << '\n');

  if (BuiltinKind != SPIRVBuiltinVariableKind::BuiltInMax) {
    if (static_cast<uint32_t>(BuiltinKind) >=
            internal::BuiltInSubDeviceIDINTEL &&
        static_cast<uint32_t>(BuiltinKind) <=
            internal::BuiltInGlobalHWThreadIDINTEL)
      return;

    visitCallSPIRVBuiltin(&CI, BuiltinKind);
    return;
  }

  if (OC == OpImageQuerySize || OC == OpImageQuerySizeLod) {
    visitCallSPIRVImageQuerySize(&CI);
    return;
  }
  if (OC == OpMemoryBarrier) {
    visitCallSPIRVMemoryBarrier(&CI);
    return;
  }
  if (OC == OpControlBarrier) {
    visitCallSPIRVControlBarrier(&CI);
  }
  if (isSplitBarrierINTELOpCode(OC)) {
    visitCallSPIRVSplitBarrierINTEL(&CI, OC);
    return;
  }
  if (isAtomicOpCode(OC)) {
    visitCallSPIRVAtomicBuiltin(&CI, OC);
    return;
  }
  if (isGroupOpCode(OC) || isGroupNonUniformOpcode(OC)) {
    visitCallSPIRVGroupBuiltin(&CI, OC);
    return;
  }
  if (isPipeOpCode(OC)) {
    visitCallSPIRVPipeBuiltin(&CI, OC);
    return;
  }
  if (isMediaBlockINTELOpcode(OC)) {
    visitCallSPIRVImageMediaBlockBuiltin(&CI, OC);
    return;
  }
  if (isIntelSubgroupOpCode(OC)) {
    visitCallSPIRVSubgroupINTELBuiltIn(&CI, OC);
    return;
  }
  if (isSubgroupAvcINTELEvaluateOpcode(OC)) {
    visitCallSPIRVAvcINTELEvaluateBuiltIn(&CI, OC);
    return;
  }
  if (isSubgroupAvcINTELInstructionOpCode(OC)) {
    visitCallSPIRVAvcINTELInstructionBuiltin(&CI, OC);
    return;
  }
  if (OC == OpBuildNDRange) {
    visitCallBuildNDRangeBuiltIn(&CI, OC, DemangledName);
    return;
  }
  if (OC == OpGenericCastToPtr) {
    visitCallGenericCastToPtrBuiltIn(&CI, OC);
    return;
  }
  if (OC == OpGenericCastToPtrExplicit) {
    visitCallGenericCastToPtrExplicitBuiltIn(&CI, OC);
    return;
  }
  if (isCvtOpCode(OC)) {
    visitCallSPIRVCvtBuiltin(&CI, OC, DemangledName);
    return;
  }
  if (OC == OpGroupAsyncCopy) {
    visitCallAsyncWorkGroupCopy(&CI, OC);
    return;
  }
  if (OC == OpGroupWaitEvents) {
    visitCallGroupWaitEvents(&CI, OC);
    return;
  }
  if (OC == OpImageSampleExplicitLod) {
    visitCallSPIRVImageSampleExplicitLodBuiltIn(&CI, OC);
    return;
  }
  if (OC == OpImageWrite) {
    visitCallSPIRVImageWriteBuiltIn(&CI, OC);
    return;
  }
  if (OC == OpImageRead) {
    visitCallSPIRVImageReadBuiltIn(&CI, OC);
    return;
  }
  if (OC == OpImageQueryOrder || OC == OpImageQueryFormat) {
    visitCallSPIRVImageQueryBuiltIn(&CI, OC);
    return;
  }
  if (OC == OpEnqueueKernel) {
    visitCallSPIRVEnqueueKernel(&CI, OC);
    return;
  }
  if (OC == OpGenericPtrMemSemantics) {
    visitCallSPIRVGenericPtrMemSemantics(&CI);
    return;
  }
  // Check if OC is OpenCL relational builtin except bitselect and select.
  auto IsOclRelationalOp = [](Op OC) {
    return isUnaryPredicateOpCode(OC) || OC == OpOrdered || OC == OpUnordered ||
           OC == OpFOrdEqual || OC == OpFUnordNotEqual ||
           OC == OpFOrdGreaterThan || OC == OpFOrdGreaterThanEqual ||
           OC == OpFOrdLessThan || OC == OpFOrdLessThanEqual ||
           OC == OpFOrdNotEqual;
  };
  if (IsOclRelationalOp(OC)) {
    if (OC == OpAny || OC == OpAll)
      visitCallSPIRVAnyAll(&CI, OC);
    else
      visitCallSPIRVRelational(&CI, OC);
    return;
  }
  if (OC == OpReadClockKHR) {
    visitCallSPIRVReadClockKHR(&CI);
    return;
  }
  if (OC == OpConvertFToBF16INTEL || OC == OpConvertBF16ToFINTEL) {
    visitCallSPIRVBFloat16Conversions(&CI, OC);
    return;
  }
  if (OC == OpSDot || OC == OpUDot || OC == OpSUDot || OC == OpSDotAccSat ||
      OC == OpUDotAccSat || OC == OpSUDotAccSat) {
    visitCallSPIRVDot(&CI, OC, DemangledName);
    return;
  }
  if (OCLSPIRVBuiltinMap::rfind(OC))
    visitCallSPIRVBuiltin(&CI, OC);
}

void SPIRVToOCLBase::visitCastInst(CastInst &Cast) {
  if (!isa<ZExtInst>(Cast) && !isa<SExtInst>(Cast) && !isa<TruncInst>(Cast) &&
      !isa<FPTruncInst>(Cast) && !isa<FPExtInst>(Cast) &&
      !isa<FPToUIInst>(Cast) && !isa<FPToSIInst>(Cast) &&
      !isa<UIToFPInst>(Cast) && !isa<SIToFPInst>(Cast))
    return;

  Type const *SrcTy = Cast.getSrcTy();
  Type *DstVecTy = Cast.getDestTy();
  // Leave scalar casts as is. Skip boolean vector casts becase there
  // are no suitable OCL built-ins.
  if (!DstVecTy->isVectorTy() || SrcTy->getScalarSizeInBits() == 1 ||
      DstVecTy->getScalarSizeInBits() == 1)
    return;

  // We don't have OpenCL builtins for 4-bit conversions.
  if (DstVecTy->getScalarSizeInBits() == 4 || SrcTy->getScalarSizeInBits() == 4)
    return;

  // Assemble built-in name -> convert_gentypeN
  std::string CastBuiltInName(kOCLBuiltinName::ConvertPrefix);
  // Check if this is 'floating point -> unsigned integer' cast
  CastBuiltInName += mapLLVMTypeToOCLType(DstVecTy, !isa<FPToUIInst>(Cast));

  // Replace LLVM conversion instruction with call to conversion built-in
  OCLBuiltinFuncMangleInfo Mangle;
  // It does matter if the source is unsigned integer or not. SExt is for
  // signed source, ZExt and UIToFPInst are for unsigned source.
  if (isa<ZExtInst>(Cast) || isa<UIToFPInst>(Cast))
    Mangle.addUnsignedArg(0);

  AttributeList Attributes;
  CallInst *Call =
      addCallInst(M, CastBuiltInName, DstVecTy, Cast.getOperand(0), &Attributes,
                  &Cast, &Mangle, Cast.getName(), false);
  Cast.replaceAllUsesWith(Call);
  Cast.eraseFromParent();
}

void SPIRVToOCLBase::visitCallSPIRVImageQuerySize(CallInst *CI) {
  // Get image type
  Type *ImgTy = getCallValueType(CI, 0);
  auto Desc = getImageDescriptor(ImgTy);
  unsigned ImgDim = getImageDimension(Desc.Dim);
  bool ImgArray = Desc.Arrayed;

  AttributeList Attributes = CI->getCalledFunction()->getAttributes();
  OCLBuiltinFuncMangleInfo Mangle;
  Mangle.getTypeMangleInfo(0).PointerTy = ImgTy;
  Type *Int32Ty = Type::getInt32Ty(*Ctx);
  Instruction *GetImageSize = nullptr;

  if (ImgDim == 1) {
    // OpImageQuerySize from non-arrayed 1d image is always translated
    // into get_image_width returning scalar argument
    GetImageSize = addCallInst(M, kOCLBuiltinName::GetImageWidth, Int32Ty,
                               CI->getArgOperand(0), &Attributes, CI, &Mangle,
                               CI->getName(), false);
    // The width of integer type returning by OpImageQuerySize[Lod] may
    // differ from i32
    if (CI->getType()->getScalarType() != Int32Ty) {
      GetImageSize = CastInst::CreateIntegerCast(
          GetImageSize, CI->getType()->getScalarType(), false, CI->getName(),
          CI->getIterator());
    }
  } else {
    assert((ImgDim == 2 || ImgDim == 3) && "invalid image type");
    assert(CI->getType()->isVectorTy() &&
           "this code can handle vector result type only");
    // get_image_dim returns int2 and int4 for 2d and 3d images respecitvely.
    const unsigned ImgDimRetEls = ImgDim == 2 ? 2 : 4;
    VectorType *RetTy = FixedVectorType::get(Int32Ty, ImgDimRetEls);
    GetImageSize = addCallInst(M, kOCLBuiltinName::GetImageDim, RetTy,
                               CI->getArgOperand(0), &Attributes, CI, &Mangle,
                               CI->getName(), false);
    // The width of integer type returning by OpImageQuerySize[Lod] may
    // differ from i32
    if (CI->getType()->getScalarType() != Int32Ty) {
      GetImageSize = CastInst::CreateIntegerCast(
          GetImageSize,
          FixedVectorType::get(
              CI->getType()->getScalarType(),
              cast<FixedVectorType>(GetImageSize->getType())->getNumElements()),
          false, CI->getName(), CI->getIterator());
    }
  }

  if (ImgArray || ImgDim == 3) {
    auto *VecTy = cast<FixedVectorType>(CI->getType());
    const unsigned ImgQuerySizeRetEls = VecTy->getNumElements();

    if (ImgDim == 1) {
      // get_image_width returns scalar result while OpImageQuerySize
      // for image1d_array_t returns <2 x i32> vector.
      assert(ImgQuerySizeRetEls == 2 &&
             "OpImageQuerySize[Lod] must return <2 x iN> vector type");
      GetImageSize = InsertElementInst::Create(
          PoisonValue::get(VecTy), GetImageSize, ConstantInt::get(Int32Ty, 0),
          CI->getName(), CI->getIterator());
    } else {
      // get_image_dim and OpImageQuerySize returns different vector
      // types for arrayed and 3d images.
      SmallVector<Constant *, 4> MaskEls;
      for (unsigned Idx = 0; Idx < ImgQuerySizeRetEls; ++Idx)
        MaskEls.push_back(ConstantInt::get(Int32Ty, Idx));
      Constant *Mask = ConstantVector::get(MaskEls);

      GetImageSize = new ShuffleVectorInst(
          GetImageSize, PoisonValue::get(GetImageSize->getType()), Mask,
          CI->getName(), CI->getIterator());
    }
  }

  if (ImgArray) {
    assert((ImgDim == 1 || ImgDim == 2) && "invalid image array type");
    // Insert get_image_array_size to the last position of the resulting vector.
    auto *VecTy = cast<FixedVectorType>(CI->getType());
    Type *SizeTy =
        Type::getIntNTy(*Ctx, M->getDataLayout().getPointerSizeInBits(0));
    Instruction *GetImageArraySize = addCallInst(
        M, kOCLBuiltinName::GetImageArraySize, SizeTy, CI->getArgOperand(0),
        &Attributes, CI, &Mangle, CI->getName(), false);
    // The width of integer type returning by OpImageQuerySize[Lod] may
    // differ from size_t which is returned by get_image_array_size
    if (GetImageArraySize->getType() != VecTy->getElementType()) {
      GetImageArraySize = CastInst::CreateIntegerCast(
          GetImageArraySize, VecTy->getElementType(), false, CI->getName(),
          CI->getIterator());
    }
    GetImageSize = InsertElementInst::Create(
        GetImageSize, GetImageArraySize,
        ConstantInt::get(Int32Ty, VecTy->getNumElements() - 1), CI->getName(),
        CI->getIterator());
  }

  assert(GetImageSize && "must not be null");
  CI->replaceAllUsesWith(GetImageSize);
  CI->eraseFromParent();
}

std::string SPIRVToOCLBase::getUniformArithmeticBuiltinName(CallInst *CI,
                                                            Op OC) {
  assert(isUniformArithmeticOpCode(OC) &&
         "Not intended to handle other than uniform arithmetic opcodes!");
  auto FuncName = OCLSPIRVBuiltinMap::rmap(OC);
  std::string Prefix = getGroupBuiltinPrefix(CI);
  std::string Op = FuncName;
  Op.erase(0, strlen(kSPIRVName::GroupPrefix));
  // unsigned prefix cannot be removed yet, as it is necessary to properly
  // mangle the function
  bool Unsigned = Op.front() == 'u';
  if (!Unsigned)
    Op = Op.erase(0, 1);

  std::string GroupOp;
  auto GO = getArgAs<spv::GroupOperation>(CI, 1);
  switch (GO) {
  case GroupOperationReduce:
    GroupOp = "reduce";
    break;
  case GroupOperationInclusiveScan:
    GroupOp = "scan_inclusive";
    break;
  case GroupOperationExclusiveScan:
    GroupOp = "scan_exclusive";
    break;
  default:
    llvm_unreachable("Unsupported group operation!");
    break;
  }
  return Prefix + kSPIRVName::GroupPrefix + GroupOp + "_" + Op;
}

std::string SPIRVToOCLBase::getNonUniformArithmeticBuiltinName(CallInst *CI,
                                                               Op OC) {
  assert(isNonUniformArithmeticOpCode(OC) &&
         "Not intended to handle other than non uniform arithmetic opcodes!");
  std::string Prefix = getGroupBuiltinPrefix(CI);
  assert((Prefix == kOCLBuiltinName::SubPrefix) &&
         "Workgroup scope is not supported for OpGroupNonUniform opcodes");
  auto FuncName = OCLSPIRVBuiltinMap::rmap(OC);
  std::string Op = FuncName;
  Op.erase(0, strlen(kSPIRVName::GroupNonUniformPrefix));

  if (!isGroupLogicalOpCode(OC)) {
    // unsigned prefix cannot be removed yet, as it is necessary to properly
    // mangle the function
    const char Sign = Op.front();
    bool Signed = (Sign == 'i' || Sign == 'f' || Sign == 's');
    if (Signed)
      Op = Op.erase(0, 1);
    else
      assert((Sign == 'u') && "Incorrect sign!");
  } else { // LogicalOpcode
    assert(
        (Op == "logical_iand" || Op == "logical_ior" || Op == "logical_ixor") &&
        "Incorrect logical operation");
    Op = Op.erase(8, 1);
  }

  std::string GroupOp;
  std::string GroupPrefix = kSPIRVName::GroupNonUniformPrefix;
  auto GO = getArgAs<spv::GroupOperation>(CI, 1);
  switch (GO) {
  case GroupOperationReduce:
    GroupOp = "reduce";
    break;
  case GroupOperationInclusiveScan:
    GroupOp = "scan_inclusive";
    break;
  case GroupOperationExclusiveScan:
    GroupOp = "scan_exclusive";
    break;
  case GroupOperationClusteredReduce:
    GroupOp = "clustered_reduce";
    // OpenCL clustered builtin has no non_uniform prefix, ex.
    // sub_group_reduce_clustered_logical_and
    GroupPrefix = kSPIRVName::GroupPrefix;
    break;
  default:
    llvm_unreachable("Unsupported group operation!");
    break;
  }

  return Prefix + GroupPrefix + GroupOp + "_" + Op;
}

std::string SPIRVToOCLBase::getBallotBuiltinName(CallInst *CI, Op OC) {
  assert((OC == OpGroupNonUniformBallotBitCount) &&
         "Not inteded to handle other opcodes than "
         "OpGroupNonUniformBallotBitCount!");
  std::string Prefix = getGroupBuiltinPrefix(CI);
  assert(
      (Prefix == kOCLBuiltinName::SubPrefix) &&
      "Workgroup scope is not supported for OpGroupNonUniformBallotBitCount");
  std::string GroupOp;
  auto GO = getArgAs<spv::GroupOperation>(CI, 1);
  switch (GO) {
  case GroupOperationReduce:
    GroupOp = "bit_count";
    break;
  case GroupOperationInclusiveScan:
    GroupOp = "inclusive_scan";
    break;
  case GroupOperationExclusiveScan:
    GroupOp = "exclusive_scan";
    break;
  default:
    llvm_unreachable("Unsupported group operation!");
    break;
  }
  return Prefix + kSPIRVName::GroupPrefix + "ballot_" + GroupOp;
}

std::string SPIRVToOCLBase::getRotateBuiltinName(CallInst *CI, Op OC) {
  assert((OC == OpGroupNonUniformRotateKHR) &&
         "Not intended to handle other opcodes");
  std::string Prefix = getGroupBuiltinPrefix(CI);
  assert((Prefix == kOCLBuiltinName::SubPrefix) &&
         "Workgroup scope is not supported for OpGroupNonUniformRotateKHR");

  std::string OptionalClustered;
  if (CI->arg_size() == 4)
    OptionalClustered = "clustered_";
  return Prefix + kSPIRVName::GroupPrefix + OptionalClustered + "rotate";
}

std::string SPIRVToOCLBase::groupOCToOCLBuiltinName(CallInst *CI, Op OC) {
  if (OC == OpGroupNonUniformRotateKHR)
    return getRotateBuiltinName(CI, OC);

  auto FuncName = OCLSPIRVBuiltinMap::rmap(OC);
  assert(FuncName.find(kSPIRVName::GroupPrefix) == 0);

  if (!hasGroupOperation(OC)) {
    /// Transform OpenCL group builtin function names from group_
    /// to work_group_ and sub_group_.
    FuncName = getGroupBuiltinPrefix(CI) + FuncName;
  } else { // Opcodes with group operation parameter
    if (isUniformArithmeticOpCode(OC))
      FuncName = getUniformArithmeticBuiltinName(CI, OC);
    else if (isNonUniformArithmeticOpCode(OC))
      FuncName = getNonUniformArithmeticBuiltinName(CI, OC);
    else if (OC == OpGroupNonUniformBallotBitCount)
      FuncName = getBallotBuiltinName(CI, OC);
    else
      llvm_unreachable("Unsupported opcode!");
  }
  return FuncName;
}

/// Return true if the original boolean return type needs to be changed to i32
/// when mapping the SPIR-V op to an OpenCL builtin.
static bool needsInt32RetTy(Op OC) {
  return OC == OpGroupAny || OC == OpGroupAll || OC == OpGroupNonUniformAny ||
         OC == OpGroupNonUniformAll || OC == OpGroupNonUniformAllEqual ||
         OC == OpGroupNonUniformElect || OC == OpGroupNonUniformInverseBallot ||
         OC == OpGroupNonUniformBallotBitExtract || isGroupLogicalOpCode(OC);
}

void SPIRVToOCLBase::visitCallSPIRVGroupBuiltin(CallInst *CI, Op OC) {
  auto FuncName = groupOCToOCLBuiltinName(CI, OC);
  auto Mutator = mutateCallInst(CI, FuncName);
  /// Remove Group Operation argument,
  /// as in OpenCL representation this is included in the function name
  Mutator.removeArgs(0, (hasGroupOperation(OC) ? 2 : 1));

  Type *Int32Ty = Type::getInt32Ty(*Ctx);
  bool HasArg0ExtendedToi32 =
      OC == OpGroupAny || OC == OpGroupAll || OC == OpGroupNonUniformAny ||
      OC == OpGroupNonUniformAll || OC == OpGroupNonUniformBallot ||
      isGroupLogicalOpCode(OC);

  // Handle function arguments
  if (OC == OpGroupBroadcast) {
    Value *VecArg = Mutator.getArg(1);
    if (auto *VT = dyn_cast<FixedVectorType>(VecArg->getType())) {
      unsigned NumElements = VT->getNumElements();
      for (unsigned I = 0; I < NumElements; I++)
        Mutator.insertArg(1 + I, Mutator.Builder.CreateExtractElement(
                                     VecArg, Mutator.Builder.getInt32(I)));
      Mutator.removeArg(1 + NumElements);
    }
  } else if (HasArg0ExtendedToi32)
    Mutator.mapArg(0, [](IRBuilder<> &Builder, Value *V) {
      return Builder.CreateZExt(V, Builder.getInt32Ty());
    });

  // Handle function return type
  if (needsInt32RetTy(OC))
    Mutator.changeReturnType(Int32Ty, [](IRBuilder<> &Builder, CallInst *CI) {
      // The OpenCL builtin returns a non-zero integer value. Convert to a
      // boolean value.
      return Builder.CreateICmpNE(CI, Builder.getInt32(0));
    });
}

void SPIRVToOCLBase::visitCallSPIRVPipeBuiltin(CallInst *CI, Op OC) {
  auto DemangledName = OCLSPIRVBuiltinMap::rmap(OC);
  bool HasScope = DemangledName.find(kSPIRVName::GroupPrefix) == 0;
  if (HasScope)
    DemangledName = getGroupBuiltinPrefix(CI) + DemangledName;

  assert(CI->getCalledFunction() && "Unexpected indirect call");
  auto Mutator = mutateCallInst(CI, DemangledName);
  if (HasScope)
    Mutator.removeArg(0);
  if (OC == OpReadPipe || OC == OpWritePipe || OC == OpReservedReadPipe ||
      OC == OpReservedWritePipe || OC == OpReadPipeBlockingINTEL ||
      OC == OpWritePipeBlockingINTEL) {
    Mutator.mapArg(Mutator.arg_size() - 3, [](IRBuilder<> &Builder, Value *P) {
      Type *T = P->getType();
      assert(isa<PointerType>(T));
      auto *NewTy = Builder.getPtrTy(SPIRAS_Generic);
      if (T != NewTy) {
        P = Builder.CreateAddrSpaceCast(P, NewTy);
      }
      return std::make_pair(
          P, TypedPointerType::get(Builder.getInt8Ty(), SPIRAS_Generic));
    });
  }
}

void SPIRVToOCLBase::visitCallSPIRVImageMediaBlockBuiltin(CallInst *CI, Op OC) {
  Type *RetType = CI->getType();
  if (OC == OpSubgroupImageMediaBlockWriteINTEL) {
    assert(CI->arg_size() >= 5 && "Wrong media block write signature");
    RetType = CI->getArgOperand(4)->getType(); // texel type
  }
  unsigned int BitWidth = RetType->getScalarSizeInBits();
  std::string FuncPostfix;
  if (BitWidth == 8)
    FuncPostfix = "_uc";
  else if (BitWidth == 16)
    FuncPostfix = "_us";
  else if (BitWidth == 32)
    FuncPostfix = "_ui";
  else
    assert(0 && "Unsupported texel type!");

  if (auto *VecTy = dyn_cast<FixedVectorType>(RetType)) {
    unsigned int NumEl = VecTy->getNumElements();
    assert((NumEl == 2 || NumEl == 4 || NumEl == 8 || NumEl == 16) &&
           "Wrong function type!");
    FuncPostfix += std::to_string(NumEl);
  }

  mutateCallInst(CI, OCLSPIRVBuiltinMap::rmap(OC) + FuncPostfix)
      .moveArg(0, CI->arg_size() - 1);
}

void SPIRVToOCLBase::visitCallBuildNDRangeBuiltIn(CallInst *CI, Op OC,
                                                  StringRef DemangledName) {
  assert(CI->getCalledFunction() && "Unexpected indirect call");
  // __spirv_BuildNDRange_nD, drop __spirv_
  StringRef S = DemangledName;
  S = S.drop_front(strlen(kSPIRVName::Prefix));
  SmallVector<StringRef, 8> Split;
  // BuildNDRange_nD
  S.split(Split, kSPIRVPostfix::Divider,
          /*MaxSplit=*/-1, /*KeepEmpty=*/false);
  assert(Split.size() >= 2 && "Invalid SPIRV function name");
  // Cut _nD and add it to function name.
  mutateCallInst(CI, std::string(kOCLBuiltinName::NDRangePrefix) +
                         Split[1].substr(0, 3).str())
      // OpenCL built-in has another order of parameters.
      .moveArg(2, 0);
}

void SPIRVToOCLBase::visitCallGenericCastToPtrBuiltIn(CallInst *CI, Op OC) {
  assert(CI->getCalledFunction() && "Unexpected indirect call");
  IRBuilder<> Builder(CI);
  Value *PtrArg = CI->getArgOperand(0);
  auto AddrSpace =
      static_cast<SPIRAddressSpace>(CI->getType()->getPointerAddressSpace());
  Type *NewTy = PointerType::get(CI->getContext(), AddrSpace);
  Value *ASC = Builder.CreateAddrSpaceCast(PtrArg, NewTy);
  CI->replaceAllUsesWith(ASC);
  CI->eraseFromParent();
}

void SPIRVToOCLBase::visitCallGenericCastToPtrExplicitBuiltIn(CallInst *CI,
                                                              Op OC) {
  assert(CI->getCalledFunction() && "Unexpected indirect call");
  StringRef Name;
  auto AddrSpace =
      static_cast<SPIRAddressSpace>(CI->getType()->getPointerAddressSpace());
  switch (AddrSpace) {
  case SPIRAS_Global:
    Name = kOCLBuiltinName::ToGlobal;
    break;
  case SPIRAS_Local:
    Name = kOCLBuiltinName::ToLocal;
    break;
  case SPIRAS_Private:
    Name = kOCLBuiltinName::ToPrivate;
    break;
  default:
    llvm_unreachable("Invalid address space");
  }
  mutateCallInst(CI, Name.str())
      // The instruction has two arguments, whereas ocl built-in has only one
      // argument.
      .removeArg(1);
}

void SPIRVToOCLBase::visitCallSPIRVCvtBuiltin(CallInst *CI, Op OC,
                                              StringRef DemangledName) {
  if (auto *TET =
          dyn_cast<TargetExtType>(CI->getFunctionType()->getReturnType())) {
    // Preserve any cooperative matrix type conversions as SPIR-V calls.
    if (TET->getName() == "spirv.CooperativeMatrixKHR") {
      return;
    }
  }
  std::string CastBuiltInName;
  if (isCvtFromUnsignedOpCode(OC))
    CastBuiltInName = "u";
  CastBuiltInName += kOCLBuiltinName::ConvertPrefix;
  Type *DstTy = CI->getType();
  CastBuiltInName += mapLLVMTypeToOCLType(DstTy, !isCvtToUnsignedOpCode(OC));
  if (DemangledName.find("_sat") != StringRef::npos || isSatCvtOpCode(OC))
    CastBuiltInName += "_sat";
  Value *Src = CI->getOperand(0);
  assert(Src && "Invalid SPIRV convert builtin call");
  Type *SrcTy = Src->getType();
  auto Loc = DemangledName.find("_rt");
  if (Loc != StringRef::npos &&
      !(isa<IntegerType>(SrcTy) && isa<IntegerType>(DstTy)))
    CastBuiltInName += DemangledName.substr(Loc, 4).str();
  mutateCallInst(CI, CastBuiltInName);
}

void SPIRVToOCLBase::visitCallAsyncWorkGroupCopy(CallInst *CI, Op OC) {
  // First argument of AsyncWorkGroupCopy instruction is Scope, OCL
  // built-in async_work_group_strided_copy doesn't have this argument
  mutateCallInst(CI, OCLSPIRVBuiltinMap::rmap(OC)).removeArg(0);
}

void SPIRVToOCLBase::visitCallGroupWaitEvents(CallInst *CI, Op OC) {
  // First argument of GroupWaitEvents instruction is Scope, OCL
  // built-in wait_group_events doesn't have this argument
  mutateCallInst(CI, OCLSPIRVBuiltinMap::rmap(OC)).removeArg(0);
}

static std::string getTypeSuffix(Type *T, bool IsSigned) {
  std::string Suffix;

  Type *ST = T->getScalarType();
  if (ST->isHalfTy())
    Suffix = "h";
  else if (ST->isFloatTy())
    Suffix = "f";
  else if (IsSigned)
    Suffix = "i";
  else
    Suffix = "ui";

  return Suffix;
}

BuiltinCallMutator
SPIRVToOCLBase::mutateCallImageOperands(CallInst *CI, StringRef NewFuncName,
                                        Type *T, unsigned ImOpArgIndex) {
  // Default to signed.
  bool IsSigned = true;
  uint64_t ImOpValue = 0;
  if (CI->arg_size() > ImOpArgIndex) {
    ConstantInt *ImOp = dyn_cast<ConstantInt>(CI->getArgOperand(ImOpArgIndex));
    if (ImOp)
      ImOpValue = ImOp->getZExtValue();
    unsigned SignZeroExtMasks = ImageOperandsMask::ImageOperandsSignExtendMask |
                                ImageOperandsMask::ImageOperandsZeroExtendMask;
    // If one of the SPIR-V 1.4 SignExtend/ZeroExtend operands is present, take
    // it into account and drop the mask.
    if (ImOpValue & SignZeroExtMasks) {
      if (ImOpValue & ImageOperandsMask::ImageOperandsZeroExtendMask)
        IsSigned = false;
      ImOpValue &= ~SignZeroExtMasks;
    }
  }

  auto Mutator =
      mutateCallInst(CI, NewFuncName.str() + getTypeSuffix(T, IsSigned));
  if (ImOpArgIndex < Mutator.arg_size()) {
    // Drop "Image Operands" argument.
    Mutator.removeArg(ImOpArgIndex);
    if (ImOpArgIndex < Mutator.arg_size()) {
      ConstantFP *LodVal = dyn_cast<ConstantFP>(Mutator.getArg(ImOpArgIndex));
      // If the image operand is LOD and its value is zero, drop it too.
      if (LodVal && LodVal->isNullValue() &&
          ImOpValue & ImageOperandsMask::ImageOperandsLodMask) {
        Mutator.removeArgs(ImOpArgIndex, Mutator.arg_size() - ImOpArgIndex);
        ImOpValue &= ~ImageOperandsMask::ImageOperandsLodMask;
      }
    }
  }
  return Mutator;
}

static bool isOCLDepthImage(Type *Ty) {
  if (auto *TET = dyn_cast<TargetExtType>(Ty)) {
    if (TET->getName() != "spirv.Image")
      return false;
    assert(TET->getNumIntParameters() > 2 &&
           "unexpected image TargetExtType parameter count");
    return TET->getIntParameter(1);
  }

  StringRef ImageTypeName;
  if (isOCLImageType(Ty, &ImageTypeName))
    return ImageTypeName.contains("_depth_");

  return false;
}

void SPIRVToOCLBase::visitCallSPIRVImageSampleExplicitLodBuiltIn(CallInst *CI,
                                                                 Op OC) {
  Type *T = CI->getType();
  if (auto *VT = dyn_cast<VectorType>(T))
    T = VT->getElementType();
  auto Mutator =
      mutateCallImageOperands(CI, kOCLBuiltinName::SampledReadImage, T, 2);

  CallInst *CallSampledImg = cast<CallInst>(CI->getArgOperand(0));
  auto Img = getCallValue(CallSampledImg, 0);
  auto Sampler = getCallValue(CallSampledImg, 1);
  const bool IsDepthImage = isOCLDepthImage(Img.second);
  Mutator.mapArg(0, [&](Value *SampledImg) {
    if (CallSampledImg->hasOneUse()) {
      CallSampledImg->replaceAllUsesWith(
          PoisonValue::get(CallSampledImg->getType()));
      CallSampledImg->dropAllReferences();
      CallSampledImg->eraseFromParent();
    }
    return Img;
  });
  Mutator.insertArg(1, Sampler);
  if (IsDepthImage)
    Mutator.changeReturnType(T, [&](IRBuilder<> &Builder, CallInst *NewCI) {
      return Builder.CreateInsertElement(
          FixedVectorType::get(NewCI->getType(), 4), NewCI, uint64_t(0));
    });
}

void SPIRVToOCLBase::visitCallSPIRVImageWriteBuiltIn(CallInst *CI, Op OC) {
  auto Mutator = mutateCallImageOperands(CI, kOCLBuiltinName::WriteImage,
                                         CI->getArgOperand(2)->getType(), 3);
  if (Mutator.arg_size() > 3)
    Mutator.moveArg(3, 2);
}

void SPIRVToOCLBase::visitCallSPIRVImageReadBuiltIn(CallInst *CI, Op OC) {
  mutateCallImageOperands(CI, kOCLBuiltinName::ReadImage, CI->getType(), 2);
}

void SPIRVToOCLBase::visitCallSPIRVImageQueryBuiltIn(CallInst *CI, Op OC) {
  mutateCallInst(CI, OCLSPIRVBuiltinMap::rmap(OC))
      .changeReturnType(CI->getType(), [=](IRBuilder<> &Builder, CallInst *CI) {
        unsigned int Offset = 0;
        if (OC == OpImageQueryFormat)
          Offset = OCLImageChannelDataTypeOffset;
        else if (OC == OpImageQueryOrder)
          Offset = OCLImageChannelOrderOffset;
        else
          llvm_unreachable("Unsupported opcode");
        return Builder.CreateSub(CI, Builder.getInt32(Offset));
      });
}

void SPIRVToOCLBase::visitCallSPIRVSubgroupINTELBuiltIn(CallInst *CI, Op OC) {
  std::stringstream Name;
  Type *DataTy = nullptr;
  switch (OC) {
  case OpSubgroupBlockReadINTEL:
  case OpSubgroupImageBlockReadINTEL:
    Name << "intel_sub_group_block_read";
    DataTy = CI->getType();
    break;
  case OpSubgroupBlockWriteINTEL:
    Name << "intel_sub_group_block_write";
    DataTy = CI->getOperand(1)->getType();
    break;
  case OpSubgroupImageBlockWriteINTEL:
    Name << "intel_sub_group_block_write";
    DataTy = CI->getOperand(2)->getType();
    break;
  default:
    Name << OCLSPIRVBuiltinMap::rmap(OC);
    break;
  }
  if (DataTy) {
    unsigned VectorNumElements = 1;
    if (FixedVectorType *VT = dyn_cast<FixedVectorType>(DataTy))
      VectorNumElements = VT->getNumElements();
    unsigned ElementBitSize = DataTy->getScalarSizeInBits();
    Name << getIntelSubgroupBlockDataPostfix(ElementBitSize, VectorNumElements);
  }
  mutateCallInst(CI, Name.str());
}

void SPIRVToOCLBase::visitCallSPIRVAvcINTELEvaluateBuiltIn(CallInst *CI,
                                                           Op OC) {
  // There are three types of AVC Intel Evaluate opcodes:
  // 1. With multi reference images - does not use OpVmeImageINTEL opcode
  // for reference images
  // 2. With dual reference images - uses two OpVmeImageINTEL opcodes for
  // reference image
  // 3. With single reference image - uses one OpVmeImageINTEL opcode for
  // reference image
  StringRef FnName = CI->getCalledFunction()->getName();
  int NumImages = 0;
  if (FnName.contains("SingleReference"))
    NumImages = 2;
  else if (FnName.contains("DualReference"))
    NumImages = 3;
  else if (FnName.contains("MultiReference"))
    NumImages = 1;
  else if (FnName.contains("EvaluateIpe"))
    NumImages = 1;

  auto EraseVmeImageCall = [](CallInst *CI) {
    if (CI->hasOneUse()) {
      CI->replaceAllUsesWith(PoisonValue::get(CI->getType()));
      CI->dropAllReferences();
      CI->eraseFromParent();
    }
  };

  auto Mutator =
      mutateCallInst(CI, OCLSPIRVSubgroupAVCIntelBuiltinMap::rmap(OC));
  if (NumImages) {
    CallInst *SrcImage = cast<CallInst>(Mutator.getArg(0));
    if (NumImages == 1) {
      // Multi reference opcode - remove src image OpVmeImageINTEL opcode
      // and replace it with corresponding OpImage and OpSampler arguments
      size_t SamplerPos = Mutator.arg_size() - 1;
      Mutator.replaceArg(0, getCallValue(SrcImage, 0));
      Mutator.insertArg(SamplerPos, getCallValue(SrcImage, 1));
    } else {
      CallInst *FwdRefImage = cast<CallInst>(Mutator.getArg(1));
      CallInst *BwdRefImage =
          NumImages == 3 ? cast<CallInst>(Mutator.getArg(2)) : nullptr;
      // Single reference opcode - remove src and ref image
      // OpVmeImageINTEL opcodes and replace them with src and ref OpImage
      // opcodes and OpSampler
      Mutator.removeArgs(0, NumImages);
      // insert source OpImage and OpSampler
      Mutator.insertArg(0, getCallValue(SrcImage, 0));
      Mutator.insertArg(1, getCallValue(SrcImage, 1));
      // insert reference OpImage
      Mutator.insertArg(1, getCallValue(FwdRefImage, 0));
      EraseVmeImageCall(SrcImage);
      EraseVmeImageCall(FwdRefImage);
      if (BwdRefImage) {
        // Dual reference opcode - insert second reference OpImage argument
        Mutator.insertArg(2, getCallValue(BwdRefImage, 0));
        EraseVmeImageCall(BwdRefImage);
      }
    }
  } else
    llvm_unreachable("invalid avc instruction");
}

void SPIRVToOCLBase::visitCallSPIRVGenericPtrMemSemantics(CallInst *CI) {
  mutateCallInst(CI, OCLSPIRVBuiltinMap::rmap(OpGenericPtrMemSemantics))
      .changeReturnType(CI->getType(),
                        [](IRBuilder<> &Builder, CallInst *NewCI) {
                          return Builder.CreateShl(NewCI, Builder.getInt32(8));
                        });
}

void SPIRVToOCLBase::visitCallSPIRVBFloat16Conversions(CallInst *CI, Op OC) {
  Type *ArgTy = CI->getOperand(0)->getType();
  std::string N =
      ArgTy->isVectorTy()
          ? std::to_string(cast<FixedVectorType>(ArgTy)->getNumElements())
          : "";
  std::string Name;
  switch (static_cast<uint32_t>(OC)) {
  case OpConvertFToBF16INTEL:
    Name = "intel_convert_bfloat16" + N + "_as_ushort" + N;
    break;
  case OpConvertBF16ToFINTEL:
    Name = "intel_convert_as_bfloat16" + N + "_float" + N;
    break;
  default:
    break; // do nothing
  }
  mutateCallInst(CI, Name);
}

void SPIRVToOCLBase::visitCallSPIRVDot(CallInst *CI, Op OC,
                                       StringRef DemangledName) {
  // OpenCL only supports integer dot product builtins that have return types
  // of int and uint.
  if (!(DemangledName.contains("_Rint") || DemangledName.contains("_Ruint")))
    return;

  bool IsPacked = !CI->getOperand(0)->getType()->isVectorTy();
  std::stringstream Name;
  switch (OC) {
  case OpSDot:
    if (IsPacked)
      Name << kOCLBuiltinName::Dot4x8PackedPrefix << "ss_int";
    else
      // Add an extra suffix to help determine signed/unsigned arguments
      Name << kOCLBuiltinName::Dot << "_ss";
    break;
  case OpUDot:
    if (IsPacked)
      Name << kOCLBuiltinName::Dot4x8PackedPrefix << "uu_uint";
    else
      Name << kOCLBuiltinName::Dot << "_uu";
    break;
  case OpSUDot:
    if (IsPacked)
      Name << kOCLBuiltinName::Dot4x8PackedPrefix << "su_int";
    else
      Name << kOCLBuiltinName::Dot << "_su";
    break;
  case OpSDotAccSat:
    if (IsPacked)
      Name << kOCLBuiltinName::DotAccSat4x8PackedPrefix << "ss_int";
    else
      Name << kOCLBuiltinName::DotAccSat << "_ss";
    break;
  case OpUDotAccSat:
    if (IsPacked)
      Name << kOCLBuiltinName::DotAccSat4x8PackedPrefix << "uu_uint";
    else
      Name << kOCLBuiltinName::DotAccSat << "_uu";
    break;
  case OpSUDotAccSat:
    if (IsPacked)
      Name << kOCLBuiltinName::DotAccSat4x8PackedPrefix << "su_int";
    else
      Name << kOCLBuiltinName::DotAccSat << "_su";
    break;
  default:
    break; // do nothing
  }
  auto Mutator = mutateCallInst(CI, Name.str());
  if (IsPacked)
    Mutator.removeArg(CI->arg_size() - 1);
}

void SPIRVToOCLBase::visitCallSPIRVBuiltin(CallInst *CI, Op OC) {
  mutateCallInst(CI, OCLSPIRVBuiltinMap::rmap(OC));
}

void SPIRVToOCLBase::visitCallSPIRVBuiltin(CallInst *CI,
                                           SPIRVBuiltinVariableKind Kind) {
  mutateCallInst(CI, SPIRSPIRVBuiltinVariableMap::rmap(Kind));
}

void SPIRVToOCLBase::visitCallSPIRVAvcINTELInstructionBuiltin(CallInst *CI,
                                                              Op OC) {
  mutateCallInst(CI, OCLSPIRVSubgroupAVCIntelBuiltinMap::rmap(OC));
}

void SPIRVToOCLBase::visitCallSPIRVOCLExt(CallInst *CI, OCLExtOpKind Kind) {
  mutateCallInst(CI, OCLExtOpMap::map(Kind));
}

void SPIRVToOCLBase::visitCallSPIRVVLoadn(CallInst *CI, OCLExtOpKind Kind) {
  std::string Name = OCLExtOpMap::map(Kind);
  unsigned LastArg = CI->arg_size() - 1;
  if (ConstantInt *C = dyn_cast<ConstantInt>(CI->getArgOperand(LastArg))) {
    uint64_t NumComponents = C->getZExtValue();
    std::stringstream SS;
    SS << NumComponents;
    Name.replace(Name.find("n"), 1, SS.str());
  }
  mutateCallInst(CI, Name).removeArg(LastArg);
}

void SPIRVToOCLBase::visitCallSPIRVVStore(CallInst *CI, OCLExtOpKind Kind) {
  std::string Name = OCLExtOpMap::map(Kind);
  bool DropLastArg = false;
  if (Kind == OpenCLLIB::Vstore_half_r || Kind == OpenCLLIB::Vstore_halfn_r ||
      Kind == OpenCLLIB::Vstorea_halfn_r) {
    auto *C = cast<ConstantInt>(CI->getArgOperand(CI->arg_size() - 1));
    auto RoundingMode = static_cast<SPIRVFPRoundingModeKind>(C->getZExtValue());
    Name.replace(Name.find("_r"), 2,
                 std::string("_") +
                     SPIRSPIRVFPRoundingModeMap::rmap(RoundingMode));
    DropLastArg = true;
  }

  if (Kind == OpenCLLIB::Vstore_halfn || Kind == OpenCLLIB::Vstore_halfn_r ||
      Kind == OpenCLLIB::Vstorea_halfn || Kind == OpenCLLIB::Vstorea_halfn_r ||
      Kind == OpenCLLIB::Vstoren) {
    if (auto *DataType =
            dyn_cast<VectorType>(CI->getArgOperand(0)->getType())) {
      uint64_t NumElements = DataType->getElementCount().getFixedValue();
      assert((NumElements == 2 || NumElements == 3 || NumElements == 4 ||
              NumElements == 8 || NumElements == 16) &&
             "Unsupported vector size for vstore instruction!");
      std::stringstream SS;
      SS << NumElements;
      Name.replace(Name.find("n"), 1, SS.str());
    }
  }

  auto Mutator = mutateCallInst(CI, Name);
  if (DropLastArg)
    Mutator.removeArg(Mutator.arg_size() - 1);
}

void SPIRVToOCLBase::visitCallSPIRVPrintf(CallInst *CI, OCLExtOpKind Kind) {
  CallInst *NewCI = cast<CallInst>(
      mutateCallInst(CI, OCLExtOpMap::map(OpenCLLIB::Printf)).getMutated());

  // Clang represents printf function without mangling
  std::string TargetName = "printf";
  if (Function *F = M->getFunction(TargetName))
    NewCI->setCalledFunction(F);
  else
    NewCI->getCalledFunction()->setName(TargetName);
}

void SPIRVToOCLBase::visitCallSPIRVAnyAll(CallInst *CI, Op OC) {
  mutateCallInst(CI, OCLSPIRVBuiltinMap::rmap(OC))
      .mapArg(0,
              [](IRBuilder<> &Builder, Value *V) {
                Type *NewArgTy = V->getType()->getWithNewBitWidth(8);
                return Builder.CreateSExt(V, NewArgTy);
              })
      .changeReturnType(Type::getInt32Ty(*Ctx),
                        [=](IRBuilder<> &Builder, CallInst *NewCI) {
                          return Builder.CreateTrunc(NewCI, CI->getType());
                        });
}

void SPIRVToOCLBase::visitCallSPIRVRelational(CallInst *CI, Op OC) {
  Type *IntTy = Type::getInt32Ty(*Ctx);
  Type *RetTy = IntTy;
  if (CI->getType()->isVectorTy()) {
    auto *OpElemTy =
        cast<FixedVectorType>(CI->getOperand(0)->getType())->getElementType();
    if (OpElemTy->isDoubleTy())
      IntTy = Type::getInt64Ty(*Ctx);
    if (OpElemTy->isHalfTy())
      IntTy = Type::getInt16Ty(*Ctx);
    RetTy = FixedVectorType::get(
        IntTy, cast<FixedVectorType>(CI->getType())->getNumElements());
  }
  mutateCallInst(CI, OCLSPIRVBuiltinMap::rmap(OC))
      .changeReturnType(RetTy, [=](IRBuilder<> &Builder, CallInst *NewCI) {
        return Builder.CreateTruncOrBitCast(NewCI, CI->getType());
      });
}

void SPIRVToOCLBase::visitCallSPIRVReadClockKHR(CallInst *CI) {
  std::ostringstream Name;
  Name << "clock_read_";

  if (CI->getType()->isVectorTy())
    Name << "hilo_";

  // Encode the scope (taken from the argument) in the function name.
  ConstantInt *ScopeOp = cast<ConstantInt>(CI->getArgOperand(0));
  switch (static_cast<Scope>(ScopeOp->getZExtValue())) {
  case ScopeDevice:
    Name << "device";
    break;
  case ScopeWorkgroup:
    Name << "work_group";
    break;
  case ScopeSubgroup:
    Name << "sub_group";
    break;
  default:
    break;
  }

  auto Mutator = mutateCallInst(CI, Name.str());
  Mutator.removeArg(0);
}

std::string SPIRVToOCLBase::getGroupBuiltinPrefix(CallInst *CI) {
  std::string Prefix;
  auto ES = getArgAsScope(CI, 0);
  switch (ES) {
  case ScopeWorkgroup:
    Prefix = kOCLBuiltinName::WorkPrefix;
    break;
  case ScopeSubgroup:
    Prefix = kOCLBuiltinName::SubPrefix;
    break;
  default:
    llvm_unreachable("Invalid execution scope");
  }
  return Prefix;
}

std::string
SPIRVToOCLBase::getOCLImageOpaqueType(SmallVector<std::string, 8> &Postfixes) {
  SmallVector<int, 7> Ops;
  for (unsigned I = 1; I < 8; ++I)
    Ops.push_back(atoi(Postfixes[I].c_str()));
  SPIRVTypeImageDescriptor Desc(static_cast<SPIRVImageDimKind>(Ops[0]), Ops[1],
                                Ops[2], Ops[3], Ops[4], Ops[5]);

  std::string OCLStructName =
      std::string(kSPR2TypeName::OCLPrefix) + rmap<std::string>(Desc);

  SPIRVAccessQualifierKind Acc = static_cast<SPIRVAccessQualifierKind>(Ops[6]);
  insertImageNameAccessQualifier(Acc, OCLStructName);
  return OCLStructName;
}

std::string
SPIRVToOCLBase::getOCLPipeOpaqueType(SmallVector<std::string, 8> &Postfixes) {
  assert(Postfixes.size() == 1);
  unsigned PipeAccess = atoi(Postfixes[0].c_str());
  assert((PipeAccess == AccessQualifierReadOnly ||
          PipeAccess == AccessQualifierWriteOnly) &&
         "Invalid access qualifier");
  return PipeAccess ? kSPR2TypeName::PipeWO : kSPR2TypeName::PipeRO;
}

void SPIRVToOCLBase::translateOpaqueTypes() {
  for (auto *S : M->getIdentifiedStructTypes()) {
    StringRef STName = S->getStructName();
    bool IsSPIRVOpaque =
        S->isOpaque() && STName.starts_with(kSPIRVTypeName::PrefixAndDelim);

    if (!IsSPIRVOpaque)
      continue;

    S->setName(translateOpaqueType(STName));
  }
}

std::string SPIRVToOCLBase::translateOpaqueType(StringRef STName) {
  if (!STName.starts_with(kSPIRVTypeName::PrefixAndDelim))
    return STName.str();

  SmallVector<std::string, 8> Postfixes;
  std::string DecodedST = decodeSPIRVTypeName(STName, Postfixes);

  if (!SPIRVOpaqueTypeOpCodeMap::find(DecodedST))
    return STName.str();

  Op OP = SPIRVOpaqueTypeOpCodeMap::map(DecodedST);
  std::string OCLOpaqueName;
  if (OP == OpTypeImage)
    OCLOpaqueName = getOCLImageOpaqueType(Postfixes);
  else if (OP == OpTypePipe)
    OCLOpaqueName = getOCLPipeOpaqueType(Postfixes);
  else if (isSubgroupAvcINTELTypeOpCode(OP))
    OCLOpaqueName = OCLSubgroupINTELTypeOpCodeMap::rmap(OP);
  else if (isOpaqueGenericTypeOpCode(OP))
    OCLOpaqueName = OCLOpaqueTypeOpCodeMap::rmap(OP);
  else
    return STName.str();

  return OCLOpaqueName;
}

void addSPIRVBIsLoweringPass(ModulePassManager &PassMgr,
                             SPIRV::BIsRepresentation BIsRep) {
  switch (BIsRep) {
  case SPIRV::BIsRepresentation::OpenCL12:
    PassMgr.addPass(SPIRVToOCL12Pass());
    break;
  case SPIRV::BIsRepresentation::OpenCL20:
    PassMgr.addPass(SPIRVToOCL20Pass());
    break;
  case SPIRV::BIsRepresentation::SPIRVFriendlyIR:
    // nothing to do, already done
    break;
  }
}

} // namespace SPIRV

ModulePass *
llvm::createSPIRVBIsLoweringPass(Module &M,
                                 SPIRV::BIsRepresentation BIsRepresentation) {
  switch (BIsRepresentation) {
  case SPIRV::BIsRepresentation::OpenCL12:
    return createSPIRVToOCL12Legacy();
  case SPIRV::BIsRepresentation::OpenCL20:
    return createSPIRVToOCL20Legacy();
  case SPIRV::BIsRepresentation::SPIRVFriendlyIR:
    // nothing to do, already done
    return nullptr;
  }
  llvm_unreachable("Unsupported built-ins representation");
  return nullptr;
}
