// Copyright 2022 The Clspv Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "llvm/IR/Constants.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Module.h"

#include "Builtins.h"
#include "FixupBuiltinsPass.h"
#include "Types.h"

#include "clspv/Option.h"

#include <cmath>

using namespace clspv;
using namespace llvm;

namespace clspv_local {
static double rsqrt(double input) { return 1.0 / sqrt(input); }
} // namespace clspv_local

PreservedAnalyses FixupBuiltinsPass::run(Module &M, ModuleAnalysisManager &) {
  PreservedAnalyses PA;
  for (auto &F : M) {
    runOnFunction(F);
  }
  return PA;
}

bool FixupBuiltinsPass::runOnFunction(Function &F) {
  auto &FI = Builtins::Lookup(&F);
  switch (FI.getType()) {
  case Builtins::kSqrt:
    return fixupSqrt(F, sqrt);
  case Builtins::kRsqrt:
    return fixupSqrt(F, clspv_local::rsqrt);
  case Builtins::kReadImagef:
  case Builtins::kReadImagei:
  case Builtins::kReadImageui:
    if (clspv::Option::HackImage1dBufferBGRA() &&
        !FI.getParameter(1).isSampler()) {
      return fixupReadImage(F);
    } else {
      return false;
    }
  default:
    return false;
  }
}

bool FixupBuiltinsPass::fixupSqrt(Function &F, double (*fct)(double)) {
  // We only want to perform this transformation on the native sqrt/rsqrt
  // implementation.
  if (!F.isDeclaration()) {
    return false;
  }
  bool modified = false;
  SmallVector<CallInst *> worklist;
  for (auto &U : F.uses()) {
    if (auto CI = dyn_cast<CallInst>(U.getUser())) {
      worklist.push_back(CI);
    }
  }
  for (auto CI : worklist) {
    IRBuilder<> builder(CI);
    auto nan = ConstantFP::getNaN(CI->getType());
    auto zero = ConstantFP::getZero(CI->getType());
    if (auto cst = dyn_cast<ConstantFP>(CI->getOperand(0))) {
      CI->replaceAllUsesWith(ConstantFP::get(
          CI->getType(), fct(cst->getValue().convertToDouble())));
      CI->eraseFromParent();
    } else if (auto vec_cst = dyn_cast<ConstantDataVector>(CI->getOperand(0))) {
      Value *Res = UndefValue::get(vec_cst->getType());
      for (unsigned int i = 0; i < vec_cst->getNumElements(); i++) {
        auto fp = cast<ConstantFP>(vec_cst->getElementAsConstant(i))
                      ->getValue()
                      .convertToDouble();
        Res = builder.CreateInsertElement(
            Res, ConstantFP::get(CI->getType()->getScalarType(), fct(fp)), i);
      }
      CI->replaceAllUsesWith(Res);
    } else {
      auto op_is_positive = builder.CreateFCmpOGE(CI->getOperand(0), zero);
      builder.SetInsertPoint(CI->getNextNode());
      SelectInst *select =
          cast<SelectInst>(builder.CreateSelect(op_is_positive, zero, nan));
      CI->replaceAllUsesWith(select);
      select->setTrueValue(CI);
    }
    modified = true;
  }
  return modified;
}

bool FixupBuiltinsPass::fixupReadImage(Function &F) {
  const uint32_t CL_BGRA = 0x10B6;
  DenseMap<Value *, Type *> cache;
  bool changed = false;
  for (auto &U : F.uses()) {
    if (auto CI = dyn_cast<CallInst>(U.getUser())) {
      auto Img = CI->getOperand(0);
      auto *image_ty = InferType(Img, F.getContext(), &cache);
      if (clspv::ImageDimensionality(image_ty) == spv::DimBuffer) {
        IRBuilder<> B(CI);
        B.SetInsertPoint(*CI->getInsertionPointAfterDef());

        auto shuffle =
            cast<ShuffleVectorInst>(B.CreateShuffleVector(CI, {2, 1, 0, 3}));
        auto channel_order_fct = F.getParent()->getOrInsertFunction(
            "_Z23get_image_channel_order21ocl_image1d_buffer_ro",
            FunctionType::get(B.getInt32Ty(), {image_ty}, false));
        auto channel_order = B.CreateCall(channel_order_fct, {Img});
        auto cmp = B.CreateICmpNE(channel_order, B.getInt32(CL_BGRA));
        SelectInst *select = cast<SelectInst>(B.CreateSelect(cmp, CI, shuffle));

        // Do not use tmp before because llvm can optimize the node and not
        // create it. But we need to use tmp to be able to replace all uses of
        // CI without having a circular dependency.
        auto tmp = UndefValue::get(CI->getType());
        select->setTrueValue(tmp);
        shuffle->setOperand(0, tmp);

        CI->replaceAllUsesWith(select);

        // Put the right argument at the proper places.
        select->setTrueValue(CI);
        shuffle->setOperand(0, CI);
        changed = true;
      }
    }
  }
  return changed;
}
