blob: 1782514d6ec8ca509b4b37b9d40a55c058b67112 [file] [log] [blame]
// Copyright 2009 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/global-handles.h"
#include <algorithm>
#include <cstdint>
#include <map>
#include "src/api/api-inl.h"
#include "src/base/compiler-specific.h"
#include "src/execution/vm-state-inl.h"
#include "src/heap/embedder-tracing.h"
#include "src/heap/heap-write-barrier-inl.h"
#include "src/init/v8.h"
#include "src/logging/counters.h"
#include "src/objects/objects-inl.h"
#include "src/objects/slots.h"
#include "src/objects/visitors.h"
#include "src/sanitizer/asan.h"
#include "src/tasks/cancelable-task.h"
#include "src/tasks/task-utils.h"
#include "src/utils/utils.h"
namespace v8 {
namespace internal {
namespace {
// Specifies whether V8 expects the holder memory of a global handle to be live
// or dead.
enum class HandleHolder { kLive, kDead };
constexpr size_t kBlockSize = 256;
} // namespace
template <class _NodeType>
class GlobalHandles::NodeBlock final {
public:
using BlockType = NodeBlock<_NodeType>;
using NodeType = _NodeType;
V8_INLINE static const NodeBlock* From(const NodeType* node);
V8_INLINE static NodeBlock* From(NodeType* node);
NodeBlock(GlobalHandles* global_handles,
GlobalHandles::NodeSpace<NodeType>* space,
NodeBlock* next) V8_NOEXCEPT : next_(next),
global_handles_(global_handles),
space_(space) {}
NodeType* at(size_t index) { return &nodes_[index]; }
const NodeType* at(size_t index) const { return &nodes_[index]; }
GlobalHandles::NodeSpace<NodeType>* space() const { return space_; }
GlobalHandles* global_handles() const { return global_handles_; }
V8_INLINE bool IncreaseUsage();
V8_INLINE bool DecreaseUsage();
V8_INLINE void ListAdd(NodeBlock** top);
V8_INLINE void ListRemove(NodeBlock** top);
NodeBlock* next() const { return next_; }
NodeBlock* next_used() const { return next_used_; }
private:
NodeType nodes_[kBlockSize];
NodeBlock* const next_;
GlobalHandles* const global_handles_;
GlobalHandles::NodeSpace<NodeType>* const space_;
NodeBlock* next_used_ = nullptr;
NodeBlock* prev_used_ = nullptr;
uint32_t used_nodes_ = 0;
DISALLOW_COPY_AND_ASSIGN(NodeBlock);
};
template <class NodeType>
const GlobalHandles::NodeBlock<NodeType>*
GlobalHandles::NodeBlock<NodeType>::From(const NodeType* node) {
const NodeType* firstNode = node - node->index();
const BlockType* block = reinterpret_cast<const BlockType*>(firstNode);
DCHECK_EQ(node, block->at(node->index()));
return block;
}
template <class NodeType>
GlobalHandles::NodeBlock<NodeType>* GlobalHandles::NodeBlock<NodeType>::From(
NodeType* node) {
NodeType* firstNode = node - node->index();
BlockType* block = reinterpret_cast<BlockType*>(firstNode);
DCHECK_EQ(node, block->at(node->index()));
return block;
}
template <class NodeType>
bool GlobalHandles::NodeBlock<NodeType>::IncreaseUsage() {
DCHECK_LT(used_nodes_, kBlockSize);
return used_nodes_++ == 0;
}
template <class NodeType>
void GlobalHandles::NodeBlock<NodeType>::ListAdd(BlockType** top) {
BlockType* old_top = *top;
*top = this;
next_used_ = old_top;
prev_used_ = nullptr;
if (old_top != nullptr) {
old_top->prev_used_ = this;
}
}
template <class NodeType>
bool GlobalHandles::NodeBlock<NodeType>::DecreaseUsage() {
DCHECK_GT(used_nodes_, 0);
return --used_nodes_ == 0;
}
template <class NodeType>
void GlobalHandles::NodeBlock<NodeType>::ListRemove(BlockType** top) {
if (next_used_ != nullptr) next_used_->prev_used_ = prev_used_;
if (prev_used_ != nullptr) prev_used_->next_used_ = next_used_;
if (this == *top) {
*top = next_used_;
}
}
template <class BlockType>
class GlobalHandles::NodeIterator final {
public:
using NodeType = typename BlockType::NodeType;
// Iterator traits.
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = NodeType*;
using reference = value_type;
using pointer = value_type*;
explicit NodeIterator(BlockType* block) V8_NOEXCEPT : block_(block) {}
NodeIterator(NodeIterator&& other) V8_NOEXCEPT : block_(other.block_),
index_(other.index_) {}
bool operator==(const NodeIterator& other) const {
return block_ == other.block_;
}
bool operator!=(const NodeIterator& other) const {
return block_ != other.block_;
}
NodeIterator& operator++() {
if (++index_ < kBlockSize) return *this;
index_ = 0;
block_ = block_->next_used();
return *this;
}
NodeType* operator*() { return block_->at(index_); }
NodeType* operator->() { return block_->at(index_); }
private:
BlockType* block_ = nullptr;
size_t index_ = 0;
DISALLOW_COPY_AND_ASSIGN(NodeIterator);
};
template <class NodeType>
class GlobalHandles::NodeSpace final {
public:
using BlockType = NodeBlock<NodeType>;
using iterator = NodeIterator<BlockType>;
static NodeSpace* From(NodeType* node);
static void Release(NodeType* node);
explicit NodeSpace(GlobalHandles* global_handles) V8_NOEXCEPT
: global_handles_(global_handles) {}
~NodeSpace();
V8_INLINE NodeType* Acquire(Object object);
iterator begin() { return iterator(first_used_block_); }
iterator end() { return iterator(nullptr); }
size_t TotalSize() const { return blocks_ * sizeof(NodeType) * kBlockSize; }
size_t handles_count() const { return handles_count_; }
private:
void PutNodesOnFreeList(BlockType* block);
V8_INLINE void Free(NodeType* node);
GlobalHandles* const global_handles_;
BlockType* first_block_ = nullptr;
BlockType* first_used_block_ = nullptr;
NodeType* first_free_ = nullptr;
size_t blocks_ = 0;
size_t handles_count_ = 0;
};
template <class NodeType>
GlobalHandles::NodeSpace<NodeType>::~NodeSpace() {
auto* block = first_block_;
while (block != nullptr) {
auto* tmp = block->next();
delete block;
block = tmp;
}
}
template <class NodeType>
NodeType* GlobalHandles::NodeSpace<NodeType>::Acquire(Object object) {
if (first_free_ == nullptr) {
first_block_ = new BlockType(global_handles_, this, first_block_);
blocks_++;
PutNodesOnFreeList(first_block_);
}
DCHECK_NOT_NULL(first_free_);
NodeType* node = first_free_;
first_free_ = first_free_->next_free();
node->Acquire(object);
BlockType* block = BlockType::From(node);
if (block->IncreaseUsage()) {
block->ListAdd(&first_used_block_);
}
global_handles_->isolate()->counters()->global_handles()->Increment();
handles_count_++;
DCHECK(node->IsInUse());
return node;
}
template <class NodeType>
void GlobalHandles::NodeSpace<NodeType>::PutNodesOnFreeList(BlockType* block) {
for (int32_t i = kBlockSize - 1; i >= 0; --i) {
NodeType* node = block->at(i);
const uint8_t index = static_cast<uint8_t>(i);
DCHECK_EQ(i, index);
node->set_index(index);
node->Free(first_free_);
first_free_ = node;
}
}
template <class NodeType>
void GlobalHandles::NodeSpace<NodeType>::Release(NodeType* node) {
BlockType* block = BlockType::From(node);
block->space()->Free(node);
}
template <class NodeType>
void GlobalHandles::NodeSpace<NodeType>::Free(NodeType* node) {
node->Release(first_free_);
first_free_ = node;
BlockType* block = BlockType::From(node);
if (block->DecreaseUsage()) {
block->ListRemove(&first_used_block_);
}
global_handles_->isolate()->counters()->global_handles()->Decrement();
handles_count_--;
}
template <class Child>
class NodeBase {
public:
static const Child* FromLocation(const Address* location) {
return reinterpret_cast<const Child*>(location);
}
static Child* FromLocation(Address* location) {
return reinterpret_cast<Child*>(location);
}
NodeBase() {
DCHECK_EQ(offsetof(NodeBase, object_), 0);
DCHECK_EQ(offsetof(NodeBase, class_id_), Internals::kNodeClassIdOffset);
DCHECK_EQ(offsetof(NodeBase, flags_), Internals::kNodeFlagsOffset);
}
#ifdef ENABLE_HANDLE_ZAPPING
~NodeBase() {
ClearFields();
data_.next_free = nullptr;
index_ = 0;
}
#endif
void Free(Child* free_list) {
ClearFields();
AsChild()->MarkAsFree();
data_.next_free = free_list;
}
void Acquire(Object object) {
DCHECK(!AsChild()->IsInUse());
CheckFieldsAreCleared();
object_ = object.ptr();
AsChild()->MarkAsUsed();
data_.parameter = nullptr;
DCHECK(AsChild()->IsInUse());
}
void Release(Child* free_list) {
DCHECK(AsChild()->IsInUse());
Free(free_list);
DCHECK(!AsChild()->IsInUse());
}
Object object() const { return Object(object_); }
FullObjectSlot location() { return FullObjectSlot(&object_); }
Handle<Object> handle() { return Handle<Object>(&object_); }
uint8_t index() const { return index_; }
void set_index(uint8_t value) { index_ = value; }
uint16_t wrapper_class_id() const { return class_id_; }
bool has_wrapper_class_id() const {
return class_id_ != v8::HeapProfiler::kPersistentHandleNoClassId;
}
// Accessors for next free node in the free list.
Child* next_free() {
DCHECK(!AsChild()->IsInUse());
return data_.next_free;
}
void set_parameter(void* parameter) {
DCHECK(AsChild()->IsInUse());
data_.parameter = parameter;
}
void* parameter() const {
DCHECK(AsChild()->IsInUse());
return data_.parameter;
}
protected:
Child* AsChild() { return reinterpret_cast<Child*>(this); }
const Child* AsChild() const { return reinterpret_cast<const Child*>(this); }
void ClearFields() {
// Zap the values for eager trapping.
object_ = kGlobalHandleZapValue;
class_id_ = v8::HeapProfiler::kPersistentHandleNoClassId;
AsChild()->ClearImplFields();
}
void CheckFieldsAreCleared() {
DCHECK_EQ(kGlobalHandleZapValue, object_);
DCHECK_EQ(v8::HeapProfiler::kPersistentHandleNoClassId, class_id_);
AsChild()->CheckImplFieldsAreCleared();
}
// Storage for object pointer.
//
// Placed first to avoid offset computation. The stored data is equivalent to
// an Object. It is stored as a plain Address for convenience (smallest number
// of casts), and because it is a private implementation detail: the public
// interface provides type safety.
Address object_;
// Class id set by the embedder.
uint16_t class_id_;
// Index in the containing handle block.
uint8_t index_;
uint8_t flags_;
// The meaning of this field depends on node state:
// - Node in free list: Stores next free node pointer.
// - Otherwise, specific to the node implementation.
union {
Child* next_free;
void* parameter;
} data_;
};
namespace {
void ExtractInternalFields(JSObject jsobject, void** embedder_fields, int len) {
int field_count = jsobject.GetEmbedderFieldCount();
IsolateRoot isolate = GetIsolateForPtrCompr(jsobject);
for (int i = 0; i < len; ++i) {
if (field_count == i) break;
void* pointer;
if (EmbedderDataSlot(jsobject, i).ToAlignedPointer(isolate, &pointer)) {
embedder_fields[i] = pointer;
}
}
}
} // namespace
class GlobalHandles::Node final : public NodeBase<GlobalHandles::Node> {
public:
// State transition diagram:
// FREE -> NORMAL <-> WEAK -> PENDING -> NEAR_DEATH -> { NORMAL, WEAK, FREE }
enum State {
FREE = 0,
NORMAL, // Normal global handle.
WEAK, // Flagged as weak but not yet finalized.
PENDING, // Has been recognized as only reachable by weak handles.
NEAR_DEATH, // Callback has informed the handle is near death.
NUMBER_OF_NODE_STATES
};
Node() {
STATIC_ASSERT(static_cast<int>(NodeState::kMask) ==
Internals::kNodeStateMask);
STATIC_ASSERT(WEAK == Internals::kNodeStateIsWeakValue);
STATIC_ASSERT(PENDING == Internals::kNodeStateIsPendingValue);
set_in_young_list(false);
}
void Zap() {
DCHECK(IsInUse());
// Zap the values for eager trapping.
object_ = kGlobalHandleZapValue;
}
const char* label() const {
return state() == NORMAL ? reinterpret_cast<char*>(data_.parameter)
: nullptr;
}
// State and flag accessors.
State state() const { return NodeState::decode(flags_); }
void set_state(State state) { flags_ = NodeState::update(flags_, state); }
bool is_in_young_list() const { return IsInYoungList::decode(flags_); }
void set_in_young_list(bool v) { flags_ = IsInYoungList::update(flags_, v); }
WeaknessType weakness_type() const {
return NodeWeaknessType::decode(flags_);
}
void set_weakness_type(WeaknessType weakness_type) {
flags_ = NodeWeaknessType::update(flags_, weakness_type);
}
bool IsWeak() const { return state() == WEAK; }
bool IsInUse() const { return state() != FREE; }
bool IsPhantomCallback() const {
return weakness_type() == PHANTOM_WEAK ||
weakness_type() == PHANTOM_WEAK_2_EMBEDDER_FIELDS;
}
bool IsPhantomResetHandle() const {
return weakness_type() == PHANTOM_WEAK_RESET_HANDLE;
}
bool IsFinalizerHandle() const { return weakness_type() == FINALIZER_WEAK; }
bool IsPendingPhantomCallback() const {
return state() == PENDING && IsPhantomCallback();
}
bool IsPendingPhantomResetHandle() const {
return state() == PENDING && IsPhantomResetHandle();
}
bool IsPendingFinalizer() const {
return state() == PENDING && weakness_type() == FINALIZER_WEAK;
}
bool IsPending() const { return state() == PENDING; }
bool IsRetainer() const {
return state() != FREE &&
!(state() == NEAR_DEATH && weakness_type() != FINALIZER_WEAK);
}
bool IsStrongRetainer() const { return state() == NORMAL; }
bool IsWeakRetainer() const {
return state() == WEAK || state() == PENDING ||
(state() == NEAR_DEATH && weakness_type() == FINALIZER_WEAK);
}
void MarkPending() {
DCHECK(state() == WEAK);
set_state(PENDING);
}
bool has_callback() const { return weak_callback_ != nullptr; }
// Accessors for next free node in the free list.
Node* next_free() {
DCHECK_EQ(FREE, state());
return data_.next_free;
}
void MakeWeak(void* parameter,
WeakCallbackInfo<void>::Callback phantom_callback,
v8::WeakCallbackType type) {
DCHECK_NOT_NULL(phantom_callback);
DCHECK(IsInUse());
CHECK_NE(object_, kGlobalHandleZapValue);
set_state(WEAK);
switch (type) {
case v8::WeakCallbackType::kParameter:
set_weakness_type(PHANTOM_WEAK);
break;
case v8::WeakCallbackType::kInternalFields:
set_weakness_type(PHANTOM_WEAK_2_EMBEDDER_FIELDS);
break;
case v8::WeakCallbackType::kFinalizer:
set_weakness_type(FINALIZER_WEAK);
break;
}
set_parameter(parameter);
weak_callback_ = phantom_callback;
}
void MakeWeak(Address** location_addr) {
DCHECK(IsInUse());
CHECK_NE(object_, kGlobalHandleZapValue);
set_state(WEAK);
set_weakness_type(PHANTOM_WEAK_RESET_HANDLE);
set_parameter(location_addr);
weak_callback_ = nullptr;
}
void* ClearWeakness() {
DCHECK(IsInUse());
void* p = parameter();
set_state(NORMAL);
set_parameter(nullptr);
return p;
}
void AnnotateStrongRetainer(const char* label) {
DCHECK_EQ(state(), NORMAL);
data_.parameter = const_cast<char*>(label);
}
void CollectPhantomCallbackData(
std::vector<std::pair<Node*, PendingPhantomCallback>>*
pending_phantom_callbacks) {
DCHECK(weakness_type() == PHANTOM_WEAK ||
weakness_type() == PHANTOM_WEAK_2_EMBEDDER_FIELDS);
DCHECK(state() == PENDING);
DCHECK_NOT_NULL(weak_callback_);
void* embedder_fields[v8::kEmbedderFieldsInWeakCallback] = {nullptr,
nullptr};
if (weakness_type() != PHANTOM_WEAK && object().IsJSObject()) {
ExtractInternalFields(JSObject::cast(object()), embedder_fields,
v8::kEmbedderFieldsInWeakCallback);
}
// Zap with something dangerous.
location().store(Object(0xCA11));
pending_phantom_callbacks->push_back(std::make_pair(
this,
PendingPhantomCallback(weak_callback_, parameter(), embedder_fields)));
DCHECK(IsInUse());
set_state(NEAR_DEATH);
}
void ResetPhantomHandle(HandleHolder handle_holder) {
DCHECK_EQ(HandleHolder::kLive, handle_holder);
DCHECK_EQ(PHANTOM_WEAK_RESET_HANDLE, weakness_type());
DCHECK_EQ(PENDING, state());
DCHECK_NULL(weak_callback_);
Address** handle = reinterpret_cast<Address**>(parameter());
*handle = nullptr;
NodeSpace<Node>::Release(this);
}
void PostGarbageCollectionProcessing(Isolate* isolate) {
// This method invokes a finalizer. Updating the method name would require
// adjusting CFI blocklist as weak_callback_ is invoked on the wrong type.
CHECK(IsPendingFinalizer());
set_state(NEAR_DEATH);
// Check that we are not passing a finalized external string to
// the callback.
DCHECK(!object().IsExternalOneByteString() ||
ExternalOneByteString::cast(object()).resource() != nullptr);
DCHECK(!object().IsExternalTwoByteString() ||
ExternalTwoByteString::cast(object()).resource() != nullptr);
// Leaving V8.
VMState<EXTERNAL> vmstate(isolate);
HandleScope handle_scope(isolate);
void* embedder_fields[v8::kEmbedderFieldsInWeakCallback] = {nullptr,
nullptr};
v8::WeakCallbackInfo<void> data(reinterpret_cast<v8::Isolate*>(isolate),
parameter(), embedder_fields, nullptr);
weak_callback_(data);
// For finalizers the handle must have either been reset or made strong.
// Both cases reset the state.
CHECK_NE(NEAR_DEATH, state());
}
void MarkAsFree() { set_state(FREE); }
void MarkAsUsed() { set_state(NORMAL); }
GlobalHandles* global_handles() {
return NodeBlock<Node>::From(this)->global_handles();
}
private:
// Fields that are not used for managing node memory.
void ClearImplFields() { weak_callback_ = nullptr; }
void CheckImplFieldsAreCleared() { DCHECK_EQ(nullptr, weak_callback_); }
// This stores three flags (independent, partially_dependent and
// in_young_list) and a State.
using NodeState = base::BitField8<State, 0, 3>;
using IsInYoungList = NodeState::Next<bool, 1>;
using NodeWeaknessType = IsInYoungList::Next<WeaknessType, 2>;
// Handle specific callback - might be a weak reference in disguise.
WeakCallbackInfo<void>::Callback weak_callback_;
friend class NodeBase<Node>;
DISALLOW_COPY_AND_ASSIGN(Node);
};
class GlobalHandles::TracedNode final
: public NodeBase<GlobalHandles::TracedNode> {
public:
TracedNode() { set_in_young_list(false); }
// Copy and move ctors are used when constructing a TracedNode when recording
// a node for on-stack data structures. (Older compilers may refer to copy
// instead of move ctor.)
TracedNode(TracedNode&& other) V8_NOEXCEPT = default;
TracedNode(const TracedNode& other) V8_NOEXCEPT = default;
enum State { FREE = 0, NORMAL, NEAR_DEATH };
State state() const { return NodeState::decode(flags_); }
void set_state(State state) { flags_ = NodeState::update(flags_, state); }
void MarkAsFree() { set_state(FREE); }
void MarkAsUsed() { set_state(NORMAL); }
bool IsInUse() const { return state() != FREE; }
bool IsRetainer() const { return state() == NORMAL; }
bool IsPhantomResetHandle() const { return callback_ == nullptr; }
bool is_in_young_list() const { return IsInYoungList::decode(flags_); }
void set_in_young_list(bool v) { flags_ = IsInYoungList::update(flags_, v); }
bool is_root() const { return IsRoot::decode(flags_); }
void set_root(bool v) { flags_ = IsRoot::update(flags_, v); }
bool has_destructor() const { return HasDestructor::decode(flags_); }
void set_has_destructor(bool v) { flags_ = HasDestructor::update(flags_, v); }
bool markbit() const { return Markbit::decode(flags_); }
void clear_markbit() { flags_ = Markbit::update(flags_, false); }
void set_markbit() { flags_ = Markbit::update(flags_, true); }
bool is_on_stack() const { return IsOnStack::decode(flags_); }
void set_is_on_stack(bool v) { flags_ = IsOnStack::update(flags_, v); }
void SetFinalizationCallback(void* parameter,
WeakCallbackInfo<void>::Callback callback) {
set_parameter(parameter);
callback_ = callback;
}
bool HasFinalizationCallback() const { return callback_ != nullptr; }
void CopyObjectReference(const TracedNode& other) { object_ = other.object_; }
void CollectPhantomCallbackData(
std::vector<std::pair<TracedNode*, PendingPhantomCallback>>*
pending_phantom_callbacks) {
DCHECK(IsInUse());
DCHECK_NOT_NULL(callback_);
void* embedder_fields[v8::kEmbedderFieldsInWeakCallback] = {nullptr,
nullptr};
ExtractInternalFields(JSObject::cast(object()), embedder_fields,
v8::kEmbedderFieldsInWeakCallback);
// Zap with something dangerous.
location().store(Object(0xCA11));
pending_phantom_callbacks->push_back(std::make_pair(
this, PendingPhantomCallback(callback_, parameter(), embedder_fields)));
set_state(NEAR_DEATH);
}
void ResetPhantomHandle(HandleHolder handle_holder) {
DCHECK(IsInUse());
if (handle_holder == HandleHolder::kLive) {
Address** handle = reinterpret_cast<Address**>(data_.parameter);
*handle = nullptr;
}
NodeSpace<TracedNode>::Release(this);
DCHECK(!IsInUse());
}
static void Verify(GlobalHandles* global_handles, const Address* const* slot);
protected:
using NodeState = base::BitField8<State, 0, 2>;
using IsInYoungList = NodeState::Next<bool, 1>;
using IsRoot = IsInYoungList::Next<bool, 1>;
using HasDestructor = IsRoot::Next<bool, 1>;
using Markbit = HasDestructor::Next<bool, 1>;
using IsOnStack = Markbit::Next<bool, 1>;
void ClearImplFields() {
set_root(true);
// Nodes are black allocated for simplicity.
set_markbit();
callback_ = nullptr;
set_is_on_stack(false);
set_has_destructor(false);
}
void CheckImplFieldsAreCleared() const {
DCHECK(is_root());
DCHECK(markbit());
DCHECK_NULL(callback_);
}
WeakCallbackInfo<void>::Callback callback_;
friend class NodeBase<GlobalHandles::TracedNode>;
};
// Space to keep track of on-stack handles (e.g. TracedReference). Such
// references are treated as root for any V8 garbage collection. The data
// structure is self healing and pessimistally filters outdated entries on
// insertion and iteration.
//
// Design doc: http://bit.ly/on-stack-traced-reference
class GlobalHandles::OnStackTracedNodeSpace final {
public:
static GlobalHandles* GetGlobalHandles(const TracedNode* on_stack_node) {
DCHECK(on_stack_node->is_on_stack());
return reinterpret_cast<const NodeEntry*>(on_stack_node)->global_handles;
}
explicit OnStackTracedNodeSpace(GlobalHandles* global_handles)
: global_handles_(global_handles) {}
void SetStackStart(void* stack_start) {
CHECK(on_stack_nodes_.empty());
stack_start_ = base::Stack::GetRealStackAddressForSlot(stack_start);
}
V8_INLINE bool IsOnStack(uintptr_t slot) const;
void Iterate(RootVisitor* v);
TracedNode* Acquire(Object value, uintptr_t address);
void CleanupBelowCurrentStackPosition();
void NotifyEmptyEmbedderStack();
size_t NumberOfHandlesForTesting() const { return on_stack_nodes_.size(); }
private:
struct NodeEntry {
TracedNode node;
// Used to find back to GlobalHandles from a Node on copy. Needs to follow
// node.
GlobalHandles* global_handles;
};
// Keeps track of registered handles. The data structure is cleaned on
// iteration and when adding new references using the current stack address.
// Cleaning is based on current stack address and the key of the map which is
// slightly different for ASAN configs -- see below.
#ifdef V8_USE_ADDRESS_SANITIZER
// Mapping from stack slots or real stack frames to the corresponding nodes.
// In case a reference is part of a fake frame, we map it to the real stack
// frame base instead of the actual stack slot. The list keeps all nodes for
// a particular real frame.
std::map<uintptr_t, std::list<NodeEntry>> on_stack_nodes_;
#else // !V8_USE_ADDRESS_SANITIZER
// Mapping from stack slots to the corresponding nodes. We don't expect
// aliasing with overlapping lifetimes of nodes.
std::map<uintptr_t, NodeEntry> on_stack_nodes_;
#endif // !V8_USE_ADDRESS_SANITIZER
uintptr_t stack_start_ = 0;
GlobalHandles* global_handles_ = nullptr;
size_t acquire_count_ = 0;
};
bool GlobalHandles::OnStackTracedNodeSpace::IsOnStack(uintptr_t slot) const {
#ifdef V8_USE_ADDRESS_SANITIZER
if (__asan_addr_is_in_fake_stack(__asan_get_current_fake_stack(),
reinterpret_cast<void*>(slot), nullptr,
nullptr)) {
return true;
}
#endif // V8_USE_ADDRESS_SANITIZER
return stack_start_ >= slot && slot > base::Stack::GetCurrentStackPosition();
}
void GlobalHandles::OnStackTracedNodeSpace::NotifyEmptyEmbedderStack() {
on_stack_nodes_.clear();
}
void GlobalHandles::OnStackTracedNodeSpace::Iterate(RootVisitor* v) {
#ifdef V8_USE_ADDRESS_SANITIZER
for (auto& pair : on_stack_nodes_) {
for (auto& node_entry : pair.second) {
TracedNode& node = node_entry.node;
if (node.IsRetainer()) {
v->VisitRootPointer(Root::kGlobalHandles, "on-stack TracedReference",
node.location());
}
}
}
#else // !V8_USE_ADDRESS_SANITIZER
// Handles have been cleaned from the GC entry point which is higher up the
// stack.
for (auto& pair : on_stack_nodes_) {
TracedNode& node = pair.second.node;
if (node.IsRetainer()) {
v->VisitRootPointer(Root::kGlobalHandles, "on-stack TracedReference",
node.location());
}
}
#endif // !V8_USE_ADDRESS_SANITIZER
}
GlobalHandles::TracedNode* GlobalHandles::OnStackTracedNodeSpace::Acquire(
Object value, uintptr_t slot) {
constexpr size_t kAcquireCleanupThresholdLog2 = 8;
constexpr size_t kAcquireCleanupThresholdMask =
(size_t{1} << kAcquireCleanupThresholdLog2) - 1;
DCHECK(IsOnStack(slot));
if (((acquire_count_++) & kAcquireCleanupThresholdMask) == 0) {
CleanupBelowCurrentStackPosition();
}
NodeEntry entry;
entry.node.Free(nullptr);
entry.global_handles = global_handles_;
#ifdef V8_USE_ADDRESS_SANITIZER
auto pair = on_stack_nodes_.insert(
{base::Stack::GetRealStackAddressForSlot(slot), {}});
pair.first->second.push_back(std::move(entry));
TracedNode* result = &(pair.first->second.back().node);
#else // !V8_USE_ADDRESS_SANITIZER
auto pair = on_stack_nodes_.insert(
{base::Stack::GetRealStackAddressForSlot(slot), std::move(entry)});
if (!pair.second) {
// Insertion failed because there already was an entry present for that
// stack address. This can happen because cleanup is conservative in which
// stack limits it used. Reusing the entry is fine as there's no aliasing of
// different references with the same stack slot.
pair.first->second.node.Free(nullptr);
}
TracedNode* result = &(pair.first->second.node);
#endif // !V8_USE_ADDRESS_SANITIZER
result->Acquire(value);
result->set_is_on_stack(true);
return result;
}
void GlobalHandles::OnStackTracedNodeSpace::CleanupBelowCurrentStackPosition() {
if (on_stack_nodes_.empty()) return;
const auto it =
on_stack_nodes_.upper_bound(base::Stack::GetCurrentStackPosition());
on_stack_nodes_.erase(on_stack_nodes_.begin(), it);
}
// static
void GlobalHandles::TracedNode::Verify(GlobalHandles* global_handles,
const Address* const* slot) {
#ifdef DEBUG
const TracedNode* node = FromLocation(*slot);
DCHECK(node->IsInUse());
DCHECK_IMPLIES(!node->has_destructor(), nullptr == node->parameter());
DCHECK_IMPLIES(node->has_destructor() && !node->HasFinalizationCallback(),
node->parameter());
bool slot_on_stack = global_handles->on_stack_nodes_->IsOnStack(
reinterpret_cast<uintptr_t>(slot));
DCHECK_EQ(slot_on_stack, node->is_on_stack());
if (!node->is_on_stack()) {
// On-heap nodes have seprate lists for young generation processing.
bool is_young_gen_object = ObjectInYoungGeneration(node->object());
DCHECK_IMPLIES(is_young_gen_object, node->is_in_young_list());
}
bool in_young_list =
std::find(global_handles->traced_young_nodes_.begin(),
global_handles->traced_young_nodes_.end(),
node) != global_handles->traced_young_nodes_.end();
DCHECK_EQ(in_young_list, node->is_in_young_list());
#endif // DEBUG
}
void GlobalHandles::CleanupOnStackReferencesBelowCurrentStackPosition() {
on_stack_nodes_->CleanupBelowCurrentStackPosition();
}
size_t GlobalHandles::NumberOfOnStackHandlesForTesting() {
return on_stack_nodes_->NumberOfHandlesForTesting();
}
size_t GlobalHandles::TotalSize() const {
return regular_nodes_->TotalSize() + traced_nodes_->TotalSize();
}
size_t GlobalHandles::UsedSize() const {
return regular_nodes_->handles_count() * sizeof(Node) +
traced_nodes_->handles_count() * sizeof(TracedNode);
}
size_t GlobalHandles::handles_count() const {
return regular_nodes_->handles_count() + traced_nodes_->handles_count();
}
void GlobalHandles::SetStackStart(void* stack_start) {
on_stack_nodes_->SetStackStart(stack_start);
}
void GlobalHandles::NotifyEmptyEmbedderStack() {
on_stack_nodes_->NotifyEmptyEmbedderStack();
}
GlobalHandles::GlobalHandles(Isolate* isolate)
: isolate_(isolate),
regular_nodes_(new NodeSpace<GlobalHandles::Node>(this)),
traced_nodes_(new NodeSpace<GlobalHandles::TracedNode>(this)),
on_stack_nodes_(new OnStackTracedNodeSpace(this)) {}
GlobalHandles::~GlobalHandles() { regular_nodes_.reset(nullptr); }
Handle<Object> GlobalHandles::Create(Object value) {
GlobalHandles::Node* result = regular_nodes_->Acquire(value);
if (ObjectInYoungGeneration(value) && !result->is_in_young_list()) {
young_nodes_.push_back(result);
result->set_in_young_list(true);
}
return result->handle();
}
Handle<Object> GlobalHandles::Create(Address value) {
return Create(Object(value));
}
Handle<Object> GlobalHandles::CreateTraced(Object value, Address* slot,
bool has_destructor) {
return CreateTraced(
value, slot, has_destructor,
on_stack_nodes_->IsOnStack(reinterpret_cast<uintptr_t>(slot)));
}
Handle<Object> GlobalHandles::CreateTraced(Object value, Address* slot,
bool has_destructor,
bool is_on_stack) {
GlobalHandles::TracedNode* result;
if (is_on_stack) {
result = on_stack_nodes_->Acquire(value, reinterpret_cast<uintptr_t>(slot));
} else {
result = traced_nodes_->Acquire(value);
if (ObjectInYoungGeneration(value) && !result->is_in_young_list()) {
traced_young_nodes_.push_back(result);
result->set_in_young_list(true);
}
}
result->set_has_destructor(has_destructor);
result->set_parameter(has_destructor ? slot : nullptr);
return result->handle();
}
Handle<Object> GlobalHandles::CreateTraced(Address value, Address* slot,
bool has_destructor) {
return CreateTraced(Object(value), slot, has_destructor);
}
Handle<Object> GlobalHandles::CopyGlobal(Address* location) {
DCHECK_NOT_NULL(location);
GlobalHandles* global_handles =
Node::FromLocation(location)->global_handles();
#ifdef VERIFY_HEAP
if (i::FLAG_verify_heap) {
Object(*location).ObjectVerify(global_handles->isolate());
}
#endif // VERIFY_HEAP
return global_handles->Create(*location);
}
namespace {
void SetSlotThreadSafe(Address** slot, Address* val) {
reinterpret_cast<std::atomic<Address*>*>(slot)->store(
val, std::memory_order_relaxed);
}
} // namespace
// static
void GlobalHandles::CopyTracedGlobal(const Address* const* from, Address** to) {
DCHECK_NOT_NULL(*from);
DCHECK_NULL(*to);
const TracedNode* node = TracedNode::FromLocation(*from);
// Copying a traced handle with finalization callback is prohibited because
// the callback may require knowing about multiple copies of the traced
// handle.
CHECK_WITH_MSG(!node->HasFinalizationCallback(),
"Copying of references is not supported when "
"SetFinalizationCallback is set.");
GlobalHandles* global_handles =
GlobalHandles::From(const_cast<TracedNode*>(node));
Handle<Object> o = global_handles->CreateTraced(
node->object(), reinterpret_cast<Address*>(to), node->has_destructor());
SetSlotThreadSafe(to, o.location());
TracedNode::Verify(global_handles, from);
TracedNode::Verify(global_handles, to);
#ifdef VERIFY_HEAP
if (i::FLAG_verify_heap) {
Object(**to).ObjectVerify(global_handles->isolate());
}
#endif // VERIFY_HEAP
}
void GlobalHandles::MoveGlobal(Address** from, Address** to) {
DCHECK_NOT_NULL(*from);
DCHECK_NOT_NULL(*to);
DCHECK_EQ(*from, *to);
Node* node = Node::FromLocation(*from);
if (node->IsWeak() && node->IsPhantomResetHandle()) {
node->set_parameter(to);
}
// - Strong handles do not require fixups.
// - Weak handles with finalizers and callbacks are too general to fix up. For
// those the callers need to ensure consistency.
}
void GlobalHandles::MoveTracedGlobal(Address** from, Address** to) {
// Fast path for moving from an empty reference.
if (!*from) {
DestroyTraced(*to);
SetSlotThreadSafe(to, nullptr);
return;
}
// Determining whether from or to are on stack.
TracedNode* from_node = TracedNode::FromLocation(*from);
DCHECK(from_node->IsInUse());
TracedNode* to_node = TracedNode::FromLocation(*to);
GlobalHandles* global_handles = nullptr;
#ifdef DEBUG
global_handles = GlobalHandles::From(from_node);
#endif // DEBUG
bool from_on_stack = from_node->is_on_stack();
bool to_on_stack = false;
if (!to_node) {
// Figure out whether stack or heap to allow fast path for heap->heap move.
global_handles = GlobalHandles::From(from_node);
to_on_stack = global_handles->on_stack_nodes_->IsOnStack(
reinterpret_cast<uintptr_t>(to));
} else {
to_on_stack = to_node->is_on_stack();
}
// Moving a traced handle with finalization callback is prohibited because
// the callback may require knowing about multiple copies of the traced
// handle.
CHECK_WITH_MSG(!from_node->HasFinalizationCallback(),
"Moving of references is not supported when "
"SetFinalizationCallback is set.");
// Types in v8.h ensure that we only copy/move handles that have the same
// destructor behavior.
DCHECK_IMPLIES(to_node,
to_node->has_destructor() == from_node->has_destructor());
// Moving.
if (from_on_stack || to_on_stack) {
// Move involving a stack slot.
if (!to_node) {
DCHECK(global_handles);
Handle<Object> o = global_handles->CreateTraced(
from_node->object(), reinterpret_cast<Address*>(to),
from_node->has_destructor(), to_on_stack);
SetSlotThreadSafe(to, o.location());
to_node = TracedNode::FromLocation(*to);
DCHECK(to_node->markbit());
} else {
DCHECK(to_node->IsInUse());
to_node->CopyObjectReference(*from_node);
if (!to_node->is_on_stack() && !to_node->is_in_young_list() &&
ObjectInYoungGeneration(to_node->object())) {
global_handles = GlobalHandles::From(from_node);
global_handles->traced_young_nodes_.push_back(to_node);
to_node->set_in_young_list(true);
}
}
DestroyTraced(*from);
SetSlotThreadSafe(from, nullptr);
} else {
// Pure heap move.
DestroyTraced(*to);
SetSlotThreadSafe(to, *from);
to_node = from_node;
DCHECK_NOT_NULL(*from);
DCHECK_NOT_NULL(*to);
DCHECK_EQ(*from, *to);
// Fixup back reference for destructor.
if (to_node->has_destructor()) {
to_node->set_parameter(to);
}
SetSlotThreadSafe(from, nullptr);
}
TracedNode::Verify(global_handles, to);
}
// static
GlobalHandles* GlobalHandles::From(const TracedNode* node) {
return node->is_on_stack()
? OnStackTracedNodeSpace::GetGlobalHandles(node)
: NodeBlock<TracedNode>::From(node)->global_handles();
}
void GlobalHandles::MarkTraced(Address* location) {
TracedNode* node = TracedNode::FromLocation(location);
node->set_markbit();
DCHECK(node->IsInUse());
}
void GlobalHandles::Destroy(Address* location) {
if (location != nullptr) {
NodeSpace<Node>::Release(Node::FromLocation(location));
}
}
void GlobalHandles::DestroyTraced(Address* location) {
if (location != nullptr) {
TracedNode* node = TracedNode::FromLocation(location);
if (node->is_on_stack()) {
node->Release(nullptr);
} else {
NodeSpace<TracedNode>::Release(node);
}
}
}
void GlobalHandles::SetFinalizationCallbackForTraced(
Address* location, void* parameter,
WeakCallbackInfo<void>::Callback callback) {
TracedNode::FromLocation(location)->SetFinalizationCallback(parameter,
callback);
}
using GenericCallback = v8::WeakCallbackInfo<void>::Callback;
void GlobalHandles::MakeWeak(Address* location, void* parameter,
GenericCallback phantom_callback,
v8::WeakCallbackType type) {
Node::FromLocation(location)->MakeWeak(parameter, phantom_callback, type);
}
void GlobalHandles::MakeWeak(Address** location_addr) {
Node::FromLocation(*location_addr)->MakeWeak(location_addr);
}
void* GlobalHandles::ClearWeakness(Address* location) {
return Node::FromLocation(location)->ClearWeakness();
}
void GlobalHandles::AnnotateStrongRetainer(Address* location,
const char* label) {
Node::FromLocation(location)->AnnotateStrongRetainer(label);
}
bool GlobalHandles::IsWeak(Address* location) {
return Node::FromLocation(location)->IsWeak();
}
DISABLE_CFI_PERF
void GlobalHandles::IterateWeakRootsForFinalizers(RootVisitor* v) {
for (Node* node : *regular_nodes_) {
if (node->IsWeakRetainer() && node->state() == Node::PENDING) {
DCHECK(!node->IsPhantomCallback());
DCHECK(!node->IsPhantomResetHandle());
// Finalizers need to survive.
v->VisitRootPointer(Root::kGlobalHandles, node->label(),
node->location());
}
}
}
DISABLE_CFI_PERF
void GlobalHandles::IterateWeakRootsForPhantomHandles(
WeakSlotCallbackWithHeap should_reset_handle) {
for (Node* node : *regular_nodes_) {
if (node->IsWeakRetainer() &&
should_reset_handle(isolate()->heap(), node->location())) {
if (node->IsPhantomResetHandle()) {
node->MarkPending();
node->ResetPhantomHandle(HandleHolder::kLive);
++number_of_phantom_handle_resets_;
} else if (node->IsPhantomCallback()) {
node->MarkPending();
node->CollectPhantomCallbackData(&regular_pending_phantom_callbacks_);
}
}
}
for (TracedNode* node : *traced_nodes_) {
if (!node->IsInUse()) continue;
// Detect unreachable nodes first.
if (!node->markbit() && node->IsPhantomResetHandle() &&
!node->has_destructor()) {
// The handle is unreachable and does not have a callback and a
// destructor associated with it. We can clear it even if the target V8
// object is alive. Note that the desctructor and the callback may
// access the handle, that is why we avoid clearing it.
node->ResetPhantomHandle(HandleHolder::kDead);
++number_of_phantom_handle_resets_;
continue;
} else if (node->markbit()) {
// Clear the markbit for the next GC.
node->clear_markbit();
}
DCHECK(node->IsInUse());
// Detect nodes with unreachable target objects.
if (should_reset_handle(isolate()->heap(), node->location())) {
// If the node allows eager resetting, then reset it here. Otherwise,
// collect its callback that will reset it.
if (node->IsPhantomResetHandle()) {
node->ResetPhantomHandle(node->has_destructor() ? HandleHolder::kLive
: HandleHolder::kDead);
++number_of_phantom_handle_resets_;
} else {
node->CollectPhantomCallbackData(&traced_pending_phantom_callbacks_);
}
}
}
}
void GlobalHandles::IterateWeakRootsIdentifyFinalizers(
WeakSlotCallbackWithHeap should_reset_handle) {
for (Node* node : *regular_nodes_) {
if (node->IsWeak() &&
should_reset_handle(isolate()->heap(), node->location())) {
if (node->IsFinalizerHandle()) {
node->MarkPending();
}
}
}
}
void GlobalHandles::IdentifyWeakUnmodifiedObjects(
WeakSlotCallback is_unmodified) {
if (!FLAG_reclaim_unmodified_wrappers) return;
LocalEmbedderHeapTracer* const tracer =
isolate()->heap()->local_embedder_heap_tracer();
for (TracedNode* node : traced_young_nodes_) {
if (node->IsInUse()) {
DCHECK(node->is_root());
if (is_unmodified(node->location())) {
v8::Value* value = ToApi<v8::Value>(node->handle());
if (node->has_destructor()) {
node->set_root(tracer->IsRootForNonTracingGC(
*reinterpret_cast<v8::TracedGlobal<v8::Value>*>(&value)));
} else {
node->set_root(tracer->IsRootForNonTracingGC(
*reinterpret_cast<v8::TracedReference<v8::Value>*>(&value)));
}
}
}
}
}
void GlobalHandles::IterateYoungStrongAndDependentRoots(RootVisitor* v) {
for (Node* node : young_nodes_) {
if (node->IsStrongRetainer()) {
v->VisitRootPointer(Root::kGlobalHandles, node->label(),
node->location());
}
}
for (TracedNode* node : traced_young_nodes_) {
if (node->IsInUse() && node->is_root()) {
v->VisitRootPointer(Root::kGlobalHandles, nullptr, node->location());
}
}
}
void GlobalHandles::MarkYoungWeakDeadObjectsPending(
WeakSlotCallbackWithHeap is_dead) {
for (Node* node : young_nodes_) {
DCHECK(node->is_in_young_list());
if (node->IsWeak() && is_dead(isolate_->heap(), node->location())) {
if (!node->IsPhantomCallback() && !node->IsPhantomResetHandle()) {
node->MarkPending();
}
}
}
}
void GlobalHandles::IterateYoungWeakDeadObjectsForFinalizers(RootVisitor* v) {
for (Node* node : young_nodes_) {
DCHECK(node->is_in_young_list());
if (node->IsWeakRetainer() && (node->state() == Node::PENDING)) {
DCHECK(!node->IsPhantomCallback());
DCHECK(!node->IsPhantomResetHandle());
// Finalizers need to survive.
v->VisitRootPointer(Root::kGlobalHandles, node->label(),
node->location());
}
}
}
void GlobalHandles::IterateYoungWeakObjectsForPhantomHandles(
RootVisitor* v, WeakSlotCallbackWithHeap should_reset_handle) {
for (Node* node : young_nodes_) {
DCHECK(node->is_in_young_list());
if (node->IsWeakRetainer() && (node->state() != Node::PENDING)) {
if (should_reset_handle(isolate_->heap(), node->location())) {
DCHECK(node->IsPhantomResetHandle() || node->IsPhantomCallback());
if (node->IsPhantomResetHandle()) {
node->MarkPending();
node->ResetPhantomHandle(HandleHolder::kLive);
++number_of_phantom_handle_resets_;
} else if (node->IsPhantomCallback()) {
node->MarkPending();
node->CollectPhantomCallbackData(&regular_pending_phantom_callbacks_);
} else {
UNREACHABLE();
}
} else {
// Node survived and needs to be visited.
v->VisitRootPointer(Root::kGlobalHandles, node->label(),
node->location());
}
}
}
if (!FLAG_reclaim_unmodified_wrappers) return;
LocalEmbedderHeapTracer* const tracer =
isolate()->heap()->local_embedder_heap_tracer();
for (TracedNode* node : traced_young_nodes_) {
if (!node->IsInUse()) continue;
DCHECK_IMPLIES(node->is_root(),
!should_reset_handle(isolate_->heap(), node->location()));
if (should_reset_handle(isolate_->heap(), node->location())) {
if (node->IsPhantomResetHandle()) {
if (node->has_destructor()) {
// For handles with destructor it is guaranteed that the embedder
// memory is still alive as the destructor would have otherwise
// removed the memory.
node->ResetPhantomHandle(HandleHolder::kLive);
} else {
v8::Value* value = ToApi<v8::Value>(node->handle());
tracer->ResetHandleInNonTracingGC(
*reinterpret_cast<v8::TracedReference<v8::Value>*>(&value));
DCHECK(!node->IsInUse());
}
++number_of_phantom_handle_resets_;
} else {
node->CollectPhantomCallbackData(&traced_pending_phantom_callbacks_);
}
} else {
if (!node->is_root()) {
node->set_root(true);
v->VisitRootPointer(Root::kGlobalHandles, nullptr, node->location());
}
}
}
}
void GlobalHandles::InvokeSecondPassPhantomCallbacksFromTask() {
DCHECK(second_pass_callbacks_task_posted_);
second_pass_callbacks_task_posted_ = false;
Heap::DevToolsTraceEventScope devtools_trace_event_scope(
isolate()->heap(), "MajorGC", "invoke weak phantom callbacks");
TRACE_EVENT0("v8", "V8.GCPhantomHandleProcessingCallback");
isolate()->heap()->CallGCPrologueCallbacks(
GCType::kGCTypeProcessWeakCallbacks, kNoGCCallbackFlags);
InvokeSecondPassPhantomCallbacks();
isolate()->heap()->CallGCEpilogueCallbacks(
GCType::kGCTypeProcessWeakCallbacks, kNoGCCallbackFlags);
}
void GlobalHandles::InvokeSecondPassPhantomCallbacks() {
// The callbacks may execute JS, which in turn may lead to another GC run.
// If we are already processing the callbacks, we do not want to start over
// from within the inner GC. Newly added callbacks will always be run by the
// outermost GC run only.
if (running_second_pass_callbacks_) return;
running_second_pass_callbacks_ = true;
AllowJavascriptExecution allow_js(isolate());
while (!second_pass_callbacks_.empty()) {
auto callback = second_pass_callbacks_.back();
second_pass_callbacks_.pop_back();
callback.Invoke(isolate(), PendingPhantomCallback::kSecondPass);
}
running_second_pass_callbacks_ = false;
}
size_t GlobalHandles::PostScavengeProcessing(unsigned post_processing_count) {
size_t freed_nodes = 0;
for (Node* node : young_nodes_) {
// Filter free nodes.
if (!node->IsRetainer()) continue;
if (node->IsPending()) {
DCHECK(node->has_callback());
DCHECK(node->IsPendingFinalizer());
node->PostGarbageCollectionProcessing(isolate_);
}
if (InRecursiveGC(post_processing_count)) return freed_nodes;
if (!node->IsRetainer()) freed_nodes++;
}
return freed_nodes;
}
size_t GlobalHandles::PostMarkSweepProcessing(unsigned post_processing_count) {
size_t freed_nodes = 0;
for (Node* node : *regular_nodes_) {
// Filter free nodes.
if (!node->IsRetainer()) continue;
if (node->IsPending()) {
DCHECK(node->has_callback());
DCHECK(node->IsPendingFinalizer());
node->PostGarbageCollectionProcessing(isolate_);
}
if (InRecursiveGC(post_processing_count)) return freed_nodes;
if (!node->IsRetainer()) freed_nodes++;
}
return freed_nodes;
}
template <typename T>
void GlobalHandles::UpdateAndCompactListOfYoungNode(
std::vector<T*>* node_list) {
size_t last = 0;
for (T* node : *node_list) {
DCHECK(node->is_in_young_list());
if (node->IsInUse()) {
if (ObjectInYoungGeneration(node->object())) {
(*node_list)[last++] = node;
isolate_->heap()->IncrementNodesCopiedInNewSpace();
} else {
node->set_in_young_list(false);
isolate_->heap()->IncrementNodesPromoted();
}
} else {
node->set_in_young_list(false);
isolate_->heap()->IncrementNodesDiedInNewSpace();
}
}
DCHECK_LE(last, node_list->size());
node_list->resize(last);
node_list->shrink_to_fit();
}
void GlobalHandles::UpdateListOfYoungNodes() {
UpdateAndCompactListOfYoungNode(&young_nodes_);
UpdateAndCompactListOfYoungNode(&traced_young_nodes_);
}
template <typename T>
size_t GlobalHandles::InvokeFirstPassWeakCallbacks(
std::vector<std::pair<T*, PendingPhantomCallback>>* pending) {
size_t freed_nodes = 0;
std::vector<std::pair<T*, PendingPhantomCallback>> pending_phantom_callbacks;
pending_phantom_callbacks.swap(*pending);
{
// The initial pass callbacks must simply clear the nodes.
for (auto& pair : pending_phantom_callbacks) {
T* node = pair.first;
DCHECK_EQ(T::NEAR_DEATH, node->state());
pair.second.Invoke(isolate(), PendingPhantomCallback::kFirstPass);
// Transition to second pass. It is required that the first pass callback
// resets the handle using |v8::PersistentBase::Reset|. Also see comments
// on |v8::WeakCallbackInfo|.
CHECK_WITH_MSG(T::FREE == node->state(),
"Handle not reset in first callback. See comments on "
"|v8::WeakCallbackInfo|.");
if (pair.second.callback()) second_pass_callbacks_.push_back(pair.second);
freed_nodes++;
}
}
return freed_nodes;
}
size_t GlobalHandles::InvokeFirstPassWeakCallbacks() {
return InvokeFirstPassWeakCallbacks(&regular_pending_phantom_callbacks_) +
InvokeFirstPassWeakCallbacks(&traced_pending_phantom_callbacks_);
}
void GlobalHandles::InvokeOrScheduleSecondPassPhantomCallbacks(
bool synchronous_second_pass) {
if (!second_pass_callbacks_.empty()) {
if (FLAG_optimize_for_size || FLAG_predictable || synchronous_second_pass) {
Heap::DevToolsTraceEventScope devtools_trace_event_scope(
isolate()->heap(), "MajorGC", "invoke weak phantom callbacks");
isolate()->heap()->CallGCPrologueCallbacks(
GCType::kGCTypeProcessWeakCallbacks, kNoGCCallbackFlags);
InvokeSecondPassPhantomCallbacks();
isolate()->heap()->CallGCEpilogueCallbacks(
GCType::kGCTypeProcessWeakCallbacks, kNoGCCallbackFlags);
} else if (!second_pass_callbacks_task_posted_) {
second_pass_callbacks_task_posted_ = true;
auto taskrunner = V8::GetCurrentPlatform()->GetForegroundTaskRunner(
reinterpret_cast<v8::Isolate*>(isolate()));
taskrunner->PostTask(MakeCancelableTask(
isolate(), [this] { InvokeSecondPassPhantomCallbacksFromTask(); }));
}
}
}
void GlobalHandles::PendingPhantomCallback::Invoke(Isolate* isolate,
InvocationType type) {
Data::Callback* callback_addr = nullptr;
if (type == kFirstPass) {
callback_addr = &callback_;
}
Data data(reinterpret_cast<v8::Isolate*>(isolate), parameter_,
embedder_fields_, callback_addr);
Data::Callback callback = callback_;
callback_ = nullptr;
callback(data);
}
bool GlobalHandles::InRecursiveGC(unsigned gc_processing_counter) {
return gc_processing_counter != post_gc_processing_count_;
}
size_t GlobalHandles::PostGarbageCollectionProcessing(
GarbageCollector collector, const v8::GCCallbackFlags gc_callback_flags) {
// Process weak global handle callbacks. This must be done after the
// GC is completely done, because the callbacks may invoke arbitrary
// API functions.
DCHECK_EQ(Heap::NOT_IN_GC, isolate_->heap()->gc_state());
const unsigned post_processing_count = ++post_gc_processing_count_;
size_t freed_nodes = 0;
bool synchronous_second_pass =
isolate_->heap()->IsTearingDown() ||
(gc_callback_flags &
(kGCCallbackFlagForced | kGCCallbackFlagCollectAllAvailableGarbage |
kGCCallbackFlagSynchronousPhantomCallbackProcessing)) != 0;
InvokeOrScheduleSecondPassPhantomCallbacks(synchronous_second_pass);
if (InRecursiveGC(post_processing_count)) return freed_nodes;
freed_nodes += Heap::IsYoungGenerationCollector(collector)
? PostScavengeProcessing(post_processing_count)
: PostMarkSweepProcessing(post_processing_count);
if (InRecursiveGC(post_processing_count)) return freed_nodes;
UpdateListOfYoungNodes();
return freed_nodes;
}
void GlobalHandles::IterateStrongRoots(RootVisitor* v) {
for (Node* node : *regular_nodes_) {
if (node->IsStrongRetainer()) {
v->VisitRootPointer(Root::kGlobalHandles, node->label(),
node->location());
}
}
}
void GlobalHandles::IterateStrongStackRoots(RootVisitor* v) {
on_stack_nodes_->Iterate(v);
}
void GlobalHandles::IterateWeakRoots(RootVisitor* v) {
for (Node* node : *regular_nodes_) {
if (node->IsWeak()) {
v->VisitRootPointer(Root::kGlobalHandles, node->label(),
node->location());
}
}
for (TracedNode* node : *traced_nodes_) {
if (node->IsInUse()) {
v->VisitRootPointer(Root::kGlobalHandles, nullptr, node->location());
}
}
}
DISABLE_CFI_PERF
void GlobalHandles::IterateAllRoots(RootVisitor* v) {
for (Node* node : *regular_nodes_) {
if (node->IsRetainer()) {
v->VisitRootPointer(Root::kGlobalHandles, node->label(),
node->location());
}
}
for (TracedNode* node : *traced_nodes_) {
if (node->IsRetainer()) {
v->VisitRootPointer(Root::kGlobalHandles, nullptr, node->location());
}
}
on_stack_nodes_->Iterate(v);
}
DISABLE_CFI_PERF
void GlobalHandles::IterateAllYoungRoots(RootVisitor* v) {
for (Node* node : young_nodes_) {
if (node->IsRetainer()) {
v->VisitRootPointer(Root::kGlobalHandles, node->label(),
node->location());
}
}
for (TracedNode* node : traced_young_nodes_) {
if (node->IsRetainer()) {
v->VisitRootPointer(Root::kGlobalHandles, nullptr, node->location());
}
}
on_stack_nodes_->Iterate(v);
}
DISABLE_CFI_PERF
void GlobalHandles::ApplyPersistentHandleVisitor(
v8::PersistentHandleVisitor* visitor, GlobalHandles::Node* node) {
v8::Value* value = ToApi<v8::Value>(node->handle());
visitor->VisitPersistentHandle(
reinterpret_cast<v8::Persistent<v8::Value>*>(&value),
node->wrapper_class_id());
}
DISABLE_CFI_PERF
void GlobalHandles::IterateAllRootsWithClassIds(
v8::PersistentHandleVisitor* visitor) {
for (Node* node : *regular_nodes_) {
if (node->IsRetainer() && node->has_wrapper_class_id()) {
ApplyPersistentHandleVisitor(visitor, node);
}
}
}
DISABLE_CFI_PERF
void GlobalHandles::IterateTracedNodes(
v8::EmbedderHeapTracer::TracedGlobalHandleVisitor* visitor) {
for (TracedNode* node : *traced_nodes_) {
if (node->IsInUse()) {
v8::Value* value = ToApi<v8::Value>(node->handle());
if (node->has_destructor()) {
visitor->VisitTracedGlobalHandle(
*reinterpret_cast<v8::TracedGlobal<v8::Value>*>(&value));
} else {
visitor->VisitTracedReference(
*reinterpret_cast<v8::TracedReference<v8::Value>*>(&value));
}
}
}
}
DISABLE_CFI_PERF
void GlobalHandles::IterateAllYoungRootsWithClassIds(
v8::PersistentHandleVisitor* visitor) {
for (Node* node : young_nodes_) {
if (node->IsRetainer() && node->has_wrapper_class_id()) {
ApplyPersistentHandleVisitor(visitor, node);
}
}
}
DISABLE_CFI_PERF
void GlobalHandles::IterateYoungWeakRootsWithClassIds(
v8::PersistentHandleVisitor* visitor) {
for (Node* node : young_nodes_) {
if (node->has_wrapper_class_id() && node->IsWeak()) {
ApplyPersistentHandleVisitor(visitor, node);
}
}
}
void GlobalHandles::RecordStats(HeapStats* stats) {
*stats->global_handle_count = 0;
*stats->weak_global_handle_count = 0;
*stats->pending_global_handle_count = 0;
*stats->near_death_global_handle_count = 0;
*stats->free_global_handle_count = 0;
for (Node* node : *regular_nodes_) {
*stats->global_handle_count += 1;
if (node->state() == Node::WEAK) {
*stats->weak_global_handle_count += 1;
} else if (node->state() == Node::PENDING) {
*stats->pending_global_handle_count += 1;
} else if (node->state() == Node::NEAR_DEATH) {
*stats->near_death_global_handle_count += 1;
} else if (node->state() == Node::FREE) {
*stats->free_global_handle_count += 1;
}
}
}
#ifdef DEBUG
void GlobalHandles::PrintStats() {
int total = 0;
int weak = 0;
int pending = 0;
int near_death = 0;
int destroyed = 0;
for (Node* node : *regular_nodes_) {
total++;
if (node->state() == Node::WEAK) weak++;
if (node->state() == Node::PENDING) pending++;
if (node->state() == Node::NEAR_DEATH) near_death++;
if (node->state() == Node::FREE) destroyed++;
}
PrintF("Global Handle Statistics:\n");
PrintF(" allocated memory = %zuB\n", total * sizeof(Node));
PrintF(" # weak = %d\n", weak);
PrintF(" # pending = %d\n", pending);
PrintF(" # near_death = %d\n", near_death);
PrintF(" # free = %d\n", destroyed);
PrintF(" # total = %d\n", total);
}
void GlobalHandles::Print() {
PrintF("Global handles:\n");
for (Node* node : *regular_nodes_) {
PrintF(" handle %p to %p%s\n", node->location().ToVoidPtr(),
reinterpret_cast<void*>(node->object().ptr()),
node->IsWeak() ? " (weak)" : "");
}
}
#endif
EternalHandles::~EternalHandles() {
for (Address* block : blocks_) delete[] block;
}
void EternalHandles::IterateAllRoots(RootVisitor* visitor) {
int limit = size_;
for (Address* block : blocks_) {
DCHECK_GT(limit, 0);
visitor->VisitRootPointers(Root::kEternalHandles, nullptr,
FullObjectSlot(block),
FullObjectSlot(block + Min(limit, kSize)));
limit -= kSize;
}
}
void EternalHandles::IterateYoungRoots(RootVisitor* visitor) {
for (int index : young_node_indices_) {
visitor->VisitRootPointer(Root::kEternalHandles, nullptr,
FullObjectSlot(GetLocation(index)));
}
}
void EternalHandles::PostGarbageCollectionProcessing() {
size_t last = 0;
for (int index : young_node_indices_) {
if (ObjectInYoungGeneration(Object(*GetLocation(index)))) {
young_node_indices_[last++] = index;
}
}
DCHECK_LE(last, young_node_indices_.size());
young_node_indices_.resize(last);
}
void EternalHandles::Create(Isolate* isolate, Object object, int* index) {
DCHECK_EQ(kInvalidIndex, *index);
if (object == Object()) return;
Object the_hole = ReadOnlyRoots(isolate).the_hole_value();
DCHECK_NE(the_hole, object);
int block = size_ >> kShift;
int offset = size_ & kMask;
// Need to resize.
if (offset == 0) {
Address* next_block = new Address[kSize];
MemsetPointer(FullObjectSlot(next_block), the_hole, kSize);
blocks_.push_back(next_block);
}
DCHECK_EQ(the_hole.ptr(), blocks_[block][offset]);
blocks_[block][offset] = object.ptr();
if (ObjectInYoungGeneration(object)) {
young_node_indices_.push_back(size_);
}
*index = size_++;
}
} // namespace internal
} // namespace v8