/*
 * 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_full_gc-inl.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_marker-inl.h"

namespace panda::ecmascript {
void SharedFullGC::RunPhases()
{
    ASSERT("SharedFullGC should be disabled" && !g_isEnableCMCGC);
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, ("SharedFullGC::RunPhases;GCReason"
        + std::to_string(static_cast<int>(sHeap_->GetEcmaGCStats()->GetGCReason()))
        + ";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);
    Initialize();
    Mark();
    Sweep();
    Finish();
    sHeap_->SetGCThreadQosPriority(common::PriorityMode::FOREGROUND);
}

void SharedFullGC::Initialize()
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "SharedFullGC::Initialize", "");
    TRACE_GC(GCStats::Scope::ScopeId::Initialize, sHeap_->GetEcmaGCStats());
    sHeap_->Prepare(true);
    if (UNLIKELY(sHeap_->CheckOngoingConcurrentMarking())) {
        // Concurrent shared mark should always trigger shared gc without moving.
        sHeap_->GetConcurrentMarker()->Reset(true);
    }
    sHeap_->GetAppSpawnSpace()->EnumerateRegions([](Region *current) {
        current->ClearMarkGCBitset();
        current->ClearCrossRegionRSet();
    });
    sHeap_->EnumerateOldSpaceRegions([](Region *current) {
        ASSERT(current->InSharedSweepableSpace());
        current->ResetAliveObject();
    });
    sWorkManager_->Initialize(TriggerGCType::SHARED_FULL_GC, SharedParallelMarkPhase::SHARED_COMPRESS_TASK, 0);
}

void SharedFullGC::MarkRoots(SharedMarkType markType, RootVisitor &rootVisitor)
{
    SharedGCMovableMarker *marker = static_cast<SharedGCMovableMarker*>(sHeap_->GetSharedGCMovableMarker());
    marker->MarkRoots(rootVisitor, markType);
}

void SharedFullGC::Mark()
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "SharedFullGC::Mark", "");
    TRACE_GC(GCStats::Scope::ScopeId::Mark, sHeap_->GetEcmaGCStats());
    SharedGCMovableMarker *marker = sHeap_->GetSharedGCMovableMarker();

    SharedFullGCRunner sRunner(sHeap_, sWorkManager_->GetSharedGCWorkNodeHolder(DAEMON_THREAD_INDEX));
    SharedFullGCMarkRootVisitor &rootVisitor = sRunner.GetMarkRootVisitor();
    SharedFullGCMarkLocalToShareRSetVisitor &rSetVisitor = sRunner.GetMarkLocalToShareRSetVisitor();

    MarkRoots(SharedMarkType::NOT_CONCURRENT_MARK, rootVisitor);
    marker->ProcessLocalToShareRSet(rSetVisitor);
    marker->ProcessMarkStack(DAEMON_THREAD_INDEX);
    marker->MergeBackAndResetRSetWorkListHandler();
    sHeap_->WaitRunningMarkTaskFinished();
}

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

    stringTableCleaner->JoinAndWaitSweepWeakRefTask(gcUpdateWeak);
    sHeap_->GetSweeper()->Sweep(true);
    sHeap_->GetSweeper()->PostTask(true);
}

void SharedFullGC::Finish()
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "SharedFullGC::Finish", "");
    TRACE_GC(GCStats::Scope::ScopeId::Finish, sHeap_->GetEcmaGCStats());
    sHeap_->SwapOldSpace();
    sWorkManager_->Finish();
    if (!isAppspawn_) {
        sHeap_->Reclaim(TriggerGCType::SHARED_FULL_GC);
    } else {
        sHeap_->ReclaimForAppSpawn();
    }

    sHeap_->GetSweeper()->TryFillSweptRegion();
}

void SharedFullGC::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());
            ASSERT(value.IsWeak());
            auto header = value.GetTaggedWeakRef();
            Region *objectRegion = Region::ObjectAddressToRange(header);
            if (!objectRegion->InSharedOldSpace()) {
                if (!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.Clear();
                }
            }
        }
    };
    auto totalThreadCount = common::Taskpool::GetCurrentTaskpool()->GetTotalThreadNum() + 1;
    for (uint32_t i = 0; i < totalThreadCount; i++) {
        processWeakReference(sWorkManager_->GetSharedGCWorkNodeHolder(i));
    }
    sWorkManager_->ForEachExtraTemporaryWorkNodeHolder(processWeakReference);
}

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