/*
 * Copyright (c) 2025 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_evacuator.h"

#include "common_components/taskpool/taskpool.h"
#include "ecmascript/mem/object_xray.h"
#include "ecmascript/mem/tlab_allocator-inl.h"

namespace panda::ecmascript {
void SharedGCEvacuator::Evacuate()
{
    EvacuateRegions();
    UpdateReference();
}

void SharedGCEvacuator::EvacuateRegionWorkload::Process(uint32_t threadIndex)
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "SharedGCEvacuator::EvacuateRegionWorkload", "");
    auto sTlabAllocator = evacuator_->GetOrCreateTlab(threadIndex);
    region_->IterateAllMarkedBits([this, sTlabAllocator](void *mem) {
        auto header = reinterpret_cast<TaggedObject *>(mem);
        JSHClass *klass = header->GetClass();
        size_t size = header->GetSize();
        uintptr_t address = sTlabAllocator->Allocate(size, SHARED_COMPRESS_SPACE);
        ASSERT(address != 0);
        if (memcpy_s(ToVoidPtr(address), size, ToVoidPtr(ToUintPtr(mem)), size) != EOK) { // LOCV_EXCL_BR_LINE
            LOG_ECMA_MEM(FATAL) << "memcpy_s failed";
            UNREACHABLE();
        }
        if (UNLIKELY(inHeapProfiler_)) {
            sHeap_->OnMoveEvent(reinterpret_cast<intptr_t>(mem), reinterpret_cast<TaggedObject *>(address), size);
        }
        Barriers::SetPrimitive(header, 0, MarkWord::FromForwardingAddress(address));
    });
}

void SharedGCEvacuator::EvacuateRegions()
{
    TRACE_GC(GCStats::Scope::ScopeId::Evacuate,  sHeap_->GetEcmaGCStats());
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, ("SharedGCEvacuator::EvacuateRegions;cset count: "
        + std::to_string(sHeap_->GetOldSpace()->GetCollectSetRegionCount())).c_str(), "");
    bool inHeapProfiler = sHeap_->InHeapProfiler();
    sHeap_->GetOldSpace()->EnumerateCollectRegionSet([this, inHeapProfiler](Region *region) {
        ASSERT(region->InSCollectSet());
        AddWorkload(std::make_unique<EvacuateRegionWorkload>(this, region, sHeap_, inHeapProfiler));
    });
    PostParallelTasks();
    ProcessWorkloads(MAIN_THREAD_INDEX);
    for (auto sTlabAllocator : sTlabs_) {
        if (sTlabAllocator) {
            sTlabAllocator->Finalize();
            delete sTlabAllocator;
        }
    }
    sTlabs_.fill(nullptr);
}

void SharedGCEvacuator::UpdateReference()
{
    TRACE_GC(GCStats::Scope::ScopeId::UpdateReference,  sHeap_->GetEcmaGCStats());
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "SharedGCEvacuator::UpdateReference", "");
    Runtime *runtime = Runtime::GetInstance();
    runtime->GCIterateThreadList([this](JSThread *thread) {
        ASSERT(thread->IsSuspended() || thread->HasLaunchedSuspendAll());
        auto heap = const_cast<Heap*>(thread->GetEcmaVM()->GetHeap());
        heap->GetSweeper()->EnsureAllTaskFinished();
        heap->EnumerateRegions([this](Region *region) {
            AddWorkload(std::make_unique<UpdateLocalReferenceWorkload>(this, region));
        });
    });
    sHeap_->EnumerateOldSpaceRegions([this](Region *region) {
        ASSERT(!region->InSCollectSet());
        if (region->IsToRegion()) {
            AddWorkload(std::make_unique<UpdateToRegionReferenceWorkload>(this, region));
        } else {
            AddWorkload(std::make_unique<UpdateSharedReferenceWorkload>(this, region));
        }
    });
    PostParallelTasks();
    runtime->IterateSharedRoot(rootVisitor_);
    runtime->GCIterateThreadList([this](JSThread *thread) {
        ASSERT(thread->IsSuspended() || thread->HasLaunchedSuspendAll());
        auto vm = thread->GetEcmaVM();
        ObjectXRay::VisitVMRoots(vm, rootVisitor_);
    });
    ProcessWorkloads(MAIN_THREAD_INDEX);
}

void SharedGCEvacuator::UpdateLocalReferenceWorkload::Process([[maybe_unused]]uint32_t threadIndex)
{
    region_->IterateAllLocalToShareBits([this](void *mem) {
        ObjectSlot slot(ToUintPtr(mem));
        return evacuator_->UpdateObjectSlot(slot);
    });
}

void SharedGCEvacuator::UpdateSharedReferenceWorkload::Process([[maybe_unused]]uint32_t threadIndex)
{
    region_->IterateAllCrossRegionBits([this](void *mem) {
        ObjectSlot slot(ToUintPtr(mem));
        evacuator_->UpdateObjectSlot(slot);
    });
}

void SharedGCEvacuator::UpdateToRegionReferenceWorkload::Process([[maybe_unused]]uint32_t threadIndex)
{
    uintptr_t curPtr = region_->GetBegin();
    uintptr_t endPtr = region_->GetEnd();
    size_t objSize = 0;
    while (curPtr < endPtr) {
        auto freeObject = FreeObject::Cast(curPtr);
        if (!freeObject->IsFreeObject()) {
            auto obj = reinterpret_cast<TaggedObject *>(curPtr);
            evacuator_->ProcessObjectField(obj, obj->GetClass());
            objSize = obj->GetSize();
        } else {
            objSize = freeObject->Available();
        }
        curPtr += objSize;
        CHECK_OBJECT_SIZE(objSize);
    }
    CHECK_REGION_END(curPtr, endPtr);
}

bool SharedGCEvacuator::ParallelTask::Run([[maybe_unused]]uint32_t threadIndex)
{
    ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "SharedGCEvacuator::ParallelTask", "");
    evacuator_->ProcessWorkloads(threadIndex);
    return true;
}

void SharedGCEvacuator::WaitFinished()
{
    if (parallel_ > 0) {
        LockHolder holder(lock_);
        while (parallel_ > 0) {
            condition_.Wait(&lock_);
        }
    }
}

int SharedGCEvacuator::CalculateParallelThreadNum()
{
    constexpr uint32_t regionPerThread = 8;
    uint32_t count = workloads_.size() / regionPerThread;
    return static_cast<int>(std::min(std::max(1U, count), common::Taskpool::GetCurrentTaskpool()->GetTotalThreadNum()));
}

bool SharedGCEvacuator::UpdateObjectSlot(ObjectSlot slot)
{
    JSTaggedValue value(slot.GetTaggedType());
    if (!value.IsHeapObject()) {
        return false;
    }
    Region *region = Region::ObjectAddressToRange(slot.GetTaggedType());
    if (!region->InSCollectSet()) {
        return true;
    }
    ASSERT(region->InSharedHeap());
    bool isWeak = value.IsWeakForHeapObject();
    TaggedObject *object = value.GetHeapObject();
    MarkWord markWord(object, RELAXED_LOAD);
    ASSERT(markWord.IsForwardingAddress());
    TaggedObject *dst = markWord.ToForwardingAddress();
    if (isWeak) {
        dst = JSTaggedValue(dst).CreateAndGetWeakRef().GetRawTaggedObject();
    }
    slot.Update(dst);
    return true;
}

void SharedGCEvacuator::ObjectFieldCSetVisitor::VisitObjectRangeImpl(BaseObject *root, uintptr_t startAddr,
    uintptr_t endAddr, VisitObjectArea area)
{
    ObjectSlot start(startAddr);
    ObjectSlot end(endAddr);
    if (UNLIKELY(area == VisitObjectArea::IN_OBJECT)) {
        JSHClass *hclass = TaggedObject::Cast(root)->GetClass();
        ASSERT(!hclass->IsAllTaggedProp());
        int index = 0;
        TaggedObject *dst = hclass->GetLayout(THREAD_ARG_PLACEHOLDER).GetTaggedObject();
        LayoutInfo *layout = LayoutInfo::UncheckCast(dst);
        ObjectSlot realEnd = start;
        realEnd += layout->GetPropertiesCapacity();
        end = end > realEnd ? realEnd : end;
        for (ObjectSlot slot = start; slot < end; slot++) {
            auto attr = layout->GetAttr(THREAD_ARG_PLACEHOLDER, index++);
            if (attr.IsTaggedRep()) {
                evacuator_->UpdateObjectSlot(slot);
            }
        }
        return;
    }
    for (ObjectSlot slot = start; slot < end; slot++) {
        evacuator_->UpdateObjectSlot(slot);
    }
}

void SharedGCEvacuator::ProcessObjectField(TaggedObject *object, JSHClass *hclass)
{
    ObjectXRay::VisitObjectBody<VisitType::OLD_GC_VISIT>(object, hclass, objectFieldCSetVisitor_);
}

void SharedGCEvacuator::UpdateRootVisitor::VisitRoot([[maybe_unused]] Root type, ObjectSlot slot)
{
    UpdateObjectSlotRoot(slot);
}

void SharedGCEvacuator::UpdateRootVisitor::VisitRangeRoot([[maybe_unused]] Root type, ObjectSlot start, ObjectSlot end)
{
    for (ObjectSlot slot = start; slot < end; slot++) {
        UpdateObjectSlotRoot(slot);
    }
}

void SharedGCEvacuator::UpdateRootVisitor::VisitBaseAndDerivedRoot([[maybe_unused]] Root type, ObjectSlot base,
                                                                   ObjectSlot derived, uintptr_t baseOldObject)
{
    if (JSTaggedValue(base.GetTaggedType()).IsHeapObject()) {
        derived.Update(base.GetTaggedType() + derived.GetTaggedType() - baseOldObject);
    }
}

void SharedGCEvacuator::UpdateRootVisitor::UpdateObjectSlotRoot(ObjectSlot slot)
{
    JSTaggedValue value(slot.GetTaggedType());
    if (value.IsHeapObject()) {
        Region *region = Region::ObjectAddressToRange(slot.GetTaggedType());
        if (region->InSCollectSet()) {
            ASSERT(region->InSharedHeap());
            ASSERT(!value.IsWeakForHeapObject());
            TaggedObject *object = value.GetHeapObject();
            MarkWord markWord(object, RELAXED_LOAD);
            if (markWord.IsForwardingAddress()) {
                TaggedObject *dst = markWord.ToForwardingAddress();
                slot.Update(dst);
            } else {
                slot.Clear();
            }
        }
    }
}

void SharedGCEvacuator::PostParallelTasks()
{
    if (sHeap_->IsParallelGCEnabled()) {
        LockHolder holder(lock_);
        parallel_ = CalculateParallelThreadNum();
        auto dTid = DaemonThread::GetInstance()->GetThreadId();
        for (int i = 0; i < parallel_; i++) {
            common::Taskpool::GetCurrentTaskpool()->PostTask(std::make_unique<ParallelTask>(dTid, this));
        }
    }
}
}  // namespace panda::ecmascript