%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/handles/
Upload File :
Create Path :
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/v8/src/handles/traced-handles.cc

// Copyright 2022 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.

#include "src/handles/traced-handles.h"

#include <iterator>
#include <limits>

#include "include/v8-internal.h"
#include "include/v8-traced-handle.h"
#include "src/base/logging.h"
#include "src/base/platform/memory.h"
#include "src/base/sanitizer/asan.h"
#include "src/common/globals.h"
#include "src/handles/handles.h"
#include "src/heap/heap-write-barrier-inl.h"
#include "src/objects/objects.h"
#include "src/objects/visitors.h"

namespace v8::internal {

class TracedHandlesImpl;
namespace {

class TracedNodeBlock;

class TracedNode final {
 public:
#ifdef V8_HOST_ARCH_64_BIT
  using IndexType = uint16_t;
#else   // !V8_HOST_ARCH_64_BIT
  using IndexType = uint8_t;
#endif  // !V8_HOST_ARCH_64_BIT

  static TracedNode* FromLocation(Address* location) {
    return reinterpret_cast<TracedNode*>(location);
  }

  static const TracedNode* FromLocation(const Address* location) {
    return reinterpret_cast<const TracedNode*>(location);
  }

  TracedNode(IndexType, IndexType);

  IndexType index() const { return index_; }

  bool is_root() const { return IsRoot::decode(flags_); }
  void set_root(bool v) { flags_ = IsRoot::update(flags_, v); }

  template <AccessMode access_mode = AccessMode::NON_ATOMIC>
  bool is_in_use() const {
    if constexpr (access_mode == AccessMode::NON_ATOMIC) {
      return IsInUse::decode(flags_);
    }
    const auto flags =
        reinterpret_cast<const std::atomic<uint8_t>&>(flags_).load(
            std::memory_order_relaxed);
    return IsInUse::decode(flags);
  }
  void set_is_in_use(bool v) { flags_ = IsInUse::update(flags_, v); }

  bool is_in_young_list() const { return IsInYoungList::decode(flags_); }
  void set_is_in_young_list(bool v) {
    flags_ = IsInYoungList::update(flags_, v);
  }

  IndexType next_free() const { return next_free_index_; }
  void set_next_free(IndexType next_free_index) {
    next_free_index_ = next_free_index;
  }
  void set_class_id(uint16_t class_id) { class_id_ = class_id; }

  template <AccessMode access_mode = AccessMode::NON_ATOMIC>
  void set_markbit() {
    if constexpr (access_mode == AccessMode::NON_ATOMIC) {
      flags_ = Markbit::update(flags_, true);
      return;
    }
    std::atomic<uint8_t>& atomic_flags =
        reinterpret_cast<std::atomic<uint8_t>&>(flags_);
    const uint8_t new_value =
        Markbit::update(atomic_flags.load(std::memory_order_relaxed), true);
    atomic_flags.fetch_or(new_value, std::memory_order_relaxed);
  }

  template <AccessMode access_mode = AccessMode::NON_ATOMIC>
  bool markbit() const {
    if constexpr (access_mode == AccessMode::NON_ATOMIC) {
      return Markbit::decode(flags_);
    }
    const auto flags =
        reinterpret_cast<const std::atomic<uint8_t>&>(flags_).load(
            std::memory_order_relaxed);
    return Markbit::decode(flags);
  }

  void clear_markbit() { flags_ = Markbit::update(flags_, false); }

  bool has_old_host() const { return HasOldHost::decode(flags_); }
  void set_has_old_host(bool v) { flags_ = HasOldHost::update(flags_, v); }

  template <AccessMode access_mode = AccessMode::NON_ATOMIC>
  void set_raw_object(Address value) {
    if constexpr (access_mode == AccessMode::NON_ATOMIC) {
      object_ = value;
    } else {
      reinterpret_cast<std::atomic<Address>*>(&object_)->store(
          value, std::memory_order_relaxed);
    }
  }
  Address raw_object() const { return object_; }
  Tagged<Object> object() const { return Tagged<Object>(object_); }
  Handle<Object> handle() { return Handle<Object>(&object_); }
  FullObjectSlot location() { return FullObjectSlot(&object_); }

  Handle<Object> Publish(Tagged<Object> object, bool needs_young_bit_update,
                         bool needs_black_allocation, bool has_old_host);
  void Release();

 private:
  using IsInUse = base::BitField8<bool, 0, 1>;
  using IsInYoungList = IsInUse::Next<bool, 1>;
  using IsRoot = IsInYoungList::Next<bool, 1>;
  // The markbit is the exception as it can be set from the main and marker
  // threads at the same time.
  using Markbit = IsRoot::Next<bool, 1>;
  using HasOldHost = Markbit::Next<bool, 1>;

  Address object_ = kNullAddress;
  union {
    // When a node is in use, the user can specify a class id.
    uint16_t class_id_;
    // When a node is not in use, this index is used to build the free list.
    IndexType next_free_index_;
  };
  IndexType index_;
  uint8_t flags_ = 0;
};

TracedNode::TracedNode(IndexType index, IndexType next_free_index)
    : next_free_index_(next_free_index), index_(index) {
  static_assert(offsetof(TracedNode, class_id_) ==
                Internals::kTracedNodeClassIdOffset);
  // TracedNode size should stay within 2 words.
  static_assert(sizeof(TracedNode) <= (2 * kSystemPointerSize));
  DCHECK(!is_in_use());
  DCHECK(!is_in_young_list());
  DCHECK(!is_root());
  DCHECK(!markbit());
  DCHECK(!has_old_host());
}

// Publishes all internal state to be consumed by other threads.
Handle<Object> TracedNode::Publish(Tagged<Object> object,
                                   bool needs_young_bit_update,
                                   bool needs_black_allocation,
                                   bool has_old_host) {
  DCHECK(!is_in_use());
  DCHECK(!is_root());
  DCHECK(!markbit());
  set_class_id(0);
  if (needs_young_bit_update) {
    set_is_in_young_list(true);
  }
  if (needs_black_allocation) {
    set_markbit();
  }
  if (has_old_host) {
    DCHECK(is_in_young_list());
    set_has_old_host(true);
  }
  set_root(true);
  set_is_in_use(true);
  reinterpret_cast<std::atomic<Address>*>(&object_)->store(
      object.ptr(), std::memory_order_release);
  return Handle<Object>(&object_);
}

void TracedNode::Release() {
  DCHECK(is_in_use());
  // Only preserve the in-young-list bit which is used to avoid duplicates in
  // TracedHandlesImpl::young_nodes_;
  flags_ &= IsInYoungList::encode(true);
  DCHECK(!is_in_use());
  DCHECK(!is_root());
  DCHECK(!markbit());
  DCHECK(!has_old_host());
  set_raw_object(kGlobalHandleZapValue);
}

template <typename T, typename NodeAccessor>
class DoublyLinkedList final {
  template <typename U>
  class IteratorImpl final
      : public base::iterator<std::forward_iterator_tag, U> {
   public:
    explicit IteratorImpl(U* object) : object_(object) {}
    IteratorImpl(const IteratorImpl& other) V8_NOEXCEPT
        : object_(other.object_) {}
    U* operator*() { return object_; }
    bool operator==(const IteratorImpl& rhs) const {
      return rhs.object_ == object_;
    }
    bool operator!=(const IteratorImpl& rhs) const { return !(*this == rhs); }
    inline IteratorImpl& operator++() {
      object_ = ListNodeFor(object_)->next;
      return *this;
    }
    inline IteratorImpl operator++(int) {
      IteratorImpl tmp(*this);
      operator++();
      return tmp;
    }

   private:
    U* object_;
  };

 public:
  using Iterator = IteratorImpl<T>;
  using ConstIterator = IteratorImpl<const T>;

  struct ListNode {
    T* prev = nullptr;
    T* next = nullptr;
  };

  T* Front() { return front_; }

  void PushFront(T* object) {
    DCHECK(!Contains(object));
    ListNodeFor(object)->next = front_;
    if (front_) {
      ListNodeFor(front_)->prev = object;
    }
    front_ = object;
    size_++;
  }

  void PopFront() {
    DCHECK(!Empty());
    if (ListNodeFor(front_)->next) {
      ListNodeFor(ListNodeFor(front_)->next)->prev = nullptr;
    }
    front_ = ListNodeFor(front_)->next;
    size_--;
  }

  void Remove(T* object) {
    DCHECK(Contains(object));
    auto& next_object = ListNodeFor(object)->next;
    auto& prev_object = ListNodeFor(object)->prev;
    if (front_ == object) {
      front_ = next_object;
    }
    if (next_object) {
      ListNodeFor(next_object)->prev = prev_object;
    }
    if (prev_object) {
      ListNodeFor(prev_object)->next = next_object;
    }
    next_object = nullptr;
    prev_object = nullptr;
    size_--;
  }

  bool Contains(T* object) const {
    if (front_ == object) return true;
    auto* list_node = ListNodeFor(object);
    return list_node->prev || list_node->next;
  }

  size_t Size() const { return size_; }
  bool Empty() const { return size_ == 0; }

  Iterator begin() { return Iterator(front_); }
  Iterator end() { return Iterator(nullptr); }
  ConstIterator begin() const { return ConstIterator(front_); }
  ConstIterator end() const { return ConstIterator(nullptr); }

 private:
  static ListNode* ListNodeFor(T* object) {
    return NodeAccessor::GetListNode(object);
  }
  static const ListNode* ListNodeFor(const T* object) {
    return NodeAccessor::GetListNode(const_cast<T*>(object));
  }

  T* front_ = nullptr;
  size_t size_ = 0;
};

class TracedNodeBlock final {
  struct OverallListNode {
    static auto* GetListNode(TracedNodeBlock* block) {
      return &block->overall_list_node_;
    }
  };

  struct UsableListNode {
    static auto* GetListNode(TracedNodeBlock* block) {
      return &block->usable_list_node_;
    }
  };

  class NodeIteratorImpl final
      : public base::iterator<std::forward_iterator_tag, TracedNode> {
   public:
    explicit NodeIteratorImpl(TracedNodeBlock* block) : block_(block) {}
    NodeIteratorImpl(TracedNodeBlock* block,
                     TracedNode::IndexType current_index)
        : block_(block), current_index_(current_index) {}
    NodeIteratorImpl(const NodeIteratorImpl& other) V8_NOEXCEPT
        : block_(other.block_),
          current_index_(other.current_index_) {}

    TracedNode* operator*() { return block_->at(current_index_); }
    bool operator==(const NodeIteratorImpl& rhs) const {
      return rhs.block_ == block_ && rhs.current_index_ == current_index_;
    }
    bool operator!=(const NodeIteratorImpl& rhs) const {
      return !(*this == rhs);
    }
    inline NodeIteratorImpl& operator++() {
      current_index_++;
      return *this;
    }
    inline NodeIteratorImpl operator++(int) {
      NodeIteratorImpl tmp(*this);
      operator++();
      return tmp;
    }

   private:
    TracedNodeBlock* block_;
    TracedNode::IndexType current_index_ = 0;
  };

 public:
  using OverallList = DoublyLinkedList<TracedNodeBlock, OverallListNode>;
  using UsableList = DoublyLinkedList<TracedNodeBlock, UsableListNode>;
  using Iterator = NodeIteratorImpl;

#if defined(V8_USE_ADDRESS_SANITIZER)
  static constexpr size_t kMinCapacity = 1;
  static constexpr size_t kMaxCapacity = 1;
#else  // !defined(V8_USE_ADDRESS_SANITIZER)
#ifdef V8_HOST_ARCH_64_BIT
  static constexpr size_t kMinCapacity = 256;
#else   // !V8_HOST_ARCH_64_BIT
  static constexpr size_t kMinCapacity = 128;
#endif  // !V8_HOST_ARCH_64_BIT
  static constexpr size_t kMaxCapacity =
      std::numeric_limits<TracedNode::IndexType>::max() - 1;
#endif  // !defined(V8_USE_ADDRESS_SANITIZER)

  static constexpr TracedNode::IndexType kInvalidFreeListNodeIndex = -1;

  static_assert(kMinCapacity <= kMaxCapacity);
  static_assert(kInvalidFreeListNodeIndex > kMaxCapacity);

  static TracedNodeBlock* Create(TracedHandlesImpl&, OverallList&, UsableList&);
  static void Delete(TracedNodeBlock*);

  static TracedNodeBlock& From(TracedNode& node);
  static const TracedNodeBlock& From(const TracedNode& node);

  TracedNode* AllocateNode();
  void FreeNode(TracedNode*);

  TracedNode* at(TracedNode::IndexType index) {
    return &(reinterpret_cast<TracedNode*>(this + 1)[index]);
  }
  const TracedNode* at(TracedNode::IndexType index) const {
    return const_cast<TracedNodeBlock*>(this)->at(index);
  }

  const void* nodes_begin_address() const { return at(0); }
  const void* nodes_end_address() const { return at(capacity_); }

  TracedHandlesImpl& traced_handles() const { return traced_handles_; }

  Iterator begin() { return Iterator(this); }
  Iterator end() { return Iterator(this, capacity_); }

  bool IsFull() const { return used_ == capacity_; }
  bool IsEmpty() const { return used_ == 0; }

  size_t size_bytes() const {
    return sizeof(*this) + capacity_ * sizeof(TracedNode);
  }

 private:
  TracedNodeBlock(TracedHandlesImpl&, OverallList&, UsableList&,
                  TracedNode::IndexType);

  OverallList::ListNode overall_list_node_;
  UsableList::ListNode usable_list_node_;
  TracedHandlesImpl& traced_handles_;
  TracedNode::IndexType used_ = 0;
  const TracedNode::IndexType capacity_ = 0;
  TracedNode::IndexType first_free_node_ = 0;
};

// static
TracedNodeBlock* TracedNodeBlock::Create(TracedHandlesImpl& traced_handles,
                                         OverallList& overall_list,
                                         UsableList& usable_list) {
  static_assert(alignof(TracedNodeBlock) >= alignof(TracedNode));
  static_assert(sizeof(TracedNodeBlock) % alignof(TracedNode) == 0,
                "TracedNodeBlock size is used to auto-align node FAM storage.");
  const size_t min_wanted_size =
      sizeof(TracedNodeBlock) +
      sizeof(TracedNode) * TracedNodeBlock::kMinCapacity;
  const auto raw_result = v8::base::AllocateAtLeast<char>(min_wanted_size);
  const size_t capacity = std::min(
      (raw_result.count - sizeof(TracedNodeBlock)) / sizeof(TracedNode),
      kMaxCapacity);
  CHECK_LT(capacity, std::numeric_limits<TracedNode::IndexType>::max());
  const auto result = std::make_pair(raw_result.ptr, capacity);
  return new (result.first)
      TracedNodeBlock(traced_handles, overall_list, usable_list,
                      static_cast<TracedNode::IndexType>(result.second));
}

// static
void TracedNodeBlock::Delete(TracedNodeBlock* block) { free(block); }

TracedNodeBlock::TracedNodeBlock(TracedHandlesImpl& traced_handles,
                                 OverallList& overall_list,
                                 UsableList& usable_list,
                                 TracedNode::IndexType capacity)
    : traced_handles_(traced_handles), capacity_(capacity) {
  for (TracedNode::IndexType i = 0; i < (capacity_ - 1); i++) {
    new (at(i)) TracedNode(i, i + 1);
  }
  new (at(capacity_ - 1)) TracedNode(capacity_ - 1, kInvalidFreeListNodeIndex);
  overall_list.PushFront(this);
  usable_list.PushFront(this);
}

// static
TracedNodeBlock& TracedNodeBlock::From(TracedNode& node) {
  TracedNode* first_node = &node - node.index();
  return *reinterpret_cast<TracedNodeBlock*>(
      reinterpret_cast<uintptr_t>(first_node) - sizeof(TracedNodeBlock));
}

// static
const TracedNodeBlock& TracedNodeBlock::From(const TracedNode& node) {
  return From(const_cast<TracedNode&>(node));
}

TracedNode* TracedNodeBlock::AllocateNode() {
  if (used_ == capacity_) {
    DCHECK_EQ(first_free_node_, kInvalidFreeListNodeIndex);
    return nullptr;
  }

  DCHECK_NE(first_free_node_, kInvalidFreeListNodeIndex);
  auto* node = at(first_free_node_);
  first_free_node_ = node->next_free();
  used_++;
  DCHECK(!node->is_in_use());
  return node;
}

void TracedNodeBlock::FreeNode(TracedNode* node) {
  DCHECK(node->is_in_use());
  node->Release();
  DCHECK(!node->is_in_use());
  node->set_next_free(first_free_node_);
  first_free_node_ = node->index();
  used_--;
}

CppHeap* GetCppHeapIfUnifiedYoungGC(Isolate* isolate) {
  // TODO(v8:13475) Consider removing this check when unified-young-gen becomes
  // default.
  if (!v8_flags.cppgc_young_generation) return nullptr;
  auto* cpp_heap = CppHeap::From(isolate->heap()->cpp_heap());
  if (cpp_heap && cpp_heap->generational_gc_supported()) return cpp_heap;
  return nullptr;
}

bool IsCppGCHostOld(CppHeap& cpp_heap, Address host) {
  DCHECK(host);
  DCHECK(cpp_heap.generational_gc_supported());
  auto* host_ptr = reinterpret_cast<void*>(host);
  auto* page = cppgc::internal::BasePage::FromInnerAddress(&cpp_heap, host_ptr);
  // TracedReference may be created on stack, in which case assume it's young
  // and doesn't need to be remembered, since it'll anyway be scanned.
  if (!page) return false;
  return !page->ObjectHeaderFromInnerAddress(host_ptr).IsYoung();
}

void SetSlotThreadSafe(Address** slot, Address* val) {
  reinterpret_cast<std::atomic<Address*>*>(slot)->store(
      val, std::memory_order_relaxed);
}

}  // namespace

class TracedHandlesImpl final {
 public:
  explicit TracedHandlesImpl(Isolate*);
  ~TracedHandlesImpl();

  Handle<Object> Create(Address value, Address* slot,
                        GlobalHandleStoreMode store_mode);
  void Destroy(TracedNodeBlock& node_block, TracedNode& node);
  void Copy(const TracedNode& from_node, Address** to);
  void Move(TracedNode& from_node, Address** from, Address** to);

  void SetIsMarking(bool);
  void SetIsSweepingOnMutatorThread(bool);

  const TracedHandles::NodeBounds GetNodeBounds() const;

  void UpdateListOfYoungNodes();
  void ClearListOfYoungNodes();

  void DeleteEmptyBlocks();

  void ResetDeadNodes(WeakSlotCallbackWithHeap should_reset_handle);
  void ResetYoungDeadNodes(WeakSlotCallbackWithHeap should_reset_handle);

  void ComputeWeaknessForYoungObjects(WeakSlotCallback is_unmodified);
  void ProcessYoungObjects(RootVisitor* visitor,
                           WeakSlotCallbackWithHeap should_reset_handle);

  void Iterate(RootVisitor* visitor);
  void IterateYoung(RootVisitor* visitor);
  void IterateYoungRoots(RootVisitor* visitor);
  void IterateAndMarkYoungRootsWithOldHosts(RootVisitor* visitor);
  void IterateYoungRootsWithOldHostsForTesting(RootVisitor* visitor);

  size_t used_node_count() const { return used_nodes_; }
  size_t used_size_bytes() const { return sizeof(TracedNode) * used_nodes_; }
  size_t total_size_bytes() const { return block_size_bytes_; }

  bool HasYoung() const { return !young_nodes_.empty(); }

 private:
  TracedNode* AllocateNode();
  void FreeNode(TracedNode*);

  bool NeedsToBeRemembered(Tagged<Object> value, TracedNode* node,
                           Address* slot,
                           GlobalHandleStoreMode store_mode) const;

  TracedNodeBlock::OverallList blocks_;
  TracedNodeBlock::UsableList usable_blocks_;
  // List of young nodes. May refer to nodes in `blocks_`, `usable_blocks_`, and
  // `empty_block_candidates_`.
  std::vector<TracedNode*> young_nodes_;
  // Empty blocks that are still referred to from `young_nodes_`.
  std::vector<TracedNodeBlock*> empty_block_candidates_;
  // Fully empty blocks that are neither referenced from any stale references in
  // destructors nor from young nodes.
  std::vector<TracedNodeBlock*> empty_blocks_;
  Isolate* isolate_;
  bool is_marking_ = false;
  bool is_sweeping_on_mutator_thread_ = false;
  size_t used_nodes_ = 0;
  size_t block_size_bytes_ = 0;
};

TracedNode* TracedHandlesImpl::AllocateNode() {
  auto* block = usable_blocks_.Front();
  if (!block) {
    if (empty_blocks_.empty() && empty_block_candidates_.empty()) {
      block = TracedNodeBlock::Create(*this, blocks_, usable_blocks_);
      block_size_bytes_ += block->size_bytes();
    } else {
      // Pick a block from candidates first as such blocks may anyways still be
      // referred to from young nodes and thus are not eligible for freeing.
      auto& block_source = empty_block_candidates_.empty()
                               ? empty_blocks_
                               : empty_block_candidates_;
      block = block_source.back();
      block_source.pop_back();
      DCHECK(block->IsEmpty());
      usable_blocks_.PushFront(block);
      blocks_.PushFront(block);
    }
    DCHECK_EQ(block, usable_blocks_.Front());
  }
  auto* node = block->AllocateNode();
  if (node) {
    used_nodes_++;
    return node;
  }

  usable_blocks_.Remove(block);
  return AllocateNode();
}

void TracedHandlesImpl::FreeNode(TracedNode* node) {
  auto& block = TracedNodeBlock::From(*node);
  if (block.IsFull() && !usable_blocks_.Contains(&block)) {
    usable_blocks_.PushFront(&block);
  }
  block.FreeNode(node);
  if (block.IsEmpty()) {
    usable_blocks_.Remove(&block);
    blocks_.Remove(&block);
    empty_block_candidates_.push_back(&block);
  }
  used_nodes_--;
}

TracedHandlesImpl::TracedHandlesImpl(Isolate* isolate) : isolate_(isolate) {}

TracedHandlesImpl::~TracedHandlesImpl() {
  size_t block_size_bytes = 0;
  while (!blocks_.Empty()) {
    auto* block = blocks_.Front();
    blocks_.PopFront();
    block_size_bytes += block->size_bytes();
    TracedNodeBlock::Delete(block);
  }
  for (auto* block : empty_block_candidates_) {
    block_size_bytes += block->size_bytes();
    TracedNodeBlock::Delete(block);
  }
  for (auto* block : empty_blocks_) {
    block_size_bytes += block->size_bytes();
    TracedNodeBlock::Delete(block);
  }
  USE(block_size_bytes);
  DCHECK_EQ(block_size_bytes, block_size_bytes_);
}

namespace {
bool NeedsTrackingInYoungNodes(Tagged<Object> object, TracedNode* node) {
  return ObjectInYoungGeneration(object) && !node->is_in_young_list();
}
}  // namespace

bool TracedHandlesImpl::NeedsToBeRemembered(
    Tagged<Object> object, TracedNode* node, Address* slot,
    GlobalHandleStoreMode store_mode) const {
  DCHECK(!node->has_old_host());
  if (store_mode == GlobalHandleStoreMode::kInitializingStore) {
    // Don't record initializing stores.
    return false;
  }
  if (is_marking_) {
    // If marking is in progress, the marking barrier will be issued later.
    return false;
  }
  auto* cpp_heap = GetCppHeapIfUnifiedYoungGC(isolate_);
  if (!cpp_heap) return false;

  if (!ObjectInYoungGeneration(object)) return false;
  return IsCppGCHostOld(*cpp_heap, reinterpret_cast<Address>(slot));
}

Handle<Object> TracedHandlesImpl::Create(Address value, Address* slot,
                                         GlobalHandleStoreMode store_mode) {
  Tagged<Object> object(value);
  auto* node = AllocateNode();
  bool needs_young_bit_update = false;
  if (NeedsTrackingInYoungNodes(object, node)) {
    needs_young_bit_update = true;
    young_nodes_.push_back(node);
  }

  const bool has_old_host = NeedsToBeRemembered(object, node, slot, store_mode);
  bool needs_black_allocation = false;
  if (is_marking_ && store_mode != GlobalHandleStoreMode::kInitializingStore) {
    needs_black_allocation = true;
    WriteBarrier::MarkingFromGlobalHandle(object);
  }
  return node->Publish(object, needs_young_bit_update, needs_black_allocation,
                       has_old_host);
}

void TracedHandlesImpl::Destroy(TracedNodeBlock& node_block, TracedNode& node) {
  DCHECK_IMPLIES(is_marking_, !is_sweeping_on_mutator_thread_);
  DCHECK_IMPLIES(is_sweeping_on_mutator_thread_, !is_marking_);

  // If sweeping on the mutator thread is running then the handle destruction
  // may be a result of a Reset() call from a destructor. The node will be
  // reclaimed on the next cycle.
  //
  // This allows v8::TracedReference::Reset() calls from destructors on
  // objects that may be used from stack and heap.
  if (is_sweeping_on_mutator_thread_) {
    return;
  }

  if (is_marking_) {
    // Incremental/concurrent marking is running. This also covers the scavenge
    // case which prohibits eagerly reclaiming nodes when marking is on during a
    // scavenge.
    //
    // On-heap traced nodes are released in the atomic pause in
    // `IterateWeakRootsForPhantomHandles()` when they are discovered as not
    // marked. Eagerly clear out the object here to avoid needlessly marking it
    // from this point on. The node will be reclaimed on the next cycle.
    node.set_raw_object<AccessMode::ATOMIC>(kNullAddress);
    return;
  }

  // In case marking and sweeping are off, the handle may be freed immediately.
  // Note that this includes also the case when invoking the first pass
  // callbacks during the atomic pause which requires releasing a node fully.
  FreeNode(&node);
}

void TracedHandlesImpl::Copy(const TracedNode& from_node, Address** to) {
  DCHECK_NE(kGlobalHandleZapValue, from_node.raw_object());
  Handle<Object> o =
      Create(from_node.raw_object(), reinterpret_cast<Address*>(to),
             GlobalHandleStoreMode::kAssigningStore);
  SetSlotThreadSafe(to, o.location());
#ifdef VERIFY_HEAP
  if (v8_flags.verify_heap) {
    Object::ObjectVerify(Tagged<Object>(**to), isolate_);
  }
#endif  // VERIFY_HEAP
}

void TracedHandlesImpl::Move(TracedNode& from_node, Address** from,
                             Address** to) {
  DCHECK(from_node.is_in_use());

  // Deal with old "to".
  auto* to_node = TracedNode::FromLocation(*to);
  DCHECK_IMPLIES(*to, to_node->is_in_use());
  DCHECK_IMPLIES(*to, kGlobalHandleZapValue != to_node->raw_object());
  DCHECK_NE(kGlobalHandleZapValue, from_node.raw_object());
  if (*to) {
    auto& to_node_block = TracedNodeBlock::From(*to_node);
    Destroy(to_node_block, *to_node);
  }

  // Set "to" to "from".
  SetSlotThreadSafe(to, *from);
  to_node = &from_node;

  // Deal with new "to"
  DCHECK_NOT_NULL(*to);
  DCHECK_EQ(*from, *to);
  if (is_marking_) {
    // Write barrier needs to cover node as well as object.
    to_node->set_markbit<AccessMode::ATOMIC>();
    WriteBarrier::MarkingFromGlobalHandle(to_node->object());
  } else if (auto* cpp_heap = GetCppHeapIfUnifiedYoungGC(isolate_)) {
    const bool object_is_young_and_not_yet_recorded =
        !from_node.has_old_host() &&
        ObjectInYoungGeneration(from_node.object());
    if (object_is_young_and_not_yet_recorded &&
        IsCppGCHostOld(*cpp_heap, reinterpret_cast<Address>(to))) {
      DCHECK(from_node.is_in_young_list());
      from_node.set_has_old_host(true);
    }
  }
  SetSlotThreadSafe(from, nullptr);
}

void TracedHandlesImpl::SetIsMarking(bool value) {
  DCHECK_EQ(is_marking_, !value);
  is_marking_ = value;
}

void TracedHandlesImpl::SetIsSweepingOnMutatorThread(bool value) {
  DCHECK_EQ(is_sweeping_on_mutator_thread_, !value);
  is_sweeping_on_mutator_thread_ = value;
}

const TracedHandles::NodeBounds TracedHandlesImpl::GetNodeBounds() const {
  TracedHandles::NodeBounds block_bounds;
  block_bounds.reserve(blocks_.Size());
  for (const auto* block : blocks_) {
    block_bounds.push_back(
        {block->nodes_begin_address(), block->nodes_end_address()});
  }
  std::sort(block_bounds.begin(), block_bounds.end(),
            [](const auto& pair1, const auto& pair2) {
              return pair1.first < pair2.first;
            });
  return block_bounds;
}

void TracedHandlesImpl::UpdateListOfYoungNodes() {
  size_t last = 0;
  const bool needs_to_mark_as_old =
      static_cast<bool>(GetCppHeapIfUnifiedYoungGC(isolate_));
  for (auto* node : young_nodes_) {
    DCHECK(node->is_in_young_list());
    if (node->is_in_use() && ObjectInYoungGeneration(node->object())) {
      young_nodes_[last++] = node;
      // The node was discovered through a cppgc object, which will be
      // immediately promoted. Remember the object.
      if (needs_to_mark_as_old) node->set_has_old_host(true);
    } else {
      node->set_is_in_young_list(false);
      node->set_has_old_host(false);
    }
  }
  DCHECK_LE(last, young_nodes_.size());
  young_nodes_.resize(last);
  young_nodes_.shrink_to_fit();
  empty_blocks_.insert(empty_blocks_.end(), empty_block_candidates_.begin(),
                       empty_block_candidates_.end());
  empty_block_candidates_.clear();
  empty_block_candidates_.shrink_to_fit();
}

void TracedHandlesImpl::ClearListOfYoungNodes() {
  for (auto* node : young_nodes_) {
    DCHECK(node->is_in_young_list());
    // Nodes in use and not in use can have this bit set to false.
    node->set_is_in_young_list(false);
    node->set_has_old_host(false);
  }
  young_nodes_.clear();
  young_nodes_.shrink_to_fit();
  empty_blocks_.insert(empty_blocks_.end(), empty_block_candidates_.begin(),
                       empty_block_candidates_.end());
  empty_block_candidates_.clear();
  empty_block_candidates_.shrink_to_fit();
}

void TracedHandlesImpl::DeleteEmptyBlocks() {
  // Keep one node block around for fast allocation/deallocation patterns.
  if (empty_blocks_.size() <= 1) return;

  for (size_t i = 1; i < empty_blocks_.size(); i++) {
    auto* block = empty_blocks_[i];
    DCHECK(block->IsEmpty());
    DCHECK_GE(block_size_bytes_, block->size_bytes());
    block_size_bytes_ -= block->size_bytes();
    TracedNodeBlock::Delete(block);
  }
  empty_blocks_.resize(1);
  empty_blocks_.shrink_to_fit();
}

void TracedHandlesImpl::ResetDeadNodes(
    WeakSlotCallbackWithHeap should_reset_handle) {
  // Manual iteration as the block may be deleted in `FreeNode()`.
  for (auto it = blocks_.begin(); it != blocks_.end();) {
    auto* block = *(it++);
    for (auto* node : *block) {
      if (!node->is_in_use()) continue;

      // Detect unreachable nodes first.
      if (!node->markbit()) {
        FreeNode(node);
        continue;
      }

      // Node was reachable. Clear the markbit for the next GC.
      node->clear_markbit();
      // TODO(v8:13141): Turn into a DCHECK after some time.
      CHECK(!should_reset_handle(isolate_->heap(), node->location()));
    }
  }
}

void TracedHandlesImpl::ResetYoungDeadNodes(
    WeakSlotCallbackWithHeap should_reset_handle) {
  for (auto* node : young_nodes_) {
    DCHECK(node->is_in_young_list());
    DCHECK_IMPLIES(node->has_old_host(), node->markbit());

    if (!node->is_in_use()) continue;

    if (!node->markbit()) {
      FreeNode(node);
      continue;
    }

    // Node was reachable. Clear the markbit for the next GC.
    node->clear_markbit();
    // TODO(v8:13141): Turn into a DCHECK after some time.
    CHECK(!should_reset_handle(isolate_->heap(), node->location()));
  }
}

void TracedHandlesImpl::ComputeWeaknessForYoungObjects(
    WeakSlotCallback is_unmodified) {
  if (!v8_flags.reclaim_unmodified_wrappers) return;

  // Treat all objects as roots during incremental marking to avoid corrupting
  // marking worklists.
  if (!v8_flags.minor_ms && is_marking_) return;

  auto* const handler = isolate_->heap()->GetEmbedderRootsHandler();
  if (!handler) return;

  for (TracedNode* node : young_nodes_) {
    if (node->is_in_use()) {
      DCHECK(node->is_root());
      if (is_unmodified(node->location())) {
        v8::Value* value = ToApi<v8::Value>(node->handle());
        bool r = handler->IsRoot(
            *reinterpret_cast<v8::TracedReference<v8::Value>*>(&value));
        node->set_root(r);
      }
    }
  }
}

void TracedHandlesImpl::ProcessYoungObjects(
    RootVisitor* visitor, WeakSlotCallbackWithHeap should_reset_handle) {
  if (!v8_flags.reclaim_unmodified_wrappers) return;

  auto* const handler = isolate_->heap()->GetEmbedderRootsHandler();
  if (!handler) return;

  // If CppGC is attached, since the embeeder may trigger allocations in
  // ResetRoot().
  if (auto* cpp_heap = CppHeap::From(isolate_->heap()->cpp_heap())) {
    cpp_heap->EnterNoGCScope();
  }

  for (TracedNode* node : young_nodes_) {
    if (!node->is_in_use()) continue;

    bool should_reset = should_reset_handle(isolate_->heap(), node->location());
    CHECK_IMPLIES(node->is_root(), !should_reset);
    if (should_reset) {
      CHECK(!is_marking_);
      v8::Value* value = ToApi<v8::Value>(node->handle());
      handler->ResetRoot(
          *reinterpret_cast<v8::TracedReference<v8::Value>*>(&value));
      // We cannot check whether a node is in use here as the reset behavior
      // depends on whether incremental marking is running when reclaiming
      // young objects.
    } else {
      if (!node->is_root()) {
        node->set_root(true);
        if (visitor) {
          visitor->VisitRootPointer(Root::kGlobalHandles, nullptr,
                                    node->location());
        }
      }
    }
  }

  if (auto* cpp_heap = CppHeap::From(isolate_->heap()->cpp_heap())) {
    cpp_heap->LeaveNoGCScope();
  }
}

void TracedHandlesImpl::Iterate(RootVisitor* visitor) {
  for (auto* block : blocks_) {
    for (auto* node : *block) {
      if (!node->is_in_use()) continue;

      visitor->VisitRootPointer(Root::kTracedHandles, nullptr,
                                node->location());
    }
  }
}

void TracedHandlesImpl::IterateYoung(RootVisitor* visitor) {
  for (auto* node : young_nodes_) {
    if (!node->is_in_use()) continue;

    visitor->VisitRootPointer(Root::kTracedHandles, nullptr, node->location());
  }
}

void TracedHandlesImpl::IterateYoungRoots(RootVisitor* visitor) {
  for (auto* node : young_nodes_) {
    if (!node->is_in_use()) continue;

    CHECK_IMPLIES(is_marking_, node->is_root());

    if (!node->is_root()) continue;

    visitor->VisitRootPointer(Root::kTracedHandles, nullptr, node->location());
  }
}

void TracedHandlesImpl::IterateAndMarkYoungRootsWithOldHosts(
    RootVisitor* visitor) {
  for (auto* node : young_nodes_) {
    if (!node->is_in_use()) continue;
    if (!node->has_old_host()) continue;

    CHECK_IMPLIES(is_marking_, node->is_root());

    if (!node->is_root()) continue;

    node->set_markbit();
    CHECK(ObjectInYoungGeneration(node->object()));
    visitor->VisitRootPointer(Root::kTracedHandles, nullptr, node->location());
  }
}

void TracedHandlesImpl::IterateYoungRootsWithOldHostsForTesting(
    RootVisitor* visitor) {
  for (auto* node : young_nodes_) {
    if (!node->is_in_use()) continue;
    if (!node->has_old_host()) continue;

    CHECK_IMPLIES(is_marking_, node->is_root());

    if (!node->is_root()) continue;

    visitor->VisitRootPointer(Root::kTracedHandles, nullptr, node->location());
  }
}

TracedHandles::TracedHandles(Isolate* isolate)
    : impl_(std::make_unique<TracedHandlesImpl>(isolate)) {}

TracedHandles::~TracedHandles() = default;

Handle<Object> TracedHandles::Create(Address value, Address* slot,
                                     GlobalHandleStoreMode store_mode) {
  return impl_->Create(value, slot, store_mode);
}

void TracedHandles::SetIsMarking(bool value) { impl_->SetIsMarking(value); }

void TracedHandles::SetIsSweepingOnMutatorThread(bool value) {
  impl_->SetIsSweepingOnMutatorThread(value);
}

const TracedHandles::NodeBounds TracedHandles::GetNodeBounds() const {
  return impl_->GetNodeBounds();
}

void TracedHandles::UpdateListOfYoungNodes() {
  impl_->UpdateListOfYoungNodes();
}

void TracedHandles::ClearListOfYoungNodes() { impl_->ClearListOfYoungNodes(); }

void TracedHandles::DeleteEmptyBlocks() { impl_->DeleteEmptyBlocks(); }

void TracedHandles::ResetDeadNodes(
    WeakSlotCallbackWithHeap should_reset_handle) {
  impl_->ResetDeadNodes(should_reset_handle);
}

void TracedHandles::ResetYoungDeadNodes(
    WeakSlotCallbackWithHeap should_reset_handle) {
  impl_->ResetYoungDeadNodes(should_reset_handle);
}

void TracedHandles::ComputeWeaknessForYoungObjects(
    WeakSlotCallback is_unmodified) {
  impl_->ComputeWeaknessForYoungObjects(is_unmodified);
}

void TracedHandles::ProcessYoungObjects(
    RootVisitor* visitor, WeakSlotCallbackWithHeap should_reset_handle) {
  impl_->ProcessYoungObjects(visitor, should_reset_handle);
}

void TracedHandles::Iterate(RootVisitor* visitor) { impl_->Iterate(visitor); }

void TracedHandles::IterateYoung(RootVisitor* visitor) {
  impl_->IterateYoung(visitor);
}

void TracedHandles::IterateYoungRoots(RootVisitor* visitor) {
  impl_->IterateYoungRoots(visitor);
}

void TracedHandles::IterateAndMarkYoungRootsWithOldHosts(RootVisitor* visitor) {
  impl_->IterateAndMarkYoungRootsWithOldHosts(visitor);
}

void TracedHandles::IterateYoungRootsWithOldHostsForTesting(
    RootVisitor* visitor) {
  impl_->IterateYoungRootsWithOldHostsForTesting(visitor);
}

size_t TracedHandles::used_node_count() const {
  return impl_->used_node_count();
}

size_t TracedHandles::total_size_bytes() const {
  return impl_->total_size_bytes();
}

size_t TracedHandles::used_size_bytes() const {
  return impl_->used_size_bytes();
}

// static
void TracedHandles::Destroy(Address* location) {
  if (!location) return;

  auto* node = TracedNode::FromLocation(location);
  auto& node_block = TracedNodeBlock::From(*node);
  auto& traced_handles = node_block.traced_handles();
  traced_handles.Destroy(node_block, *node);
}

// static
void TracedHandles::Copy(const Address* const* from, Address** to) {
  DCHECK_NOT_NULL(*from);
  DCHECK_NULL(*to);

  const TracedNode* from_node = TracedNode::FromLocation(*from);
  const auto& node_block = TracedNodeBlock::From(*from_node);
  auto& traced_handles = node_block.traced_handles();
  traced_handles.Copy(*from_node, to);
}

// static
void TracedHandles::Move(Address** from, Address** to) {
  // Fast path for moving from an empty reference.
  if (!*from) {
    Destroy(*to);
    SetSlotThreadSafe(to, nullptr);
    return;
  }

  TracedNode* from_node = TracedNode::FromLocation(*from);
  auto& node_block = TracedNodeBlock::From(*from_node);
  auto& traced_handles = node_block.traced_handles();
  traced_handles.Move(*from_node, from, to);
}

namespace {
Tagged<Object> MarkObject(Tagged<Object> obj, TracedNode& node,
                          TracedHandles::MarkMode mark_mode) {
  if (mark_mode == TracedHandles::MarkMode::kOnlyYoung &&
      !node.is_in_young_list())
    return Smi::zero();
  node.set_markbit<AccessMode::ATOMIC>();
  // Being in the young list, the node may still point to an old object, in
  // which case we want to keep the node marked, but not follow the reference.
  if (mark_mode == TracedHandles::MarkMode::kOnlyYoung &&
      !ObjectInYoungGeneration(obj))
    return Smi::zero();
  return obj;
}
}  // namespace

// static
Tagged<Object> TracedHandles::Mark(Address* location, MarkMode mark_mode) {
  // The load synchronizes internal bitfields that are also read atomically
  // from the concurrent marker. The counterpart is `TracedNode::Publish()`.
  Tagged<Object> object =
      Tagged<Object>(reinterpret_cast<std::atomic<Address>*>(location)->load(
          std::memory_order_acquire));
  auto* node = TracedNode::FromLocation(location);
  DCHECK(node->is_in_use<AccessMode::ATOMIC>());
  return MarkObject(object, *node, mark_mode);
}

// static
Tagged<Object> TracedHandles::MarkConservatively(
    Address* inner_location, Address* traced_node_block_base,
    MarkMode mark_mode) {
  // Compute the `TracedNode` address based on its inner pointer.
  const ptrdiff_t delta = reinterpret_cast<uintptr_t>(inner_location) -
                          reinterpret_cast<uintptr_t>(traced_node_block_base);
  const auto index = delta / sizeof(TracedNode);
  TracedNode& node =
      reinterpret_cast<TracedNode*>(traced_node_block_base)[index];
  // `MarkConservatively()` runs concurrently with marking code. Reading
  // state concurrently to setting the markbit is safe.
  if (!node.is_in_use<AccessMode::ATOMIC>()) return Smi::zero();
  return MarkObject(node.object(), node, mark_mode);
}

bool TracedHandles::HasYoung() const { return impl_->HasYoung(); }

}  // namespace v8::internal

Zerion Mini Shell 1.0