Commit edef7f13 authored by Michael Lippautz's avatar Michael Lippautz Committed by Commit Bot

[api,global-handle] Introduce TracedGlobal::SetFinalizationCallback

Introduce a way to set a custom finalization callback that can be used
to signal and set up destruction of embedder memory.

Bug: chromium:923361
Change-Id: Ifc62ebd534aba3b02511c74b59161ec3edc0ee0d
Reviewed-on: https://chromium-review.googlesource.com/c/1452447
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59381}
parent 8408dbaf
......@@ -941,6 +941,19 @@ class V8_EXPORT TracedGlobal {
*/
V8_INLINE uint16_t WrapperClassId() const;
/**
* Adds a finalization callback to the handle. The type of this callback is
* similar to WeakCallbackType::kInternalFields, i.e., it will pass the
* parameter and the first two internal fields of the object.
*
* The callback is then supposed to reset the handle in the callback. No
* further V8 API may be called in this callback. In case additional work
* involving V8 needs to be done, a second callback can be scheduled using
* WeakCallbackInfo<void>::SetSecondPassCallback.
*/
V8_INLINE void SetFinalizationCallback(
void* parameter, WeakCallbackInfo<void>::Callback callback);
private:
V8_INLINE static T* New(Isolate* isolate, T* that, T** slot);
......@@ -8681,6 +8694,9 @@ class V8_EXPORT V8 {
WeakCallbackType type);
static void MakeWeak(internal::Address** location_addr);
static void* ClearWeak(internal::Address* location);
static void SetFinalizationCallbackTraced(
internal::Address* location, void* parameter,
WeakCallbackInfo<void>::Callback callback);
static void AnnotateStrongRetainer(internal::Address* location,
const char* label);
static Value* Eternalize(Isolate* isolate, Value* handle);
......@@ -9858,6 +9874,13 @@ uint16_t TracedGlobal<T>::WrapperClassId() const {
return *reinterpret_cast<uint16_t*>(addr);
}
template <class T>
void TracedGlobal<T>::SetFinalizationCallback(
void* parameter, typename WeakCallbackInfo<void>::Callback callback) {
V8::SetFinalizationCallbackTraced(
reinterpret_cast<internal::Address*>(this->val_), parameter, callback);
}
template <typename T>
ReturnValue<T>::ReturnValue(internal::Address* slot) : value_(slot) {}
......
......@@ -1074,6 +1074,13 @@ void V8::DisposeTracedGlobal(internal::Address* location) {
i::GlobalHandles::DestroyTraced(location);
}
void V8::SetFinalizationCallbackTraced(
internal::Address* location, void* parameter,
WeakCallbackInfo<void>::Callback callback) {
i::GlobalHandles::SetFinalizationCallbackForTraced(location, parameter,
callback);
}
Value* V8::Eternalize(Isolate* v8_isolate, Value* value) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
i::Object object = *Utils::OpenHandle(value);
......
......@@ -345,6 +345,21 @@ class NodeBase {
} data_;
};
namespace {
void ExtractInternalFields(JSObject jsobject, void** embedder_fields, int len) {
int field_count = jsobject->GetEmbedderFieldCount();
for (int i = 0; i < len; ++i) {
if (field_count == i) break;
void* pointer;
if (EmbedderDataSlot(jsobject, i).ToAlignedPointer(&pointer)) {
embedder_fields[i] = pointer;
}
}
}
} // namespace
class GlobalHandles::Node final : public NodeBase<GlobalHandles::Node> {
public:
// State transition diagram:
......@@ -519,7 +534,8 @@ class GlobalHandles::Node final : public NodeBase<GlobalHandles::Node> {
}
void CollectPhantomCallbackData(
std::vector<PendingPhantomCallback>* pending_phantom_callbacks) {
std::vector<std::pair<Node*, PendingPhantomCallback>>*
pending_phantom_callbacks) {
DCHECK(weakness_type() == PHANTOM_WEAK ||
weakness_type() == PHANTOM_WEAK_2_EMBEDDER_FIELDS);
DCHECK(state() == PENDING);
......@@ -528,29 +544,23 @@ class GlobalHandles::Node final : public NodeBase<GlobalHandles::Node> {
void* embedder_fields[v8::kEmbedderFieldsInWeakCallback] = {nullptr,
nullptr};
if (weakness_type() != PHANTOM_WEAK && object()->IsJSObject()) {
JSObject jsobject = JSObject::cast(object());
int field_count = jsobject->GetEmbedderFieldCount();
for (int i = 0; i < v8::kEmbedderFieldsInWeakCallback; ++i) {
if (field_count == i) break;
void* pointer;
if (EmbedderDataSlot(jsobject, i).ToAlignedPointer(&pointer)) {
embedder_fields[i] = pointer;
}
}
ExtractInternalFields(JSObject::cast(object()), embedder_fields,
v8::kEmbedderFieldsInWeakCallback);
}
// Zap with something dangerous.
location().store(Object(0x6057CA11));
location().store(Object(0xCA11));
pending_phantom_callbacks->push_back(PendingPhantomCallback(
this, weak_callback_, parameter(), embedder_fields));
pending_phantom_callbacks->push_back(std::make_pair(
this,
PendingPhantomCallback(weak_callback_, parameter(), embedder_fields)));
DCHECK(IsInUse());
set_state(NEAR_DEATH);
}
void ResetPhantomHandle() {
DCHECK(weakness_type() == PHANTOM_WEAK_RESET_HANDLE);
DCHECK(state() == PENDING);
DCHECK_EQ(PHANTOM_WEAK_RESET_HANDLE, weakness_type());
DCHECK_EQ(PENDING, state());
DCHECK_NULL(weak_callback_);
Address** handle = reinterpret_cast<Address**>(parameter());
*handle = nullptr;
......@@ -626,7 +636,7 @@ class GlobalHandles::TracedNode final
public:
TracedNode() { set_in_new_space_list(false); }
enum State { FREE = 0, NORMAL };
enum State { FREE = 0, NORMAL, NEAR_DEATH };
State state() const { return NodeState::decode(flags_); }
void set_state(State state) { flags_ = NodeState::update(flags_, state); }
......@@ -634,6 +644,8 @@ class GlobalHandles::TracedNode final
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_new_space_list() const { return IsInNewSpaceList::decode(flags_); }
void set_in_new_space_list(bool v) {
......@@ -643,6 +655,31 @@ class GlobalHandles::TracedNode final
bool is_root() const { return IsRoot::decode(flags_); }
void set_root(bool v) { flags_ = IsRoot::update(flags_, v); }
void SetFinalizationCallback(void* parameter,
WeakCallbackInfo<void>::Callback callback) {
set_parameter(parameter);
callback_ = callback;
}
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() {
DCHECK(IsInUse());
Address** handle = reinterpret_cast<Address**>(data_.parameter);
......@@ -652,12 +689,21 @@ class GlobalHandles::TracedNode final
}
protected:
class NodeState : public BitField8<State, 0, 1> {};
class NodeState : public BitField8<State, 0, 2> {};
class IsInNewSpaceList : public BitField8<bool, NodeState::kNext, 1> {};
class IsRoot : public BitField8<bool, IsInNewSpaceList::kNext, 1> {};
void ClearImplFields() { set_root(true); }
void CheckImplFieldsAreCleared() const { DCHECK(is_root()); }
void ClearImplFields() {
set_root(true);
callback_ = nullptr;
}
void CheckImplFieldsAreCleared() const {
DCHECK(is_root());
DCHECK_NULL(callback_);
}
WeakCallbackInfo<void>::Callback callback_;
friend class NodeBase<GlobalHandles::TracedNode>;
......@@ -744,6 +790,13 @@ void GlobalHandles::DestroyTraced(Address* location) {
}
}
void GlobalHandles::SetFinalizationCallbackForTraced(
Address* location, void* parameter,
WeakCallbackInfo<void>::Callback callback) {
TracedNode::FromLocation(location)->SetFinalizationCallback(parameter,
callback);
}
typedef v8::WeakCallbackInfo<void>::Callback GenericCallback;
void GlobalHandles::MakeWeak(Address* location, void* parameter,
......@@ -798,15 +851,19 @@ void GlobalHandles::IterateWeakRootsForPhantomHandles(
++number_of_phantom_handle_resets_;
} else if (node->IsPhantomCallback()) {
node->MarkPending();
node->CollectPhantomCallbackData(&pending_phantom_callbacks_);
node->CollectPhantomCallbackData(&regular_pending_phantom_callbacks_);
}
}
}
for (TracedNode* node : *traced_nodes_) {
if (node->IsInUse() &&
should_reset_handle(isolate()->heap(), node->location())) {
if (node->IsPhantomResetHandle()) {
node->ResetPhantomHandle();
++number_of_phantom_handle_resets_;
} else {
node->CollectPhantomCallbackData(&traced_pending_phantom_callbacks_);
}
}
}
}
......@@ -903,7 +960,7 @@ void GlobalHandles::IterateNewSpaceWeakUnmodifiedRootsForPhantomHandles(
++number_of_phantom_handle_resets_;
} else if (node->IsPhantomCallback()) {
node->MarkPending();
node->CollectPhantomCallbackData(&pending_phantom_callbacks_);
node->CollectPhantomCallbackData(&regular_pending_phantom_callbacks_);
} else {
UNREACHABLE();
}
......@@ -920,8 +977,12 @@ void GlobalHandles::IterateNewSpaceWeakUnmodifiedRootsForPhantomHandles(
DCHECK_IMPLIES(node->is_root(),
!should_reset_handle(isolate_->heap(), node->location()));
if (should_reset_handle(isolate_->heap(), node->location())) {
if (node->IsPhantomResetHandle()) {
node->ResetPhantomHandle();
++number_of_phantom_handle_resets_;
} else {
node->CollectPhantomCallbackData(&traced_pending_phantom_callbacks_);
}
} else {
if (!node->is_root()) {
node->set_root(true);
......@@ -946,9 +1007,7 @@ void GlobalHandles::InvokeSecondPassPhantomCallbacks() {
while (!second_pass_callbacks_.empty()) {
auto callback = second_pass_callbacks_.back();
second_pass_callbacks_.pop_back();
DCHECK_NULL(callback.node());
// Fire second pass callback
callback.Invoke(isolate());
callback.Invoke(isolate(), PendingPhantomCallback::kSecondPass);
}
}
......@@ -1023,23 +1082,38 @@ void GlobalHandles::UpdateListOfNewSpaceNodes() {
UpdateAndCompactListOfNewSpaceNode(&traced_new_space_nodes_);
}
size_t GlobalHandles::InvokeFirstPassWeakCallbacks() {
template <typename T>
size_t GlobalHandles::InvokeFirstPassWeakCallbacks(
std::vector<std::pair<T*, PendingPhantomCallback>>* pending) {
size_t freed_nodes = 0;
std::vector<PendingPhantomCallback> pending_phantom_callbacks;
pending_phantom_callbacks.swap(pending_phantom_callbacks_);
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 callback : pending_phantom_callbacks) {
// Skip callbacks that have already been processed once.
if (callback.node() == nullptr) continue;
callback.Invoke(isolate());
if (callback.callback()) second_pass_callbacks_.push_back(callback);
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()) {
......@@ -1059,11 +1133,10 @@ void GlobalHandles::InvokeOrScheduleSecondPassPhantomCallbacks(
}
}
void GlobalHandles::PendingPhantomCallback::Invoke(Isolate* isolate) {
void GlobalHandles::PendingPhantomCallback::Invoke(Isolate* isolate,
InvocationType type) {
Data::Callback* callback_addr = nullptr;
if (node_ != nullptr) {
// Initialize for first pass callback.
DCHECK(node_->state() == Node::NEAR_DEATH);
if (type == kFirstPass) {
callback_addr = &callback_;
}
Data data(reinterpret_cast<v8::Isolate*>(isolate), parameter_,
......@@ -1071,15 +1144,6 @@ void GlobalHandles::PendingPhantomCallback::Invoke(Isolate* isolate) {
Data::Callback callback = callback_;
callback_ = nullptr;
callback(data);
if (node_ != nullptr) {
// 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(Node::FREE == node_->state(),
"Handle not reset in first callback. See comments on "
"|v8::WeakCallbackInfo|.");
node_ = nullptr;
}
}
bool GlobalHandles::InRecursiveGC(unsigned gc_processing_counter) {
......@@ -1143,7 +1207,7 @@ void GlobalHandles::IterateAllRoots(RootVisitor* v) {
}
}
for (TracedNode* node : *traced_nodes_) {
if (node->IsInUse()) {
if (node->IsRetainer()) {
v->VisitRootPointer(Root::kGlobalHandles, nullptr, node->location());
}
}
......@@ -1158,7 +1222,7 @@ void GlobalHandles::IterateAllNewSpaceRoots(RootVisitor* v) {
}
}
for (TracedNode* node : traced_new_space_nodes_) {
if (node->IsInUse()) {
if (node->IsRetainer()) {
v->VisitRootPointer(Root::kGlobalHandles, nullptr, node->location());
}
}
......
......@@ -6,6 +6,7 @@
#define V8_GLOBAL_HANDLES_H_
#include <type_traits>
#include <utility>
#include <vector>
#include "include/v8.h"
......@@ -43,17 +44,16 @@ class GlobalHandles final {
template <class NodeType>
class NodeBlock;
// Move a global handle.
static void MoveGlobal(Address** from, Address** to);
static void MoveTracedGlobal(Address** from, Address** to);
//
// API for regular handles.
//
// Destroy a global handle.
static void Destroy(Address* location);
static void DestroyTraced(Address* location);
static void MoveGlobal(Address** from, Address** to);
// Copy a global handle.
static Handle<Object> CopyGlobal(Address* location);
static void Destroy(Address* location);
// Make the global handle weak and set the callback parameter for the
// handle. When the garbage collector recognizes that only weak global
// handles point to an object the callback function is invoked (for each
......@@ -66,7 +66,6 @@ class GlobalHandles final {
static void MakeWeak(Address* location, void* parameter,
WeakCallbackInfo<void>::Callback weak_callback,
v8::WeakCallbackType type);
static void MakeWeak(Address** location_addr);
static void AnnotateStrongRetainer(Address* location, const char* label);
......@@ -80,6 +79,16 @@ class GlobalHandles final {
// Tells whether global handle is weak.
static bool IsWeak(Address* location);
//
// API for traced handles.
//
static void MoveTracedGlobal(Address** from, Address** to);
static void DestroyTraced(Address* location);
static void SetFinalizationCallbackForTraced(
Address* location, void* parameter,
WeakCallbackInfo<void>::Callback callback);
explicit GlobalHandles(Isolate* isolate);
~GlobalHandles();
......@@ -195,6 +204,10 @@ class GlobalHandles final {
size_t PostScavengeProcessing(unsigned post_processing_count);
size_t PostMarkSweepProcessing(unsigned post_processing_count);
template <typename T>
size_t InvokeFirstPassWeakCallbacks(
std::vector<std::pair<T*, PendingPhantomCallback>>* pending);
template <typename T>
void UpdateAndCompactListOfNewSpaceNode(std::vector<T*>* node_list);
void UpdateListOfNewSpaceNodes();
......@@ -216,7 +229,10 @@ class GlobalHandles final {
size_t handles_count_ = 0;
size_t number_of_phantom_handle_resets_ = 0;
std::vector<PendingPhantomCallback> pending_phantom_callbacks_;
std::vector<std::pair<Node*, PendingPhantomCallback>>
regular_pending_phantom_callbacks_;
std::vector<std::pair<TracedNode*, PendingPhantomCallback>>
traced_pending_phantom_callbacks_;
std::vector<PendingPhantomCallback> second_pass_callbacks_;
bool second_pass_callbacks_task_posted_ = false;
......@@ -229,22 +245,23 @@ class GlobalHandles final {
class GlobalHandles::PendingPhantomCallback final {
public:
typedef v8::WeakCallbackInfo<void> Data;
enum InvocationType { kFirstPass, kSecondPass };
PendingPhantomCallback(
Node* node, Data::Callback callback, void* parameter,
Data::Callback callback, void* parameter,
void* embedder_fields[v8::kEmbedderFieldsInWeakCallback])
: node_(node), callback_(callback), parameter_(parameter) {
: callback_(callback), parameter_(parameter) {
for (int i = 0; i < v8::kEmbedderFieldsInWeakCallback; ++i) {
embedder_fields_[i] = embedder_fields[i];
}
}
void Invoke(Isolate* isolate);
void Invoke(Isolate* isolate, InvocationType type);
Node* node() const { return node_; }
Data::Callback callback() const { return callback_; }
private:
Node* node_;
Data::Callback callback_;
void* parameter_;
void* embedder_fields_[v8::kEmbedderFieldsInWeakCallback];
......
......@@ -488,6 +488,63 @@ TEST(TracedGlobalIteration) {
CHECK_EQ(1, visitor.count());
}
namespace {
void FinalizationCallback(const WeakCallbackInfo<void>& data) {
v8::TracedGlobal<v8::Object>* traced =
reinterpret_cast<v8::TracedGlobal<v8::Object>*>(data.GetParameter());
CHECK_EQ(reinterpret_cast<void*>(0x4), data.GetInternalField(0));
CHECK_EQ(reinterpret_cast<void*>(0x8), data.GetInternalField(1));
traced->Reset();
}
} // namespace
TEST(TracedGlobalSetFinalizationCallbackScavenge) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
TestEmbedderHeapTracer tracer;
tracer.ConsiderTracedGlobalAsRoot(false);
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
v8::TracedGlobal<v8::Object> traced;
ConstructJSApiObject(isolate, isolate->GetCurrentContext(), &traced);
CHECK(!traced.IsEmpty());
{
v8::HandleScope scope(isolate);
auto local = traced.Get(isolate);
local->SetAlignedPointerInInternalField(0, reinterpret_cast<void*>(0x4));
local->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(0x8));
}
traced.SetFinalizationCallback(&traced, FinalizationCallback);
heap::InvokeScavenge();
CHECK(traced.IsEmpty());
}
TEST(TracedGlobalSetFinalizationCallbackMarkSweep) {
ManualGCScope manual_gc;
CcTest::InitializeVM();
v8::Isolate* isolate = CcTest::isolate();
v8::HandleScope scope(isolate);
TestEmbedderHeapTracer tracer;
TemporaryEmbedderHeapTracerScope tracer_scope(isolate, &tracer);
v8::TracedGlobal<v8::Object> traced;
ConstructJSApiObject(isolate, isolate->GetCurrentContext(), &traced);
CHECK(!traced.IsEmpty());
{
v8::HandleScope scope(isolate);
auto local = traced.Get(isolate);
local->SetAlignedPointerInInternalField(0, reinterpret_cast<void*>(0x4));
local->SetAlignedPointerInInternalField(1, reinterpret_cast<void*>(0x8));
}
traced.SetFinalizationCallback(&traced, FinalizationCallback);
heap::InvokeMarkSweep();
CHECK(traced.IsEmpty());
}
} // namespace heap
} // namespace internal
} // namespace v8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment