//===---------- speculation.cpp - Utilities for Speculation ----------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "llvm/ExecutionEngine/Orc/Speculation.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Support/Debug.h"

#include <vector>

namespace llvm {

namespace orc {

// ImplSymbolMap methods
void ImplSymbolMap::trackImpls(SymbolAliasMap ImplMaps, JITDylib *SrcJD) {
  assert(SrcJD && "Tracking on Null Source .impl dylib");
  std::lock_guard<std::mutex> Lockit(ConcurrentAccess);
  for (auto &I : ImplMaps) {
    auto It = Maps.insert({I.first, {I.second.Aliasee, SrcJD}});
    // check rationale when independent dylibs have same symbol name?
    assert(It.second && "ImplSymbols are already tracked for this Symbol?");
    (void)(It);
  }
}

// Trigger Speculative Compiles.
void Speculator::speculateForEntryPoint(Speculator *Ptr, uint64_t StubId) {
  assert(Ptr && " Null Address Received in orc_speculate_for ");
  Ptr->speculateFor(StubId);
}

Error Speculator::addSpeculationRuntime(JITDylib &JD,
                                        MangleAndInterner &Mangle) {
  JITEvaluatedSymbol ThisPtr(pointerToJITTargetAddress(this),
                             JITSymbolFlags::Exported);
  JITEvaluatedSymbol SpeculateForEntryPtr(
      pointerToJITTargetAddress(&speculateForEntryPoint),
      JITSymbolFlags::Exported);
  return JD.define(absoluteSymbols({
      {Mangle("__orc_speculator"), ThisPtr},                // Data Symbol
      {Mangle("__orc_speculate_for"), SpeculateForEntryPtr} // Callable Symbol
  }));
}

// If two modules, share the same LLVMContext, different threads must
// not access them concurrently without locking the associated LLVMContext
// this implementation follows this contract.
void IRSpeculationLayer::emit(MaterializationResponsibility R,
                              ThreadSafeModule TSM) {

  assert(TSM && "Speculation Layer received Null Module ?");
  assert(TSM.getContext().getContext() != nullptr &&
         "Module with null LLVMContext?");

  // Instrumentation of runtime calls, lock the Module
  TSM.withModuleDo([this, &R](Module &M) {
    auto &MContext = M.getContext();
    auto SpeculatorVTy = StructType::create(MContext, "Class.Speculator");
    auto RuntimeCallTy = FunctionType::get(
        Type::getVoidTy(MContext),
        {SpeculatorVTy->getPointerTo(), Type::getInt64Ty(MContext)}, false);
    auto RuntimeCall =
        Function::Create(RuntimeCallTy, Function::LinkageTypes::ExternalLinkage,
                         "__orc_speculate_for", &M);
    auto SpeclAddr = new GlobalVariable(
        M, SpeculatorVTy, false, GlobalValue::LinkageTypes::ExternalLinkage,
        nullptr, "__orc_speculator");

    IRBuilder<> Mutator(MContext);

    // QueryAnalysis allowed to transform the IR source, one such example is
    // Simplify CFG helps the static branch prediction heuristics!
    for (auto &Fn : M.getFunctionList()) {
      if (!Fn.isDeclaration()) {

        auto IRNames = QueryAnalysis(Fn);
        // Instrument and register if Query has result
        if (IRNames.hasValue()) {

          // Emit globals for each function.
          auto LoadValueTy = Type::getInt8Ty(MContext);
          auto SpeculatorGuard = new GlobalVariable(
              M, LoadValueTy, false, GlobalValue::LinkageTypes::InternalLinkage,
              ConstantInt::get(LoadValueTy, 0),
              "__orc_speculate.guard.for." + Fn.getName());
          SpeculatorGuard->setAlignment(1);
          SpeculatorGuard->setUnnamedAddr(GlobalValue::UnnamedAddr::Local);

          BasicBlock &ProgramEntry = Fn.getEntryBlock();
          // Create BasicBlocks before the program's entry basicblock
          BasicBlock *SpeculateBlock = BasicBlock::Create(
              MContext, "__orc_speculate.block", &Fn, &ProgramEntry);
          BasicBlock *SpeculateDecisionBlock = BasicBlock::Create(
              MContext, "__orc_speculate.decision.block", &Fn, SpeculateBlock);

          assert(SpeculateDecisionBlock == &Fn.getEntryBlock() &&
                 "SpeculateDecisionBlock not updated?");
          Mutator.SetInsertPoint(SpeculateDecisionBlock);

          auto LoadGuard =
              Mutator.CreateLoad(LoadValueTy, SpeculatorGuard, "guard.value");
          // if just loaded value equal to 0,return true.
          auto CanSpeculate =
              Mutator.CreateICmpEQ(LoadGuard, ConstantInt::get(LoadValueTy, 0),
                                   "compare.to.speculate");
          Mutator.CreateCondBr(CanSpeculate, SpeculateBlock, &ProgramEntry);

          Mutator.SetInsertPoint(SpeculateBlock);
          auto ImplAddrToUint =
              Mutator.CreatePtrToInt(&Fn, Type::getInt64Ty(MContext));
          Mutator.CreateCall(RuntimeCallTy, RuntimeCall,
                             {SpeclAddr, ImplAddrToUint});
          Mutator.CreateStore(ConstantInt::get(LoadValueTy, 1),
                              SpeculatorGuard);
          Mutator.CreateBr(&ProgramEntry);

          assert(Mutator.GetInsertBlock()->getParent() == &Fn &&
                 "IR builder association mismatch?");
          S.registerSymbols(internToJITSymbols(IRNames.getValue()),
                            &R.getTargetJITDylib());
        }
      }
    }
  });

  assert(!TSM.withModuleDo([](const Module &M) { return verifyModule(M); }) &&
         "Speculation Instrumentation breaks IR?");

  NextLayer.emit(std::move(R), std::move(TSM));
}

} // namespace orc
} // namespace llvm
