/*
 * Copyright (c) 2021 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 "ecmascript/mem/full_gc-inl.h"

#include "common_components/taskpool/taskpool.h"
#include "ecmascript/js_weak_container.h"
#include "ecmascript/linked_hash_table.h"
#include "ecmascript/mem/concurrent_marker.h"
#include "ecmascript/mem/parallel_marker.h"
#include "ecmascript/mem/verification.h"
#include "ecmascript/runtime_call_id.h"

namespace panda::ecmascript {
FullGC::FullGC(Heap *heap) : heap_(heap), workManager_(heap->GetWorkManager()) {}

void FullGC::RunPhases()
{
    ASSERT("FullGC should be disabled" && !g_isEnableCMCGC);
    if (Runtime::GetInstance()->IsInBackground()) {
        common::Taskpool::GetCurrentTaskpool()->SetThreadPriority(common::PriorityMode::HIGH_PRIORITY_BACKGROUND);
    } else {
        common::Taskpool::GetCurrentTaskpool()->SetThreadPriority(common::PriorityMode::STW);
    }
    GCStats *gcStats = heap_->GetEcmaVM()->GetEcmaGCStats();
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, ("FullGC::RunPhases;GCReason"
        + std::to_string(static_cast<int>(gcStats->GetGCReason()))
        + ";Sensitive" + std::to_string(static_cast<int>(heap_->GetSensitiveStatus()))
        + ";IsInBackground" + std::to_string(Runtime::GetInstance()->IsInBackground())
        + ";Startup" + std::to_string(static_cast<int>(heap_->GetStartupStatus()))
#if USE_CMS_GC  // fixme: refactor?
        + ";Slot" + std::to_string(heap_->GetSlotSpace()->GetCommittedSize())
#else
        + ";Young" + std::to_string(heap_->GetNewSpace()->GetCommittedSize())
        + ";Old" + std::to_string(heap_->GetOldSpace()->GetCommittedSize())
#endif
        + ";huge" + std::to_string(heap_->GetHugeObjectSpace()->GetCommittedSize())
        + ";NonMov" + std::to_string(heap_->GetNonMovableSpace()->GetCommittedSize())
        + ";TotCommit" + std::to_string(heap_->GetCommittedSize())
        + ";ObjSizeBeforeSensitive"
        + std::to_string(heap_->GetRecordHeapObjectSizeBeforeSensitive())).c_str(), "");
    TRACE_GC(GCStats::Scope::ScopeId::TotalGC, gcStats);
    MEM_ALLOCATE_AND_GC_TRACE(heap_->GetEcmaVM(), FullGC_RunPhases);

    if (heap_->CheckOngoingConcurrentMarking()) {
        LOG_GC(DEBUG) << "FullGC after ConcurrentMarking";
        heap_->GetConcurrentMarker()->Reset();  // HPPGC use mark result to move TaggedObject.
    }
    ASSERT(!heap_->GetJSThread()->IsConcurrentCopying());
    ProcessSharedGCRSetWorkList();
    Initialize();
    Mark();
    Sweep();
    Finish();
    if (UNLIKELY(heap_->ShouldVerifyHeap())) {
        // verify mark
        LOG_ECMA(DEBUG) << "start verify post fullgc";
        Verification(heap_, VerifyKind::VERIFY_SHARED_RSET_POST_FULL_GC).VerifyAll();
    }
    common::Taskpool::GetCurrentTaskpool()->SetThreadPriority(common::PriorityMode::FOREGROUND);
}

void FullGC::RunPhasesForAppSpawn()
{
    auto marker = reinterpret_cast<CompressGCMarker*>(heap_->GetCompressGCMarker());
    marker->SetAppSpawn(true);
    RunPhases();
    marker->SetAppSpawn(false);
}

void FullGC::Initialize()
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "FullGC::Initialize", "");
    TRACE_GC(GCStats::Scope::ScopeId::Initialize, heap_->GetEcmaVM()->GetEcmaGCStats());
    heap_->Prepare();
    auto callback = [](Region *current) {
        // fixme: refactor?
        if constexpr (G_USE_CMS_GC) {
            ASSERT(current->AliveObject() == 0);
        } else {
            current->ResetAliveObject();
        }
        current->ClearOldToNewRSet();
    };
    heap_->EnumerateNonMovableRegions(callback);
    heap_->GetAppSpawnSpace()->EnumerateRegions([](Region *current) {
        current->ClearMarkGCBitset();
        current->ClearCrossRegionRSet();
    });
    // fixme: refactor?
    if constexpr (G_USE_CMS_GC) {
        heap_->GetSlotSpace()->PrepareCompact();
        if constexpr (G_USE_STICKY_CMS_GC) {
            heap_->ClearGCBitSetForCMS();
        }
    } else {
        heap_->SwapNewSpace();
    }
    workManager_->Initialize(TriggerGCType::FULL_GC, ParallelGCTaskPhase::COMPRESS_HANDLE_GLOBAL_POOL_TASK);
    heap_->GetCompressGCMarker()->Initialize();
}

void FullGC::MarkRoots()
{
    CompressGCMarker *marker = static_cast<CompressGCMarker*>(heap_->GetCompressGCMarker());
    FullGCRunner fullGCRunner(heap_, workManager_->GetWorkNodeHolder(MAIN_THREAD_INDEX), forAppSpawn_);
    FullGCMarkRootVisitor &fullGCMarkRootVisitor = fullGCRunner.GetMarkRootVisitor();
    marker->MarkRoots(fullGCMarkRootVisitor);
}

void FullGC::Mark()
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "FullGC::Mark", "");
    TRACE_GC(GCStats::Scope::ScopeId::Mark, heap_->GetEcmaVM()->GetEcmaGCStats());
    MarkRoots();
    CompressGCMarker *marker = static_cast<CompressGCMarker *>(heap_->GetCompressGCMarker());
    marker->ProcessMarkStack(MAIN_THREAD_INDEX);
    heap_->WaitRunningMarkTaskFinished();

    marker->MarkJitCodeMap(MAIN_THREAD_INDEX);
    marker->ProcessMarkStack(MAIN_THREAD_INDEX);
    heap_->WaitRunningMarkTaskFinished();

    bool prev = heap_->IsParallelGCEnabled();
    heap_->SetParallelGCEnabled(false);
    MarkUntilFixPoint();
    heap_->SetParallelGCEnabled(prev);
}

void FullGC::MarkUntilFixPoint()
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "FullGC::MarkUntilFixPoint", "");
    CompressGCMarker *marker = static_cast<CompressGCMarker *>(heap_->GetCompressGCMarker());
    WorkNodeHolder *holder = workManager_->GetWorkNodeHolder(MAIN_THREAD_INDEX);
    FullGCRunner runner(heap_, holder, forAppSpawn_);

    std::vector<WeakAggregate> pendingWeakAggregates;
    ASSERT(holder->freshWeakAggregateWorkNodeWrapper_.IsLocalAndGlobalEmpty());

    WeakAggregate weakAggregate;
    while (holder->PopPendingWeakAggregate(&weakAggregate)) {
        if (!runner.HandleWeakAggregate(weakAggregate)) {
            pendingWeakAggregates.emplace_back(weakAggregate);
        }
    }
    while (true) {
        if (holder->markWorkNodeWrapper_.IsLocalAndGlobalEmpty()) {
            return;
        }
        marker->ProcessMarkStack(MAIN_THREAD_INDEX);
        marker->MarkJitCodeMap(MAIN_THREAD_INDEX);

        while (holder->PopFreshWeakAggregate(&weakAggregate)) {
            if (!runner.HandleWeakAggregate(weakAggregate)) {
                pendingWeakAggregates.emplace_back(weakAggregate);
            }
        }

        auto it = pendingWeakAggregates.begin();
        while (it != pendingWeakAggregates.end()) {
            if (runner.HandleWeakAggregate(*it)) {
                *it = pendingWeakAggregates.back();
                if (it + 1 == pendingWeakAggregates.end()) {
                    pendingWeakAggregates.pop_back();
                    it = pendingWeakAggregates.end();
                } else {
                    pendingWeakAggregates.pop_back();
                }
            } else {
                ++it;
            }
        }
    }
}

void FullGC::Sweep()
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "FullGC::Sweep", "");
    TRACE_GC(GCStats::Scope::ScopeId::Sweep, heap_->GetEcmaVM()->GetEcmaGCStats());
    // process weak reference
    uint32_t totalThreadCount = 1; // 1 : mainthread
    if (heap_->IsParallelGCEnabled()) {
        totalThreadCount += common::Taskpool::GetCurrentTaskpool()->GetTotalThreadNum();
    }
    for (uint32_t i = 0; i < totalThreadCount; i++) {
        UpdateRecordWeakReference(i);
    }
    for (uint32_t i = 0; i < totalThreadCount; i++) {
        UpdateRecordWeakLinkedHashMap(i);
    }

    WeakRootVisitor gcUpdateWeak = [this](TaggedObject *header) -> TaggedObject* {
        Region *objectRegion = Region::ObjectAddressToRange(header);
        if (UNLIKELY(objectRegion == nullptr)) {
            LOG_GC(ERROR) << "FullGC updateWeakReference: region is nullptr, header is " << header;
            return nullptr;
        }
        if (!HasEvacuated(objectRegion)) {
            // The weak object in shared heap is always alive during fullGC.
            if (objectRegion->InSharedHeap() || objectRegion->Test(header)) {
                return header;
            }
            return nullptr;
        }

        MarkWord markWord(header, RELAXED_LOAD);
        if (markWord.IsForwardingAddress()) {
            return markWord.ToForwardingAddress();
        }
        return nullptr;
    };
    heap_->GetEcmaVM()->GetJSThread()->IterateWeakEcmaGlobalStorage(gcUpdateWeak);
    heap_->GetEcmaVM()->ProcessReferences(gcUpdateWeak);
    heap_->GetEcmaVM()->ProcessSnapShotEnv(gcUpdateWeak);
    heap_->GetEcmaVM()->GetJSThread()->UpdateJitCodeMapReference(gcUpdateWeak);
    heap_->GetEcmaVM()->IterateWeakGlobalEnvList(gcUpdateWeak);
    heap_->GetSweeper()->Sweep(TriggerGCType::FULL_GC);
    heap_->GetSweeper()->PostTask(TriggerGCType::FULL_GC);
    heap_->GetJSThread()->ClearYoungGlobalList();
    heap_->GetJSThread()->ClearToBeDeletedNodes();
}

void FullGC::Finish()
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "FullGC::Finish", "");
    TRACE_GC(GCStats::Scope::ScopeId::Finish, heap_->GetEcmaVM()->GetEcmaGCStats());
    // fixme: refactor?
    if constexpr (!G_USE_CMS_GC) {
        if (!forAppSpawn_) {
            heap_->SwapOldSpace();
        }
    } else {
        heap_->GetSlotSpace()->MergeToRegions();
    }
    workManager_->Finish();
    if (forAppSpawn_) {
        heap_->ResumeForAppSpawn();
    } else {
        heap_->Resume(FULL_GC);
    }
    heap_->GetSweeper()->TryFillSweptRegion();
    heap_->SetFullMarkRequestedState(false);
}

bool FullGC::HasEvacuated(Region *region)
{
    if (forAppSpawn_) {
        return !region->InHugeObjectSpace()  && !region->InReadOnlySpace() && !region->InNonMovableSpace() &&
               !region->InSharedHeap();
    }
    // fixme: refactor?
    if constexpr (G_USE_CMS_GC) {
        return region->InSlotSpace();
    }
    return region->InYoungOrOldSpace();
}

void FullGC::SetForAppSpawn(bool flag)
{
    forAppSpawn_ = flag;
}

void FullGC::ProcessSharedGCRSetWorkList()
{
    TRACE_GC(GCStats::Scope::ScopeId::ProcessSharedGCRSetWorkList, heap_->GetEcmaVM()->GetEcmaGCStats());
    heap_->ProcessSharedGCRSetWorkList();
}

void FullGC::UpdateRecordWeakReference(uint32_t threadId)
{
    ProcessQueue *queue = workManager_->GetWorkNodeHolder(threadId)->GetWeakReferenceQueue();
    while (true) {
        auto obj = queue->PopBack();
        if (UNLIKELY(obj == nullptr)) {
            break;
        }
        ObjectSlot slot(ToUintPtr(obj));
        JSTaggedValue value(slot.GetTaggedType());
        auto header = value.GetTaggedWeakRef();
        Region *objectRegion = Region::ObjectAddressToRange(header);
        if (!HasEvacuated(objectRegion)) {
            if (!objectRegion->InSharedHeap() && !objectRegion->Test(header)) {
                slot.Clear();
            }
        } else {
            MarkWord markWord(header, RELAXED_LOAD);
            if (markWord.IsForwardingAddress()) {
                TaggedObject *dst = markWord.ToForwardingAddress();
                auto weakRef = JSTaggedValue(JSTaggedValue(dst).CreateAndGetWeakRef()).GetRawTaggedObject();
                slot.Update(weakRef);
            } else {
                slot.Update(static_cast<JSTaggedType>(JSTaggedValue::Undefined().GetRawData()));
            }
        }
    }
}

void FullGC::UpdateRecordWeakLinkedHashMap(uint32_t threadId)
{
    WeakLinkedHashMapProcessQueue *queue = workManager_->GetWorkNodeHolder(threadId)->GetWeakLinkedHashMapQueue();
    JSThread *thread = heap_->GetJSThread();
    while (true) {
        TaggedObject *obj = queue->PopBack();
        if (UNLIKELY(obj == nullptr)) {
            break;
        }

        WeakLinkedHashMap *map = WeakLinkedHashMap::Cast(obj);
        ASSERT(map->VerifyLayout());

        int entries = map->NumberOfAllUsedElements();
        for (int i = 0; i < entries; ++i) {
            JSTaggedValue maybeKey = map->GetKey(thread, i);
            if (maybeKey.IsUndefined()) {
                // set to undefined by GC since key is dead, and the weak ref of key is updated.
                map->RemoveEntryFromGCThread(i);
            }
        }
    }
}
}  // namespace panda::ecmascript