/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * 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 <map>

#include "ecmascript/stackmap/ark_stackmap_builder.h"
#include "ecmascript/compiler/aot_file/aot_file_manager.h"
#include "ecmascript/compiler/assembler/assembler.h"
#include "ecmascript/stackmap/ark_stackmap_parser.h"
#include "ecmascript/stackmap/llvm_stackmap_parser.h"
#include "ecmascript/stackmap/litecg_stackmap_type.h"

namespace panda::ecmascript::kungfu {
void BinaryBufferWriter::WriteBuffer(const uint8_t *src, uint32_t count, bool flag)
{
    uint8_t *dst = buffer_ + offset_;
    if (flag) {
        std::cout << "buffer_:0x" << std::hex << buffer_ << " offset_:0x" << offset_ << std::endl;
    }
    if (dst >= buffer_ && dst + count <= buffer_ + length_) {
        if (memcpy_s(dst, buffer_ + length_ - dst, src, count) != EOK) {
            LOG_FULL(FATAL) << "memcpy_s failed";
            return;
        };
        offset_ = offset_ + count;
    }  else {
        LOG_FULL(FATAL) << "parse buffer error, length is 0 or overflow";
    }
}

void ArkStackMapBuilder::Dump(const StackMapDumper& dumpInfo) const
{
    LOG_COMPILER(INFO) << "total callsite num: " << dumpInfo.callsiteNum
                       << ", total ark stack map num: " << dumpInfo.stackmapNum
                       << ", total deopt num: " << dumpInfo.deoptNum;
    double callsiteHeadsSize = static_cast<double>(dumpInfo.callsiteHeadSize);
    double stackMapsSize = static_cast<double>(dumpInfo.arkStackMapSize);
    double deoptsSize = static_cast<double>(dumpInfo.deoptSize);
    LOG_COMPILER(INFO) << "total callsite head size: "
                       << std::fixed << std::setprecision(DECIMAL_LENS)
                       << (callsiteHeadsSize / 1_KB) << "KB, total stackmap size: "
                       << std::fixed << std::setprecision(DECIMAL_LENS)
                       << (stackMapsSize / 1_KB) << "KB, total deopt size: "
                       << std::fixed << std::setprecision(DECIMAL_LENS)
                       << (deoptsSize / 1_KB) << "KB";
}

std::pair<std::shared_ptr<uint8_t>, uint32_t> ArkStackMapBuilder::Run(std::unique_ptr<uint8_t []> stackMapAddr,
    uintptr_t hostCodeSectionAddr, Triple triple)
{
    LLVMStackMapInfo stackMapInfo;
    LLVMStackMapParser parser(stackMapInfo);
    auto result = parser.CalculateStackMap(std::move(stackMapAddr), hostCodeSectionAddr, 0);
    if (!result) {
        LOG_ECMA(FATAL) << "this branch is unreachable";
        UNREACHABLE();
    }
    std::pair<std::shared_ptr<uint8_t>, uint32_t> info = GenerateArkStackMap(stackMapInfo, triple);
    return info;
}

void ArkStackMapBuilder::Collect(
    std::unique_ptr<uint8_t []> stackMapAddr,
    uintptr_t hostCodeSectionAddr,
    uintptr_t hostCodeSectionOffset,
    CGStackMapInfo &stackMapInfo)
{
    LLVMStackMapInfo &llvmStackMapInfo = static_cast<LLVMStackMapInfo&>(stackMapInfo);
    LLVMStackMapParser parser(llvmStackMapInfo);
    auto result = parser.CalculateStackMap(std::move(stackMapAddr), hostCodeSectionAddr, hostCodeSectionOffset);
    if (!result) {
        LOG_ECMA(FATAL) << "this branch is unreachable";
        UNREACHABLE();
    }
}

std::pair<std::shared_ptr<uint8_t>, uint32_t> ArkStackMapBuilder::GenerateArkStackMap(
    CGStackMapInfo &stackMapInfo, Triple triple)
{
    ARKCallsiteAOTFileInfo AOTFileInfo;
    GenArkCallsiteAOTFileInfo(stackMapInfo, AOTFileInfo, triple);
    uint32_t secSize = AOTFileInfo.secHead.secSize;
    uint8_t *p = new(std::nothrow) uint8_t[secSize];
    if (p == nullptr) {
        LOG_FULL(FATAL) << "new secSize:0x" << std::hex << secSize << " failed";
    }
    std::shared_ptr<uint8_t> ptr(p, [](uint8_t *p) { delete []p;});
    SaveArkCallsiteAOTFileInfo(ptr.get(), secSize, AOTFileInfo, triple);
    if (traceStackMap_) {
        Dump(dumper_);
    }
    return std::make_pair(ptr, secSize);
}

void ArkStackMapBuilder::SaveArkStackMap(const ARKCallsiteAOTFileInfo& info, BinaryBufferWriter& writer, Triple triple)
{
    size_t n = info.callsites.size();
    for (size_t i = 0; i < n; i++) {
        auto &callSite = info.callsites.at(i);
        LLVMStackMapType::CallSiteInfo stackmaps = callSite.stackmaps;
        size_t m = stackmaps.size();
        for (size_t j = 0; j < m; j++) {
            auto &stackmap = stackmaps.at(j);
            LLVMStackMapType::DwarfRegType reg = stackmap.first;
            LLVMStackMapType::OffsetType offset = stackmap.second;
            if (j == 0) {
                ASSERT(callSite.head.stackmapOffsetInSMSec == writer.GetOffset());
            }
            std::vector<uint8_t> regOffset;
            size_t regOffsetSize = 0;
            LLVMStackMapType::EncodeRegAndOffset(regOffset, regOffsetSize, reg, offset, triple);
            writer.WriteBuffer(reinterpret_cast<const uint8_t *>(regOffset.data()), regOffset.size());
            dumper_.arkStackMapSize += regOffsetSize;
            if (j == m - 1) {
                ASSERT((callSite.head.stackmapOffsetInSMSec + callSite.CalStackMapSize(triple)) == writer.GetOffset());
            }
        }
    }
    writer.AlignOffset();
}

void ArkStackMapBuilder::SaveArkDeopt(const ARKCallsiteAOTFileInfo& info, BinaryBufferWriter& writer, Triple triple)
{
    for (auto &it: info.callsites) {
        auto& callsite2Deopt = it.callsite2Deopt;
        size_t m = callsite2Deopt.size();
        for (size_t j = 0; j < m; j++) {
            auto &deopt = callsite2Deopt.at(j);
            if (j == 0) {
                ASSERT(it.head.deoptOffset == writer.GetOffset());
            }
            std::vector<uint8_t> vregsInfo;
            size_t vregsInfoSize = 0;
            LLVMStackMapType::EncodeVRegsInfo(vregsInfo, vregsInfoSize, deopt.id, deopt.kind);
            writer.WriteBuffer(reinterpret_cast<const uint8_t *>(vregsInfo.data()), vregsInfoSize);
            dumper_.deoptSize += vregsInfoSize;
            auto& value = deopt.value;
            if (std::holds_alternative<LLVMStackMapType::IntType>(value)) {
                LLVMStackMapType::IntType v = std::get<LLVMStackMapType::IntType>(value);
                std::vector<uint8_t> num;
                size_t numSize = 0;
                LLVMStackMapType::EncodeData(num, numSize, v);
                writer.WriteBuffer(reinterpret_cast<const uint8_t *>(num.data()), numSize);
                dumper_.deoptSize += numSize;
            } else if (std::holds_alternative<LLVMStackMapType::LargeInt>(value)) {
                LLVMStackMapType::LargeInt v = std::get<LLVMStackMapType::LargeInt>(value);
                std::vector<uint8_t> num;
                size_t numSize = 0;
                LLVMStackMapType::EncodeData(num, numSize, v);
                writer.WriteBuffer(reinterpret_cast<const uint8_t *>(num.data()), numSize);
                dumper_.deoptSize += numSize;
            } else if (std::holds_alternative<LLVMStackMapType::DwarfRegAndOffsetType>(value)) {
                LLVMStackMapType::DwarfRegAndOffsetType v = std::get<LLVMStackMapType::DwarfRegAndOffsetType>(value);
                std::vector<uint8_t> regOffset;
                size_t regOffsetSize = 0;
                LLVMStackMapType::EncodeRegAndOffset(regOffset, regOffsetSize, v.first, v.second, triple);
                writer.WriteBuffer(reinterpret_cast<const uint8_t *>(regOffset.data()), regOffset.size());
                dumper_.arkStackMapSize += regOffsetSize;
            } else {
                LOG_ECMA(FATAL) << "this branch is unreachable";
                UNREACHABLE();
            }
        }
    }
}

void ArkStackMapBuilder::SaveArkCallsiteAOTFileInfo(uint8_t *ptr, uint32_t length,
    const ARKCallsiteAOTFileInfo& info, Triple triple)
{
    BinaryBufferWriter writer(ptr, length);
    ASSERT(length >= info.secHead.secSize);
    writer.WriteBuffer(reinterpret_cast<const uint8_t *>(&(info.secHead)), sizeof(ArkStackMapHeader));
    dumper_.callsiteHeadSize += sizeof(ArkStackMapHeader);
    for (auto &it: info.callsites) {
        writer.WriteBuffer(reinterpret_cast<const uint8_t *>(&(it.head)), sizeof(CallsiteHeader));
        dumper_.callsiteHeadSize += sizeof(CallsiteHeader);
    }
    SaveArkStackMap(info, writer, triple);
    SaveArkDeopt(info, writer, triple);
#ifndef NDEBUG
    ArkStackMapParser parser;
    parser.ParseArkStackMapAndDeopt(ptr, length);
#endif
}

template <class Vec>
void ArkStackMapBuilder::SortCallSite(
    const std::vector<std::unordered_map<uintptr_t, Vec>> &infos,
    std::vector<std::pair<uintptr_t, Vec>>& result)
{
    for (auto &info: infos) {
        for (auto &it: info) {
            result.emplace_back(it);
        }
    }
    std::sort(result.begin(), result.end(),
        [](const std::pair<uintptr_t, Vec> &x, const std::pair<uintptr_t, Vec> &y) {
            return x.first < y.first;
        });
}

void ArkStackMapBuilder::CalcCallsitePc(std::vector<std::pair<uintptr_t, LLVMStackMapType::DeoptInfoType>> &pc2Deopt,
    std::vector<std::pair<uintptr_t, LLVMStackMapType::CallSiteInfo>> &pc2StackMap, std::vector<intptr_t> &callsitePcs)
{
    std::set<uintptr_t> pcSet;
    for (auto &it: pc2Deopt) {
        pcSet.insert(it.first);
    }
    for (auto &it: pc2StackMap) {
        pcSet.insert(it.first);
    }
    callsitePcs.assign(pcSet.begin(), pcSet.end());
}

int ArkStackMapBuilder::FindLoc(std::vector<intptr_t> &CallsitePcs, intptr_t pc)
{
    for (size_t i = 0; i < CallsitePcs.size(); i++) {
        if (CallsitePcs[i] == pc) {
            return i;
        }
    }
    return -1;
}

void ArkStackMapBuilder::GenARKDeopt(const LLVMStackMapType::DeoptInfoType& deopt, std::pair<uint32_t,
                                     std::vector<ARKDeopt>> &sizeAndArkDeopt, Triple triple)
{
    ASSERT(deopt.size() % DEOPT_ENTRY_SIZE == 0); // 2:<id, value>
    uint32_t total = 0;
    ARKDeopt v;
    for (size_t i = 0; i < deopt.size(); i += 2) { // 2:<id, value>
        ASSERT(std::holds_alternative<LLVMStackMapType::IntType>(deopt[i]));
        LLVMStackMapType::VRegId id = static_cast<LLVMStackMapType::VRegId>(
            std::get<LLVMStackMapType::IntType>(deopt[i]));
        v.id = id;
        auto value = deopt[i + 1];
        if (std::holds_alternative<LLVMStackMapType::IntType>(value)) {
            v.kind = LocationTy::Kind::CONSTANT;
            v.value = std::get<LLVMStackMapType::IntType>(value);
            std::vector<uint8_t> vregsInfo;
            size_t vregsInfoSize = 0;
            LLVMStackMapType::EncodeVRegsInfo(vregsInfo, vregsInfoSize, v.id, v.kind);
            size_t valueSize = panda::leb128::SignedEncodingSize(std::get<LLVMStackMapType::IntType>(value));
            total += (vregsInfoSize + valueSize);
        } else if (std::holds_alternative<LLVMStackMapType::LargeInt>(value)) {
            v.kind = LocationTy::Kind::CONSTANTNDEX;
            v.value = std::get<LLVMStackMapType::LargeInt>(value);
            std::vector<uint8_t> vregsInfo;
            size_t vregsInfoSize = 0;
            LLVMStackMapType::EncodeVRegsInfo(vregsInfo, vregsInfoSize, v.id, v.kind);
            size_t valueSize = panda::leb128::SignedEncodingSize(std::get<LLVMStackMapType::LargeInt>(value));
            total += (vregsInfoSize + valueSize);
        } else if (std::holds_alternative<LLVMStackMapType::DwarfRegAndOffsetType>(value)) {
            v.kind = LocationTy::Kind::INDIRECT;
            v.value = std::get<LLVMStackMapType::DwarfRegAndOffsetType>(value);
            std::vector<uint8_t> vregsInfo;
            size_t vregsInfoSize = 0;
            LLVMStackMapType::EncodeVRegsInfo(vregsInfo, vregsInfoSize, v.id, v.kind);
            LLVMStackMapType::DwarfRegType reg = std::get<LLVMStackMapType::DwarfRegAndOffsetType>(value).first;
            LLVMStackMapType::OffsetType offset = std::get<LLVMStackMapType::DwarfRegAndOffsetType>(value).second;
            std::vector<uint8_t> regOffset;
            size_t regOffsetSize = 0;
            LLVMStackMapType::EncodeRegAndOffset(regOffset, regOffsetSize, reg, offset, triple);
            total += (vregsInfoSize + regOffsetSize);
        } else {
            LOG_ECMA(FATAL) << "this branch is unreachable";
            UNREACHABLE();
        }
        sizeAndArkDeopt.second.emplace_back(v);
    }
    std::sort(sizeAndArkDeopt.second.begin(), sizeAndArkDeopt.second.end(),
        [](const ARKDeopt &a, const ARKDeopt &b) {
            return a.id < b.id;
        });
    sizeAndArkDeopt.first = total;
}

void ArkStackMapBuilder::GenArkCallsiteAOTFileInfo(const CGStackMapInfo &stackMapInfo,
                                                   ARKCallsiteAOTFileInfo &result, Triple triple)
{
    std::vector<std::pair<uintptr_t, LLVMStackMapType::CallSiteInfo>> pc2StackMaps;
    std::vector<std::pair<uintptr_t, LLVMStackMapType::DeoptInfoType>> pc2Deopts;
    if (stackMapInfo.GetStackMapKind() == CGStackMapInfo::kLiteCGStackMapInfo) {
        std::vector<LLVMStackMapType::Pc2CallSiteInfo> pc2StackMapsVec;
        std::vector<LLVMStackMapType::Pc2Deopt> pc2DeoptInfoVec;
        const auto &liteCGStackMapInfo = static_cast<const LiteCGStackMapInfo&>(stackMapInfo);
        liteCGStackMapInfo.ConvertToLLVMStackMapInfo(pc2StackMapsVec, pc2DeoptInfoVec, triple);
        SortCallSite(pc2StackMapsVec, pc2StackMaps);
        SortCallSite(pc2DeoptInfoVec, pc2Deopts);
    } else {
        const auto &llvmStackMapInfo = static_cast<const LLVMStackMapInfo&>(stackMapInfo);
        SortCallSite(llvmStackMapInfo.GetCallSiteInfoVec(), pc2StackMaps);
        SortCallSite(llvmStackMapInfo.GetDeoptInfoVec(), pc2Deopts);
    }
    ARKCallsite callsite;
    uint32_t secSize = 0;

    std::vector<intptr_t> CallsitePcs;

    CalcCallsitePc(pc2Deopts, pc2StackMaps, CallsitePcs);
    uint32_t callsiteNum = CallsitePcs.size();
    dumper_.callsiteNum = callsiteNum;
    result.callsites.resize(callsiteNum);
    uint32_t stackmapOffset = sizeof(ArkStackMapHeader) + sizeof(CallsiteHeader) * callsiteNum;
    for (auto &x: pc2StackMaps) {
        LLVMStackMapType::CallSiteInfo i = x.second;
        callsite.head.calliteOffsetInTxtSec = x.first;
        ASSERT(std::numeric_limits<uint16_t>::min() <= i.size() && i.size() <= std::numeric_limits<uint16_t>::max());
        callsite.head.stackmapNum = i.size();
        callsite.head.stackmapOffsetInSMSec = stackmapOffset;
        callsite.head.deoptOffset = 0;
        callsite.head.deoptNum = 0;
        callsite.stackmaps = i;
        stackmapOffset += callsite.CalStackMapSize(triple);
        int loc = FindLoc(CallsitePcs, x.first);
        ASSERT(loc >= 0 && loc < static_cast<int>(callsiteNum));
        result.callsites[static_cast<uint32_t>(loc)] = callsite;
        dumper_.stackmapNum += i.size();
    }
    stackmapOffset = AlignUp(stackmapOffset, LLVMStackMapType::STACKMAP_ALIGN_BYTES);
    secSize = stackmapOffset;
    for (auto &x: pc2Deopts) {
        int loc = FindLoc(CallsitePcs, x.first);
        ASSERT(loc >= 0 && loc < static_cast<int>(callsiteNum));
        LLVMStackMapType::DeoptInfoType deopt = x.second;
        result.callsites[static_cast<uint32_t>(loc)].head.calliteOffsetInTxtSec = x.first;
        ASSERT(std::numeric_limits<uint16_t>::min() <= deopt.size()
            && deopt.size() <= std::numeric_limits<uint16_t>::max());
        result.callsites[static_cast<uint32_t>(loc)].head.deoptNum = deopt.size();
        result.callsites[static_cast<uint32_t>(loc)].head.deoptOffset = secSize;
        std::pair<uint32_t, std::vector<ARKDeopt>> sizeAndArkDeopt;
        GenARKDeopt(deopt, sizeAndArkDeopt, triple);
        secSize += sizeAndArkDeopt.first;
        result.callsites[static_cast<uint32_t>(loc)].callsite2Deopt = sizeAndArkDeopt.second;
        dumper_.deoptNum += deopt.size();
    }
    result.secHead.callsiteNum = callsiteNum;
    result.secHead.secSize = secSize;
}
} // namespace panda::ecmascript::kungfu