/*
 * 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.
 */

#ifndef ECMASCRIPT_MEM_SHARED_HEAP_SHARED_GC_VISITOR_INL_H
#define ECMASCRIPT_MEM_SHARED_HEAP_SHARED_GC_VISITOR_INL_H

#include "ecmascript/mem/shared_heap/shared_gc_visitor.h"

#include "ecmascript/mem/region-inl.h"
#include "ecmascript/mem/work_manager-inl.h"

namespace panda::ecmascript {
SharedGCMarkRootVisitor::SharedGCMarkRootVisitor(SharedGCWorkNodeHolder *sWorkNodeHolder)
    : sWorkNodeHolder_(sWorkNodeHolder)
{
}

void SharedGCMarkRootVisitor::VisitRoot([[maybe_unused]] Root type, ObjectSlot slot)
{
    JSTaggedValue value(slot.GetTaggedType());
    if (value.IsHeapObject()) {
        ASSERT(!value.IsWeak());
        MarkObject(value.GetTaggedObject());
    }
}

void SharedGCMarkRootVisitor::VisitRangeRoot([[maybe_unused]] Root type, ObjectSlot start, ObjectSlot end)
{
    for (ObjectSlot slot = start; slot < end; slot++) {
        JSTaggedValue value(slot.GetTaggedType());
        if (value.IsHeapObject()) {
            ASSERT(!value.IsWeak());
            MarkObject(value.GetTaggedObject());
        }
    }
}

void SharedGCMarkRootVisitor::VisitBaseAndDerivedRoot([[maybe_unused]] Root type, [[maybe_unused]] ObjectSlot base,
                                                      [[maybe_unused]] ObjectSlot derived,
                                                      [[maybe_unused]] uintptr_t baseOldObject)
{
    // It is only used to update the derived value. The mark of SharedGC does not need to update slot
}

void SharedGCMarkRootVisitor::MarkObject(TaggedObject *object)
{
    Region *objectRegion = Region::ObjectAddressToRange(object);
    if (!objectRegion->InSharedSweepableSpace()) {
        return;
    }
    if (objectRegion->AtomicMark(object)) {
        sWorkNodeHolder_->Push(object);
    }
}

SharedGCMarkObjectVisitor::SharedGCMarkObjectVisitor(SharedGCWorkNodeHolder *sWorkNodeHolder)
    : sWorkNodeHolder_(sWorkNodeHolder)
{
}

void SharedGCMarkObjectVisitor::VisitObjectRangeImpl(BaseObject *root, uintptr_t startAddr, uintptr_t endAddr,
                                                     VisitObjectArea area)
{
    Region *rootRegion = Region::ObjectAddressToRange(root);
    ObjectSlot start(startAddr);
    ObjectSlot end(endAddr);
    if (UNLIKELY(area == VisitObjectArea::IN_OBJECT)) {
        JSHClass *hclass = TaggedObject::Cast(root)->SynchronizedGetClass();
        ASSERT(!hclass->IsAllTaggedProp());
        int index = 0;
        LayoutInfo *layout = LayoutInfo::UncheckCast(hclass->GetLayout(THREAD_ARG_PLACEHOLDER).GetTaggedObject());
        ObjectSlot realEnd = start;
        realEnd += layout->GetPropertiesCapacity();
        end = end > realEnd ? realEnd : end;
        for (ObjectSlot slot = start; slot < end; slot++) {
            PropertyAttributes attr = layout->GetAttr(THREAD_ARG_PLACEHOLDER, index++);
            if (attr.IsTaggedRep()) {
                HandleSlot(slot, rootRegion);
            }
        }
        return;
    }
    for (ObjectSlot slot = start; slot < end; slot++) {
        HandleSlot(slot, rootRegion);
    }
}

void SharedGCMarkObjectVisitor::VisitObjectHClassImpl(BaseObject *hclass)
{
    ASSERT(TaggedObject::Cast(hclass)->GetClass()->IsHClass());
    Region *hclassRegion = Region::ObjectAddressToRange(hclass);
    if (hclassRegion->InSharedSweepableSpace()) {
        ASSERT(hclassRegion->InSharedNonMovableSpace());
        MarkAndPush(TaggedObject::Cast(hclass), hclassRegion);
    }
}

void SharedGCMarkObjectVisitor::HandleSlot(ObjectSlot slot, Region *rootRegion)
{
    JSTaggedValue value(slot.GetTaggedType());
    if (!value.IsHeapObject()) {
        return;
    }

    Region *objectRegion = Region::ObjectAddressToRange(value.GetRawHeapObject());
    ASSERT(objectRegion->InSharedHeap());

    if (!objectRegion->InSharedSweepableSpace()) {
        return;
    }

    if (!value.IsWeakForHeapObject()) {
        TaggedObject *object = value.GetTaggedObject();
        MarkAndPush(object, objectRegion);
        if (objectRegion->InSharedOldSpace() && (rootRegion != objectRegion)) {
            rootRegion->AtomicInsertCrossRegionRSet(slot.SlotAddress());
        }
    } else {
        RecordWeakReference(reinterpret_cast<JSTaggedType*>(slot.SlotAddress()));
    }
}

void SharedGCMarkObjectVisitor::MarkAndPush(TaggedObject *object, Region *objectRegion)
{
    if (objectRegion->AtomicMark(object)) {
        sWorkNodeHolder_->Push(object);
    }
}

void SharedGCMarkObjectVisitor::RecordWeakReference(JSTaggedType *weak)
{
    sWorkNodeHolder_->PushWeakReference(weak);
}

template <SharedMarkType markType>
SharedGCMarkLocalToShareRSetVisitor<markType>::SharedGCMarkLocalToShareRSetVisitor(
    SharedGCWorkNodeHolder *sWorkNodeHolder) : sWorkNodeHolder_(sWorkNodeHolder)
{
}

template <SharedMarkType markType>
bool SharedGCMarkLocalToShareRSetVisitor<markType>::operator()(void *mem) const
{
    ObjectSlot slot(ToUintPtr(mem));
    JSTaggedValue value(slot.GetTaggedType());
    if (value.IsInSharedSweepableSpace()) {
        if constexpr (markType == SharedMarkType::CONCURRENT_MARK_INITIAL_MARK) {
            // For now if record weak references from local to share in marking root, the slots
            // may be invalid due to LocalGC, so just mark them as strong-reference.
            MarkObject(value.GetHeapObject());
        } else {
            static_assert(markType == SharedMarkType::NOT_CONCURRENT_MARK);
            if (value.IsWeakForHeapObject()) {
                RecordWeakReference(reinterpret_cast<JSTaggedType *>(mem));
            } else {
                MarkObject(value.GetTaggedObject());
            }
        }
        return true;
    }
    return false;
}

template <SharedMarkType markType>
void SharedGCMarkLocalToShareRSetVisitor<markType>::MarkObject(TaggedObject *object) const
{
    Region *objectRegion = Region::ObjectAddressToRange(object);
    ASSERT(objectRegion->InSharedHeap());
    if (!objectRegion->InSharedReadOnlySpace() && objectRegion->AtomicMark(object)) {
        ASSERT(objectRegion->InSharedSweepableSpace());
        sWorkNodeHolder_->Push(object);
    }
}
template <SharedMarkType markType>
void SharedGCMarkLocalToShareRSetVisitor<markType>::RecordWeakReference(JSTaggedType *weak) const
{
    sWorkNodeHolder_->PushWeakReference(weak);
}
}  // namespace panda::ecmascript
#endif  // ECMASCRIPT_MEM_SHARED_HEAP_SHARED_GC_VISITOR_INL_H