// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_HEAP_REMEMBERED_SET_H_
#define V8_HEAP_REMEMBERED_SET_H_

#include <memory>

#include "src/base/bounds.h"
#include "src/base/memory.h"
#include "src/codegen/reloc-info.h"
#include "src/common/globals.h"
#include "src/heap/base/worklist.h"
#include "src/heap/heap.h"
#include "src/heap/memory-chunk-layout.h"
#include "src/heap/mutable-page-metadata.h"
#include "src/heap/paged-spaces.h"
#include "src/heap/slot-set.h"
#include "src/heap/spaces.h"

namespace v8 {
namespace internal {

class RememberedSetOperations {
 public:
  // Given a page and a slot in that page, this function adds the slot to the
  // remembered set.
  template <AccessMode access_mode>
  static void Insert(SlotSet* slot_set, size_t slot_offset) {
    slot_set->Insert<access_mode == v8::internal::AccessMode::ATOMIC
                         ? v8::internal::SlotSet::AccessMode::ATOMIC
                         : v8::internal::SlotSet::AccessMode::NON_ATOMIC>(
        slot_offset);
  }

  template <AccessMode access_mode = AccessMode::ATOMIC, typename Callback>
  static int Iterate(SlotSet* slot_set, const MutablePageMetadata* chunk,
                     Callback callback, SlotSet::EmptyBucketMode mode) {
    int slots = 0;
    if (slot_set != nullptr) {
      slots += slot_set->Iterate<access_mode>(
          chunk->ChunkAddress(), 0, chunk->BucketsInSlotSet(), callback, mode);
    }
    return slots;
  }

  static void Remove(SlotSet* slot_set, MutablePageMetadata* chunk,
                     Address slot_addr) {
    if (slot_set != nullptr) {
      uintptr_t offset = chunk->Offset(slot_addr);
      slot_set->Remove(offset);
    }
  }

  static void RemoveRange(SlotSet* slot_set, MutablePageMetadata* page,
                          Address start, Address end,
                          SlotSet::EmptyBucketMode mode) {
    if (slot_set != nullptr) {
      MemoryChunk* chunk = page->Chunk();
      uintptr_t start_offset = chunk->Offset(start);
      uintptr_t end_offset = chunk->OffsetMaybeOutOfRange(end);
      DCHECK_LE(start_offset, end_offset);
      slot_set->RemoveRange(static_cast<int>(start_offset),
                            static_cast<int>(end_offset),
                            page->BucketsInSlotSet(), mode);
    }
  }

  static void CheckNoneInRange(SlotSet* slot_set, MemoryChunk* chunk,
                               Address start, Address end) {
    if (slot_set != nullptr) {
      size_t start_bucket = SlotSet::BucketForSlot(chunk->Offset(start));
      // Both 'end' and 'end_bucket' are exclusive limits, so do some index
      // juggling to make sure we get the right bucket even if the end address
      // is at the start of a bucket.
      size_t end_bucket = SlotSet::BucketForSlot(
                              chunk->OffsetMaybeOutOfRange(end) - kTaggedSize) +
                          1;
      slot_set->Iterate(
          chunk->address(), start_bucket, end_bucket,
          [start, end](MaybeObjectSlot slot) {
            CHECK(slot.address() < start || slot.address() >= end);
            return KEEP_SLOT;
          },
          SlotSet::KEEP_EMPTY_BUCKETS);
    }
  }
};

template <RememberedSetType type>
class RememberedSet : public AllStatic {
 public:
  // Given a page and a slot in that page, this function adds the slot to the
  // remembered set.
  template <AccessMode access_mode>
  static void Insert(MutablePageMetadata* page, size_t slot_offset) {
    SlotSet* slot_set = page->slot_set<type, access_mode>();
    if (slot_set == nullptr) {
      slot_set = page->AllocateSlotSet(type);
    }
    RememberedSetOperations::Insert<access_mode>(slot_set, slot_offset);
  }

  // Given a page and a slot set, this function merges the slot set to the set
  // of the page. |other_slot_set| should not be used after calling this method.
  static void MergeAndDelete(MutablePageMetadata* chunk,
                             SlotSet&& other_slot_set) {
    static_assert(type == RememberedSetType::OLD_TO_NEW ||
                  type == RememberedSetType::OLD_TO_NEW_BACKGROUND);
    SlotSet* slot_set = chunk->slot_set<type, AccessMode::NON_ATOMIC>();
    if (slot_set == nullptr) {
      chunk->set_slot_set<type, AccessMode::NON_ATOMIC>(&other_slot_set);
      return;
    }
    slot_set->Merge(&other_slot_set, chunk->BucketsInSlotSet());
    SlotSet::Delete(&other_slot_set);
  }

  // Given a page and a slot set, this function merges the slot set to the set
  // of the page. |other_slot_set| should not be used after calling this method.
  static void MergeAndDeleteTyped(MutablePageMetadata* chunk,
                                  TypedSlotSet&& other_typed_slot_set) {
    static_assert(type == RememberedSetType::OLD_TO_NEW);
    TypedSlotSet* typed_slot_set =
        chunk->typed_slot_set<type, AccessMode::NON_ATOMIC>();
    if (typed_slot_set == nullptr) {
      chunk->set_typed_slot_set<RememberedSetType::OLD_TO_NEW,
                                AccessMode::NON_ATOMIC>(&other_typed_slot_set);
      return;
    }
    typed_slot_set->Merge(&other_typed_slot_set);
    delete &other_typed_slot_set;
  }

  static void DeleteTyped(TypedSlotSet&& other_typed_slot_set) {
    delete &other_typed_slot_set;
  }

  // Given a page and a slot in that page, this function returns true if
  // the remembered set contains the slot.
  static bool Contains(MutablePageMetadata* chunk, Address slot_addr) {
    DCHECK(chunk->Contains(slot_addr));
    SlotSet* slot_set = chunk->slot_set<type>();
    if (slot_set == nullptr) {
      return false;
    }
    uintptr_t offset = chunk->Offset(slot_addr);
    return slot_set->Contains(offset);
  }

  static void CheckNoneInRange(MutablePageMetadata* page, Address start,
                               Address end) {
    SlotSet* slot_set = page->slot_set<type>();
    RememberedSetOperations::CheckNoneInRange(slot_set, page->Chunk(), start,
                                              end);
  }

  // Given a page and a slot in that page, this function removes the slot from
  // the remembered set.
  // If the slot was never added, then the function does nothing.
  static void Remove(MutablePageMetadata* chunk, Address slot_addr) {
    DCHECK(chunk->Contains(slot_addr));
    SlotSet* slot_set = chunk->slot_set<type>();
    RememberedSetOperations::Remove(slot_set, chunk, slot_addr);
  }

  // Given a page and a range of slots in that page, this function removes the
  // slots from the remembered set.
  static void RemoveRange(MutablePageMetadata* chunk, Address start,
                          Address end, SlotSet::EmptyBucketMode mode) {
    SlotSet* slot_set = chunk->slot_set<type>();
    RememberedSetOperations::RemoveRange(slot_set, chunk, start, end, mode);
  }

  // Iterates over all memory chunks that contains non-empty slot sets.
  // The callback should take (MutablePageMetadata* chunk) and return void.
  template <typename Callback>
  static void IterateMemoryChunks(Heap* heap, Callback callback) {
    OldGenerationMemoryChunkIterator it(heap);
    MutablePageMetadata* chunk;
    while ((chunk = it.next()) != nullptr) {
      SlotSet* slot_set = chunk->slot_set<type>();
      TypedSlotSet* typed_slot_set = chunk->typed_slot_set<type>();
      if (slot_set != nullptr || typed_slot_set != nullptr) {
        callback(chunk);
      }
    }
  }

  // Iterates and filters the remembered set in the given memory chunk with
  // the given callback. The callback should take (Address slot) and return
  // SlotCallbackResult.
  //
  // Notice that |mode| can only be of FREE* or PREFREE* if there are no other
  // threads concurrently inserting slots.
  template <AccessMode access_mode = AccessMode::ATOMIC, typename Callback>
  static int Iterate(MutablePageMetadata* chunk, Callback callback,
                     SlotSet::EmptyBucketMode mode) {
    SlotSet* slot_set = chunk->slot_set<type>();
    return Iterate<access_mode>(slot_set, chunk, callback, mode);
  }

  template <AccessMode access_mode = AccessMode::ATOMIC, typename Callback>
  static int Iterate(SlotSet* slot_set, const MutablePageMetadata* chunk,
                     Callback callback, SlotSet::EmptyBucketMode mode) {
    return RememberedSetOperations::Iterate<access_mode>(slot_set, chunk,
                                                         callback, mode);
  }

  template <typename Callback>
  static int IterateAndTrackEmptyBuckets(
      MutablePageMetadata* chunk, Callback callback,
      ::heap::base::Worklist<MutablePageMetadata*, 64>::Local* empty_chunks) {
    SlotSet* slot_set = chunk->slot_set<type>();
    int slots = 0;
    if (slot_set != nullptr) {
      PossiblyEmptyBuckets* possibly_empty_buckets =
          chunk->possibly_empty_buckets();
      slots += slot_set->IterateAndTrackEmptyBuckets(
          chunk->ChunkAddress(), 0, chunk->BucketsInSlotSet(), callback,
          possibly_empty_buckets);
      if (!possibly_empty_buckets->IsEmpty()) empty_chunks->Push(chunk);
    }
    return slots;
  }

  static bool CheckPossiblyEmptyBuckets(MutablePageMetadata* chunk) {
    DCHECK(type == OLD_TO_NEW || type == OLD_TO_NEW_BACKGROUND);
    SlotSet* slot_set = chunk->slot_set<type, AccessMode::NON_ATOMIC>();
    if (slot_set != nullptr &&
        slot_set->CheckPossiblyEmptyBuckets(chunk->BucketsInSlotSet(),
                                            chunk->possibly_empty_buckets())) {
      chunk->ReleaseSlotSet(type);
      return true;
    }

    return false;
  }

  // Given a page and a typed slot in that page, this function adds the slot
  // to the remembered set.
  static void InsertTyped(MutablePageMetadata* memory_chunk, SlotType slot_type,
                          uint32_t offset) {
    TypedSlotSet* slot_set = memory_chunk->typed_slot_set<type>();
    if (slot_set == nullptr) {
      slot_set = memory_chunk->AllocateTypedSlotSet(type);
    }
    slot_set->Insert(slot_type, offset);
  }

  static void MergeTyped(MutablePageMetadata* page,
                         std::unique_ptr<TypedSlots> other) {
    TypedSlotSet* slot_set = page->typed_slot_set<type>();
    if (slot_set == nullptr) {
      slot_set = page->AllocateTypedSlotSet(type);
    }
    slot_set->Merge(other.get());
  }

  // Given a page and a range of typed slots in that page, this function removes
  // the slots from the remembered set.
  static void RemoveRangeTyped(MutablePageMetadata* page, Address start,
                               Address end) {
    TypedSlotSet* slot_set = page->typed_slot_set<type>();
    if (slot_set != nullptr) {
      slot_set->Iterate(
          [=](SlotType slot_type, Address slot_addr) {
            return start <= slot_addr && slot_addr < end ? REMOVE_SLOT
                                                         : KEEP_SLOT;
          },
          TypedSlotSet::FREE_EMPTY_CHUNKS);
    }
  }

  // Iterates and filters typed pointers in the given memory chunk with the
  // given callback. The callback should take (SlotType slot_type, Address addr)
  // and return SlotCallbackResult.
  template <typename Callback>
  static int IterateTyped(MutablePageMetadata* chunk, Callback callback) {
    TypedSlotSet* slot_set = chunk->typed_slot_set<type>();
    if (!slot_set) return 0;
    return IterateTyped(slot_set, callback);
  }

  template <typename Callback>
  static int IterateTyped(TypedSlotSet* slot_set, Callback callback) {
    DCHECK_NOT_NULL(slot_set);
    return slot_set->Iterate(callback, TypedSlotSet::KEEP_EMPTY_CHUNKS);
  }

  // Clear all old to old slots from the remembered set.
  static void ClearAll(Heap* heap) {
    static_assert(type == OLD_TO_OLD || type == TRUSTED_TO_CODE);
    OldGenerationMemoryChunkIterator it(heap);
    MutablePageMetadata* chunk;
    while ((chunk = it.next()) != nullptr) {
      chunk->ReleaseSlotSet(OLD_TO_OLD);
      chunk->ReleaseSlotSet(TRUSTED_TO_CODE);
      chunk->ReleaseTypedSlotSet(OLD_TO_OLD);
    }
  }
};

class UpdateTypedSlotHelper {
 public:
  // Updates a typed slot using an untyped slot callback where |addr| depending
  // on slot type represents either address for respective RelocInfo or address
  // of the uncompressed constant pool entry.
  // The callback accepts FullMaybeObjectSlot and returns SlotCallbackResult.
  template <typename Callback>
  static SlotCallbackResult UpdateTypedSlot(
      WritableJitAllocation& jit_allocation, Heap* heap, SlotType slot_type,
      Address addr, Callback callback);

  // Returns the HeapObject referenced by the given typed slot entry.
  inline static Tagged<HeapObject> GetTargetObject(Heap* heap,
                                                   SlotType slot_type,
                                                   Address addr);

 private:
  // Updates a code entry slot using an untyped slot callback.
  // The callback accepts FullMaybeObjectSlot and returns SlotCallbackResult.
  template <typename Callback>
  static SlotCallbackResult UpdateCodeEntry(Address entry_address,
                                            Callback callback) {
    Tagged<InstructionStream> code =
        InstructionStream::FromEntryAddress(entry_address);
    Tagged<InstructionStream> old_code = code;
    SlotCallbackResult result = callback(FullMaybeObjectSlot(&code));
    DCHECK(!HasWeakHeapObjectTag(code));
    if (code != old_code) {
      base::Memory<Address>(entry_address) = code->instruction_start();
    }
    return result;
  }

  // Updates a code target slot using an untyped slot callback.
  // The callback accepts FullMaybeObjectSlot and returns SlotCallbackResult.
  template <typename Callback>
  static SlotCallbackResult UpdateCodeTarget(WritableRelocInfo* rinfo,
                                             Callback callback) {
    DCHECK(RelocInfo::IsCodeTargetMode(rinfo->rmode()));
    Tagged<InstructionStream> old_target =
        InstructionStream::FromTargetAddress(rinfo->target_address());
    Tagged<InstructionStream> new_target = old_target;
    SlotCallbackResult result = callback(FullMaybeObjectSlot(&new_target));
    DCHECK(!HasWeakHeapObjectTag(new_target));
    if (new_target != old_target) {
      rinfo->set_target_address(
          Cast<InstructionStream>(new_target)->instruction_start());
    }
    return result;
  }

  // Updates an embedded pointer slot using an untyped slot callback.
  // The callback accepts FullMaybeObjectSlot and returns SlotCallbackResult.
  template <typename Callback>
  static SlotCallbackResult UpdateEmbeddedPointer(Heap* heap,
                                                  WritableRelocInfo* rinfo,
                                                  Callback callback) {
    DCHECK(RelocInfo::IsEmbeddedObjectMode(rinfo->rmode()));
    Tagged<HeapObject> old_target = rinfo->target_object(heap->isolate());
    Tagged<HeapObject> new_target = old_target;
    SlotCallbackResult result = callback(FullMaybeObjectSlot(&new_target));
    DCHECK(!HasWeakHeapObjectTag(new_target));
    if (new_target != old_target) {
      rinfo->set_target_object(Cast<HeapObject>(new_target));
    }
    return result;
  }
};

}  // namespace internal
}  // namespace v8

#endif  // V8_HEAP_REMEMBERED_SET_H_
