/*
 * Copyright 2015, 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/Renderscript/RSUtils.h"
#include "bcc/Support/Log.h"

#include "rsDefines.h"

#include <llvm/IR/Constant.h>
#include <llvm/IR/Constants.h>
#include <llvm/IR/Type.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/Function.h>
#include <llvm/Pass.h>

#include <sstream>
#include <vector>

namespace {

const bool kDebugGlobalInfo = false;

/* RSGlobalInfoPass: Embeds additional information about RenderScript global
 * variables into the Module. The 5 variables added are specified as follows:
 * 1) .rs.global_entries
 *    i32 - int
 *    Optional number of global variables.
 * 2) .rs.global_names
 *    [N * i8*] - const char *[N]
 *    Optional global variable name info. Each entry corresponds to the name
 *    of 1 of the N global variables.
 * 3) .rs.global_addresses
 *    [N * i8*] - void*[N] or void**
 *    Optional global variable address info. Each entry corresponds to the
 *    address of 1 of the N global variables.
 * 4) .rs.global_sizes
 *    [N * i32] or [N * i64] - size_t[N]
 *    Optional global variable size info. Each entry corresponds to the size
 *    of 1 of the N global variables.
 * 5) .rs.global_properties
 *    [N * i32]
 *    Optional global properties. Each entry corresponds to the properties
 *    for 1 of the N global variables. The 32-bit integer for properties
 *    can be broken down as follows:
 *    bit(s)    Encoded value
 *    ------    -------------
 *        18    Pointer (1 is pointer, 0 is non-pointer)
 *        17    Static (1 is static, 0 is extern)
 *        16    Constant (1 is const, 0 is non-const)
 *    15 - 0    RsDataType (see frameworks/rs/rsDefines.h for more info)
 */
class RSGlobalInfoPass: public llvm::ModulePass {
private:
  // If true, we don't include information about immutable global variables
  // in our various exported data structures.
  bool mSkipConstants;

  // Encodes properties of the GlobalVariable into a uint32_t.
  // These values are used to populate the .rs.global_properties array.
  static uint32_t getEncodedProperties(const llvm::GlobalVariable &GV) {
    auto GlobalType = GV.getType()->getPointerElementType();

    // We start by getting the RsDataType and placing it into our result.
    uint32_t result = getRsDataTypeForType(GlobalType);
    bccAssert(!(result & ~RS_GLOBAL_TYPE));  // Can only alter lower 16-bits.

    if (GlobalType->isPointerTy()) {
      // Global variables that are pointers can all be used with "bind".
      result |= RS_GLOBAL_POINTER;
    }

    if (GV.isConstant()) {
      result |= RS_GLOBAL_CONSTANT;
    }

    if (GV.getLinkage() == llvm::GlobalValue::InternalLinkage) {
      // We only have internal linkage in RS to signify static.
      result |= RS_GLOBAL_STATIC;
    }

    return result;
  }

public:
  static char ID;

  RSGlobalInfoPass(bool pSkipConstants = false)
    : ModulePass (ID), mSkipConstants(pSkipConstants) {
  }

  void getAnalysisUsage(llvm::AnalysisUsage &AU) const override {
    // This pass does not use any other analysis passes, but it does
    // add new global variables.
  }

  bool runOnModule(llvm::Module &M) override {
    std::vector<llvm::Constant *> GVAddresses;
    std::vector<llvm::Constant *> GVNames;
    std::vector<std::string> GVNameStrings;
    std::vector<uint32_t> GVSizes32;
    std::vector<uint64_t> GVSizes64;
    std::vector<uint32_t> GVProperties;

    const llvm::DataLayout &DL = M.getDataLayout();
    const size_t PointerSizeInBits = DL.getPointerSizeInBits();

    bccAssert(PointerSizeInBits == 32 || PointerSizeInBits == 64);

    int GlobalNumber = 0;

    // i8* - LLVM uses this to represent void* and char*
    llvm::Type *VoidPtrTy = llvm::Type::getInt8PtrTy(M.getContext());

    // i32
    llvm::Type *Int32Ty = llvm::Type::getInt32Ty(M.getContext());

    // i32 or i64 depending on our actual size_t
    llvm::Type *SizeTy = llvm::Type::getIntNTy(M.getContext(),
                                               PointerSizeInBits);

    for (auto &GV : M.globals()) {
      // Skip constant variables if we were configured to do so.
      if (mSkipConstants && GV.isConstant()) {
        continue;
      }

      // In LLVM, an instance of GlobalVariable is actually a Value
      // corresponding to the address of it.
      GVAddresses.push_back(llvm::ConstantExpr::getBitCast(&GV, VoidPtrTy));
      GVNameStrings.push_back(GV.getName());

      // Since these are all global variables, their type is actually a
      // pointer to the underlying data. We can extract the total underlying
      // storage size by looking at the first contained type.
      auto GlobalType = GV.getType()->getPointerElementType();
      auto TypeSize = DL.getTypeAllocSize(GlobalType);
      if (PointerSizeInBits == 32) {
        GVSizes32.push_back(TypeSize);
      } else {
        GVSizes64.push_back(TypeSize);
      }

      GVProperties.push_back(getEncodedProperties(GV));
    }

    // Create the new strings for storing the names of the global variables.
    // This has to be done as a separate pass (over the original global
    // variables), because these strings are new global variables themselves.
    for (auto GVN : GVNameStrings) {
      llvm::Constant *C =
          llvm::ConstantDataArray::getString(M.getContext(), GVN);
      std::stringstream VarName;
      VarName << ".rs.name_str_" << GlobalNumber++;
      llvm::Value *V = M.getOrInsertGlobal(VarName.str(), C->getType());
      llvm::GlobalVariable *VarAsStr = llvm::dyn_cast<llvm::GlobalVariable>(V);
      VarAsStr->setInitializer(C);
      VarAsStr->setConstant(true);
      VarAsStr->setLinkage(llvm::GlobalValue::PrivateLinkage);
      VarAsStr->setUnnamedAddr(true);
      // VarAsStr has type [_ x i8]*. Cast to i8* for storing in
      // .rs.global_names.
      GVNames.push_back(llvm::ConstantExpr::getBitCast(VarAsStr, VoidPtrTy));
    }

    if (PointerSizeInBits == 32) {
      bccAssert(GVAddresses.size() == GVSizes32.size());
      bccAssert(GVSizes64.size() == 0);
      bccAssert(GVAddresses.size() == GVProperties.size());
    } else {
      bccAssert(GVSizes32.size() == 0);
      bccAssert(GVAddresses.size() == GVSizes64.size());
      bccAssert(GVAddresses.size() == GVProperties.size());
    }

    size_t NumGlobals = GVAddresses.size();

    // [NumGlobals * i8*]
    llvm::ArrayType *VoidPtrArrayTy = llvm::ArrayType::get(VoidPtrTy,
                                                           NumGlobals);
    // [NumGlobals * i32] or [NumGlobals * i64]
    llvm::ArrayType *SizeArrayTy = llvm::ArrayType::get(SizeTy, NumGlobals);

    // [NumGlobals * i32]
    llvm::ArrayType *Int32ArrayTy = llvm::ArrayType::get(Int32Ty, NumGlobals);

    // 1) @.rs.global_entries = constant i32 NumGlobals
    llvm::Value *V = M.getOrInsertGlobal(kRsGlobalEntries, Int32Ty);
    llvm::GlobalVariable *GlobalEntries =
        llvm::dyn_cast<llvm::GlobalVariable>(V);
    llvm::Constant *GlobalEntriesInit =
        llvm::ConstantInt::get(Int32Ty, NumGlobals);
    GlobalEntries->setInitializer(GlobalEntriesInit);
    GlobalEntries->setConstant(true);

    // 2) @.rs.global_names = constant [N * i8*] [...]
    V = M.getOrInsertGlobal(kRsGlobalNames, VoidPtrArrayTy);
    llvm::GlobalVariable *GlobalNames =
        llvm::dyn_cast<llvm::GlobalVariable>(V);
    llvm::Constant *GlobalNamesInit =
        llvm::ConstantArray::get(VoidPtrArrayTy, GVNames);
    GlobalNames->setInitializer(GlobalNamesInit);
    GlobalNames->setConstant(true);

    // 3) @.rs.global_addresses = constant [N * i8*] [...]
    V = M.getOrInsertGlobal(kRsGlobalAddresses, VoidPtrArrayTy);
    llvm::GlobalVariable *GlobalAddresses =
        llvm::dyn_cast<llvm::GlobalVariable>(V);
    llvm::Constant *GlobalAddressesInit =
        llvm::ConstantArray::get(VoidPtrArrayTy, GVAddresses);
    GlobalAddresses->setInitializer(GlobalAddressesInit);
    GlobalAddresses->setConstant(true);


    // 4) @.rs.global_sizes = constant [N * i32 or i64] [...]
    V = M.getOrInsertGlobal(kRsGlobalSizes, SizeArrayTy);
    llvm::GlobalVariable *GlobalSizes =
        llvm::dyn_cast<llvm::GlobalVariable>(V);
    llvm::Constant *GlobalSizesInit;
    if (PointerSizeInBits == 32) {
      GlobalSizesInit = llvm::ConstantDataArray::get(M.getContext(), GVSizes32);
    } else {
      GlobalSizesInit = llvm::ConstantDataArray::get(M.getContext(), GVSizes64);
    }
    GlobalSizes->setInitializer(GlobalSizesInit);
    GlobalSizes->setConstant(true);

    // 5) @.rs.global_properties = constant i32 NumGlobals
    V = M.getOrInsertGlobal(kRsGlobalProperties, Int32ArrayTy);
    llvm::GlobalVariable *GlobalProperties =
        llvm::dyn_cast<llvm::GlobalVariable>(V);
    llvm::Constant *GlobalPropertiesInit =
        llvm::ConstantDataArray::get(M.getContext(), GVProperties);
    GlobalProperties->setInitializer(GlobalPropertiesInit);
    GlobalProperties->setConstant(true);

    if (kDebugGlobalInfo) {
      GlobalEntries->dump();
      GlobalNames->dump();
      GlobalAddresses->dump();
      GlobalSizes->dump();
      GlobalProperties->dump();
    }

    // Upon completion, this pass has always modified the Module.
    return true;
  }
};

}

char RSGlobalInfoPass::ID = 0;

static llvm::RegisterPass<RSGlobalInfoPass> X("embed-rs-global-info",
  "Embed additional information about RenderScript global variables");

namespace bcc {

llvm::ModulePass * createRSGlobalInfoPass(bool pSkipConstants) {
  return new RSGlobalInfoPass(pSkipConstants);
}

}