/*
 * Copyright 2012, The Android Open Source Project
 *
 * 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 "bcc/Assert.h"
#include "bcc/Config/Config.h"
#include "bcc/Renderscript/RSTransforms.h"
#include "bcc/Renderscript/RSUtils.h"
#include "bcc/Support/Log.h"
#include "bcinfo/MetadataExtractor.h"
#include "rsDefines.h"

#include <cstdlib>
#include <vector>

#include <llvm/IR/DerivedTypes.h>
#include <llvm/IR/Function.h>
#include <llvm/IR/Instructions.h>
#include <llvm/IR/IRBuilder.h>
#include <llvm/IR/Module.h>
#include <llvm/Pass.h>
#include <llvm/Support/raw_ostream.h>
#include <llvm/IR/Type.h>

using namespace bcc;

namespace {

/* RSEmbedInfoPass - This pass operates on the entire module and embeds a
 * string constaining relevant metadata directly as a global variable.
 * This information does not need to be consistent across Android releases,
 * because the standalone compiler + compatibility driver or system driver
 * will be using the same format (i.e. bcc_compat + libRSSupport.so or
 * bcc + libRSCpuRef are always paired together for installation).
 */
class RSEmbedInfoPass : public llvm::ModulePass {
private:
  static char ID;

  llvm::Module *M;
  llvm::LLVMContext *C;

public:
  RSEmbedInfoPass()
      : ModulePass(ID),
        M(nullptr) {
  }

  virtual void getAnalysisUsage(llvm::AnalysisUsage &AU) const override {
    AU.setPreservesAll();
  }

  static std::string getRSInfoString(const llvm::Module *module) {
    std::string str;
    llvm::raw_string_ostream s(str);
    bcinfo::MetadataExtractor me(module);
    if (!me.extract()) {
      bccAssert(false && "Could not extract RS metadata for module!");
      return std::string("");
    }

    size_t exportVarCount = me.getExportVarCount();
    size_t exportFuncCount = me.getExportFuncCount();
    size_t exportForEachCount = me.getExportForEachSignatureCount();
    size_t exportReduceCount = me.getExportReduceCount();
    size_t objectSlotCount = me.getObjectSlotCount();
    size_t pragmaCount = me.getPragmaCount();
    const char **exportVarNameList = me.getExportVarNameList();
    const char **exportFuncNameList = me.getExportFuncNameList();
    const char **exportForEachNameList = me.getExportForEachNameList();
    const uint32_t *exportForEachSignatureList =
        me.getExportForEachSignatureList();
    const bcinfo::MetadataExtractor::Reduce *exportReduceList =
        me.getExportReduceList();
    const uint32_t *objectSlotList = me.getObjectSlotList();
    const char **pragmaKeyList = me.getPragmaKeyList();
    const char **pragmaValueList = me.getPragmaValueList();
    bool isThreadable = me.isThreadable();
    const char *buildChecksum = me.getBuildChecksum();

    size_t i;

    // We use a simple text format here that the compatibility library
    // can easily parse. Each section starts out with its name
    // followed by a count.  The count denotes the number of lines to
    // parse for that particular category. Variables and Functions
    // merely put the appropriate identifier on the line. ForEach
    // kernels have the encoded int signature, followed by a hyphen
    // followed by the identifier (function to look up). General
    // reduce kernels have the encoded int signature, followed by a
    // hyphen followed by the accumulator data size, followed by a
    // hyphen followed by the identifier (reduction name); and then
    // for each possible constituent function, a hyphen followed by
    // the identifier (function name) -- in the case where the
    // function is omitted, "." is used in place of the identifier.
    // Object Slots are just listed as one integer per line.

    s << "exportVarCount: " << exportVarCount << "\n";
    for (i = 0; i < exportVarCount; ++i) {
      s << exportVarNameList[i] << "\n";
    }

    s << "exportFuncCount: " << exportFuncCount << "\n";
    for (i = 0; i < exportFuncCount; ++i) {
      s << exportFuncNameList[i] << "\n";
    }

    s << "exportForEachCount: " << exportForEachCount << "\n";
    for (i = 0; i < exportForEachCount; ++i) {
      s << exportForEachSignatureList[i] << " - "
        << exportForEachNameList[i] << "\n";
    }

    s << "exportReduceCount: " << exportReduceCount << "\n";
    auto reduceFnName = [](const char *Name) { return Name ? Name : "."; };
    for (i = 0; i < exportReduceCount; ++i) {
      const bcinfo::MetadataExtractor::Reduce &reduce = exportReduceList[i];
      s << reduce.mSignature << " - "
        << reduce.mAccumulatorDataSize << " - "
        << reduce.mReduceName << " - "
        << reduceFnName(reduce.mInitializerName) << " - "
        << reduceFnName(reduce.mAccumulatorName) << " - "
        << ((reduce.mCombinerName != nullptr)
            ? reduce.mCombinerName
            : nameReduceCombinerFromAccumulator(reduce.mAccumulatorName)) << " - "
        << reduceFnName(reduce.mOutConverterName) << " - "
        << reduceFnName(reduce.mHalterName)
        << "\n";
    }

    s << "objectSlotCount: " << objectSlotCount << "\n";
    for (i = 0; i < objectSlotCount; ++i) {
      s << objectSlotList[i] << "\n";
    }

    s << "pragmaCount: " << pragmaCount << "\n";
    for (i = 0; i < pragmaCount; ++i) {
      s << pragmaKeyList[i] << " - "
        << pragmaValueList[i] << "\n";
    }
    s << "isThreadable: " << ((isThreadable) ? "yes" : "no") << "\n";

    if (buildChecksum != nullptr && buildChecksum[0]) {
      s << "buildChecksum: " << buildChecksum << "\n";
    }

    s.flush();
    return str;
  }

  virtual bool runOnModule(llvm::Module &M) {
    this->M = &M;
    C = &M.getContext();

    // Embed this as the global variable .rs.info so that it will be
    // accessible from the shared object later.
    llvm::Constant *Init = llvm::ConstantDataArray::getString(*C,
                                                              getRSInfoString(&M));
    llvm::GlobalVariable *InfoGV =
        new llvm::GlobalVariable(M, Init->getType(), true,
                                 llvm::GlobalValue::ExternalLinkage, Init,
                                 kRsInfo);
    (void) InfoGV;

    return true;
  }

  virtual const char *getPassName() const {
    return "Embed Renderscript Info";
  }

};  // end RSEmbedInfoPass

}  // end anonymous namespace

char RSEmbedInfoPass::ID = 0;

namespace bcc {

llvm::ModulePass *
createRSEmbedInfoPass() {
  return new RSEmbedInfoPass();
}

}  // end namespace bcc