/*
 * Copyright (c) 2024 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/shared_heap/shared_gc.h"

#include "common_components/taskpool/taskpool.h"
#include "ecmascript/mem/shared_heap/shared_concurrent_marker.h"
#include "ecmascript/mem/shared_heap/shared_concurrent_sweeper.h"
#include "ecmascript/mem/shared_heap/shared_gc_evacuator.h"
#include "ecmascript/mem/shared_heap/shared_gc_marker-inl.h"
#include "ecmascript/mem/shared_heap/shared_gc_visitor-inl.h"
#include "ecmascript/mem/verification.h"

namespace panda::ecmascript {
void SharedGC::RunPhases()
{
    ASSERT("SharedGC should be disabled" && !g_isEnableCMCGC);
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, ("SharedGC::RunPhases;GCReason"
        + std::to_string(static_cast<int>(sHeap_->GetEcmaGCStats()->GetGCReason()))
        + ";MarkReason" + std::to_string(static_cast<int>(sHeap_->GetEcmaGCStats()->GetMarkReason()))
        + ";Sensitive" + std::to_string(static_cast<int>(sHeap_->GetSensitiveStatus()))
        + ";IsInBackground" + std::to_string(Runtime::GetInstance()->IsInBackground())
        + ";Startup" + std::to_string(static_cast<int>(sHeap_->GetStartupStatus()))
        + ";Old" + std::to_string(sHeap_->GetOldSpace()->GetCommittedSize())
        + ";huge" + std::to_string(sHeap_->GetHugeObjectSpace()->GetCommittedSize())
        + ";NonMov" + std::to_string(sHeap_->GetNonMovableSpace()->GetCommittedSize())
        + ";TotCommit" + std::to_string(sHeap_->GetCommittedSize())
        + ";NativeBindingSize" + std::to_string(sHeap_->GetNativeSizeAfterLastGC())
        + ";NativeLimitGC" + std::to_string(sHeap_->GetNativeSizeTriggerSharedGC())
        + ";NativeLimitCM" + std::to_string(sHeap_->GetNativeSizeTriggerSharedCM())).c_str(), "");
    TRACE_GC(GCStats::Scope::ScopeId::TotalGC, sHeap_->GetEcmaGCStats());
    sHeap_->SetGCThreadQosPriority(common::PriorityMode::STW);
    markingInProgress_ = sHeap_->CheckOngoingConcurrentMarking();
    Initialize();
    Mark();
    if (UNLIKELY(sHeap_->ShouldVerifyHeap())) {
        // verify mark
        LOG_ECMA(DEBUG) << "start verify mark";
        SharedHeapVerification(sHeap_, VerifyKind::VERIFY_SHARED_GC_MARK).VerifyMark(markingInProgress_);
    }
    PreSweep();
    Evacuate();
    Sweep();
    if (UNLIKELY(sHeap_->ShouldVerifyHeap())) {
        // verify sweep
        LOG_ECMA(DEBUG) << "start verify sweep";
        SharedHeapVerification(sHeap_, VerifyKind::VERIFY_SHARED_GC_SWEEP).VerifySweep(markingInProgress_);
    }
    Finish();
    if (!concurrentProcessStringTable_) {
        sHeap_->SetGCThreadQosPriority(common::PriorityMode::FOREGROUND);
    }
    sHeap_->ResetNativeSizeAfterLastGC();
}

void SharedGC::Initialize()
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "SharedGC::Initialize", "");
    TRACE_GC(GCStats::Scope::ScopeId::Initialize, sHeap_->GetEcmaGCStats());
    if (!markingInProgress_) {
        sHeap_->Prepare(true);
        sHeap_->GetAppSpawnSpace()->EnumerateRegions([](Region *current) {
            current->ClearMarkGCBitset();
            current->ClearCrossRegionRSet();
        });
        sHeap_->EnumerateOldSpaceRegions([](Region *current) {
            ASSERT(current->InSharedSweepableSpace());
            current->ResetAliveObject();
        });
        sWorkManager_->Initialize(TriggerGCType::SHARED_GC, SharedParallelMarkPhase::SHARED_MARK_TASK, 0);
    }
}

void SharedGC::MarkRoots(SharedMarkType markType)
{
    SharedGCMarkRootVisitor sharedGCMarkRootVisitor(sWorkManager_->GetSharedGCWorkNodeHolder(DAEMON_THREAD_INDEX));
    sHeap_->GetSharedGCMarker()->MarkRoots(sharedGCMarkRootVisitor, markType);
}

void SharedGC::Mark()
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "SharedGC::Mark", "");
    TRACE_GC(GCStats::Scope::ScopeId::Mark, sHeap_->GetEcmaGCStats());
    if (markingInProgress_) {
        sHeap_->GetConcurrentMarker()->ReMark();
        return;
    }
    SharedGCMarker *marker = sHeap_->GetSharedGCMarker();
    MarkRoots(SharedMarkType::NOT_CONCURRENT_MARK);
    SharedGCMarkLocalToShareRSetVisitor<SharedMarkType::NOT_CONCURRENT_MARK> rSetVisitor(
        sWorkManager_->GetSharedGCWorkNodeHolder(DAEMON_THREAD_INDEX));
    sHeap_->GetSharedGCMarker()->ProcessLocalToShareRSet(rSetVisitor);
    sHeap_->GetSharedGCMarker()->ProcessMarkStack(DAEMON_THREAD_INDEX);
    marker->MergeBackAndResetRSetWorkListHandler();
    sHeap_->WaitRunningMarkTaskFinished();
}
void SharedGC::PreSweep()
{
    if (markingInProgress_ && sHeap_->GetGCType() == TriggerGCType::SHARED_PARTIAL_GC) {
        sHeap_->GetOldSpace()->SelectCSets();
    }
    sHeap_->GetSweeper()->Sweep(false);
    UpdateRecordWeakReference();
}

void SharedGC::Evacuate()
{
    if (sHeap_->HasCSetRegions()) {
        sHeap_->GetSharedGCEvacuator()->Evacuate();
    }
}

void SharedGC::Sweep()
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "SharedGC::Sweep", "");
    TRACE_GC(GCStats::Scope::ScopeId::Sweep, sHeap_->GetEcmaGCStats());
    WeakRootVisitor gcUpdateWeak = [](TaggedObject *header) -> TaggedObject* {
        Region *objectRegion = Region::ObjectAddressToRange(header);
        if (UNLIKELY(objectRegion == nullptr)) {
            // existence of string table entry with null value is possible
            // only if concurrent sweeping for string table is enabled
            return nullptr;
        }
        if (!objectRegion->InSharedSweepableSpace()) {
            return header;
        }
        if (objectRegion->InSCollectSet()) {
            MarkWord markWord(header, RELAXED_LOAD);
            if (markWord.IsForwardingAddress()) {
                return markWord.ToForwardingAddress();
            }
            return nullptr;
        }
        if (objectRegion->Test(header)) {
            return header;
        }
        return nullptr;
    };
    auto stringTableCleaner = Runtime::GetInstance()->GetEcmaStringTable()->GetCleaner();
    if (stringTableCleaner->IsEnableConcurrentSweep()) {
        concurrentProcessStringTable_ = sHeap_->IsParallelGCEnabled() &&
                                        !Runtime::GetInstance()->GetEcmaStringTable()->IsInUse();
    } else {
        concurrentProcessStringTable_ = false;
    }
    if (!concurrentProcessStringTable_) {
        LOG_GC(DEBUG) << "process string table stw";
        stringTableCleaner->PostSweepWeakRefTask(gcUpdateWeak);
    }
    bool needClearCache = sHeap_->HasCSetRegions();
    Runtime::GetInstance()->ProcessSharedDelete(gcUpdateWeak);
    Runtime::GetInstance()->GCIterateThreadList([&gcUpdateWeak, needClearCache](JSThread *thread) {
        ASSERT(thread->IsSuspended() || thread->HasLaunchedSuspendAll());
        thread->IterateWeakEcmaGlobalStorage(gcUpdateWeak, GCKind::SHARED_GC);
        thread->GetEcmaVM()->ProcessSnapShotEnv(gcUpdateWeak);
        const_cast<Heap*>(thread->GetEcmaVM()->GetHeap())->ResetTlab();
        if (needClearCache) {
            thread->ClearVMCachedConstantPool();
        }
    });

    if (!concurrentProcessStringTable_) {
        stringTableCleaner->JoinAndWaitSweepWeakRefTask(gcUpdateWeak);
    }
    sHeap_->GetSweeper()->PostTask(false);

    if (concurrentProcessStringTable_) {
        LOG_GC(DEBUG) << "process string table concurrently";
        stringTableCleaner->PostConcurrentSweepWeakRefTask(gcUpdateWeak);
    }
}

void SharedGC::Finish()
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "SharedGC::Finish", "");
    TRACE_GC(GCStats::Scope::ScopeId::Finish, sHeap_->GetEcmaGCStats());
    if (markingInProgress_) {
        ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "ConcurrentMarker::Reset", "");
        sHeap_->GetConcurrentMarker()->Reset(false);
    } else {
        ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "WorkManager::Finish", "");
        sWorkManager_->Finish();
    }
    sHeap_->Reclaim(TriggerGCType::SHARED_GC);
    sHeap_->GetSweeper()->TryFillSweptRegion();
}

void SharedGC::UpdateRecordWeakReference()
{
    auto processWeakReference = [](SharedGCWorkNodeHolder *holder) {
        ProcessQueue *queue = holder->GetWeakReferenceQueue();
        while (true) {
            auto obj = queue->PopBack();
            if (UNLIKELY(obj == nullptr)) {
                break;
            }
            ObjectSlot slot(ToUintPtr(obj));
            JSTaggedValue value(slot.GetTaggedType());
            if (value.IsWeak()) {
                auto header = value.GetTaggedWeakRef();
                Region *objectRegion = Region::ObjectAddressToRange(header);
                if (!objectRegion->Test(header)) {
                    slot.Clear();
                }
            }
        }
    };
    auto totalThreadCount = common::Taskpool::GetCurrentTaskpool()->GetTotalThreadNum() + 1;
    for (uint32_t i = 0; i < totalThreadCount; i++) {
        processWeakReference(sWorkManager_->GetSharedGCWorkNodeHolder(i));
    }
    sWorkManager_->ForEachExtraTemporaryWorkNodeHolder(processWeakReference);
}

void SharedGC::ResetWorkManager(SharedGCWorkManager *sWorkManager)
{
    sWorkManager_ = sWorkManager;
}
}  // namespace panda::ecmascript