diff --git a/src/aliased_buffer.h b/src/aliased_buffer.h index 134685542d4a75..53011c5fe0af25 100644 --- a/src/aliased_buffer.h +++ b/src/aliased_buffer.h @@ -198,6 +198,11 @@ class AliasedBufferBase { return js_array_.Get(isolate_); } + void Release() { + DCHECK_NULL(index_); + js_array_.Reset(); + } + /** * Get the underlying v8::ArrayBuffer underlying the TypedArray and * overlaying the native buffer diff --git a/src/base_object.h b/src/base_object.h index 3eac795e783e9d..6482bd85f86a56 100644 --- a/src/base_object.h +++ b/src/base_object.h @@ -158,6 +158,9 @@ class BaseObject : public MemoryRetainer { virtual inline void OnGCCollect(); + bool is_snapshotable() const { return is_snapshotable_; } + void set_is_snapshotable(bool val) { is_snapshotable_ = val; } + private: v8::Local WrappedObject() const override; bool IsRootNode() const override; @@ -206,6 +209,7 @@ class BaseObject : public MemoryRetainer { Environment* env_; PointerData* pointer_data_ = nullptr; + bool is_snapshotable_ = false; }; // Global alias for FromJSObject() to avoid churn. diff --git a/src/env-inl.h b/src/env-inl.h index 93d2423580a88b..91e70908f1e594 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -1089,6 +1089,17 @@ void Environment::ForEachBaseObject(T&& iterator) { } } +template +void Environment::ForEachBindingData(T&& iterator) { + BindingDataStore* map = static_cast( + context()->GetAlignedPointerFromEmbedderData( + ContextEmbedderIndex::kBindingListIndex)); + DCHECK_NOT_NULL(map); + for (auto& it : *map) { + iterator(it.first, it.second); + } +} + void Environment::modify_base_object_count(int64_t delta) { base_object_count_ += delta; } diff --git a/src/env.cc b/src/env.cc index 49d8ffd31308a0..119706129713bd 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1261,6 +1261,7 @@ EnvSerializeInfo Environment::Serialize(SnapshotCreator* creator) { EnvSerializeInfo info; Local ctx = context(); + SerializeBindingData(this, creator, &info); // Currently all modules are compiled without cache in builtin snapshot // builder. info.native_modules = std::vector( @@ -1327,6 +1328,9 @@ std::ostream& operator<<(std::ostream& output, std::ostream& operator<<(std::ostream& output, const EnvSerializeInfo& i) { output << "{\n" + << "// -- bindings begins --\n" + << i.bindings << ",\n" + << "// -- bindings ends --\n" << "// -- native_modules begins --\n" << i.native_modules << ",\n" << "// -- native_modules ends --\n" @@ -1352,9 +1356,33 @@ std::ostream& operator<<(std::ostream& output, const EnvSerializeInfo& i) { return output; } +void Environment::EnqueueDeserializeRequest(DeserializeRequestCallback cb, + Local holder, + int index, + InternalFieldInfo* info) { + DeserializeRequest request{cb, {isolate(), holder}, index, info}; + deserialize_requests_.push_back(std::move(request)); +} + +void Environment::RunDeserializeRequests() { + HandleScope scope(isolate()); + Local ctx = context(); + Isolate* is = isolate(); + while (!deserialize_requests_.empty()) { + DeserializeRequest request(std::move(deserialize_requests_.front())); + deserialize_requests_.pop_front(); + Local holder = request.holder.Get(is); + request.cb(ctx, holder, request.index, request.info); + request.holder.Reset(); + request.info->Delete(); + } +} + void Environment::DeserializeProperties(const EnvSerializeInfo* info) { Local ctx = context(); + RunDeserializeRequests(); + native_modules_in_snapshot = info->native_modules; async_hooks_.Deserialize(ctx); immediate_info_.Deserialize(ctx); diff --git a/src/env.h b/src/env.h index f3bb8f51dda562..fa61450d0945d5 100644 --- a/src/env.h +++ b/src/env.h @@ -37,6 +37,7 @@ #include "node_main_instance.h" #include "node_options.h" #include "node_perf_common.h" +#include "node_snapshotable.h" #include "req_wrap.h" #include "util.h" #include "uv.h" @@ -899,7 +900,22 @@ struct PropInfo { SnapshotIndex index; // In the snapshot }; +typedef void (*DeserializeRequestCallback)(v8::Local context, + v8::Local holder, + int index, + InternalFieldInfo* info); +struct DeserializeRequest { + DeserializeRequestCallback cb; + v8::Global holder; + int index; + InternalFieldInfo* info = nullptr; // Owned by the request + + // Move constructor + DeserializeRequest(DeserializeRequest&& other) = default; +}; + struct EnvSerializeInfo { + std::vector bindings; std::vector native_modules; AsyncHooks::SerializeInfo async_hooks; TickInfo::SerializeInfo tick_info; @@ -934,6 +950,11 @@ class Environment : public MemoryRetainer { void PrintAllBaseObjects(); void VerifyNoStrongBaseObjects(); + void EnqueueDeserializeRequest(DeserializeRequestCallback cb, + v8::Local holder, + int index, + InternalFieldInfo* info); + void RunDeserializeRequests(); // Should be called before InitializeInspector() void InitializeDiagnostics(); @@ -1374,6 +1395,9 @@ class Environment : public MemoryRetainer { void AddUnmanagedFd(int fd); void RemoveUnmanagedFd(int fd); + template + void ForEachBindingData(T&& iterator); + private: inline void ThrowError(v8::Local (*fun)(v8::Local), const char* errmsg); @@ -1462,6 +1486,8 @@ class Environment : public MemoryRetainer { bool is_in_inspector_console_call_ = false; #endif + std::list deserialize_requests_; + // handle_wrap_queue_ and req_wrap_queue_ needs to be at a fixed offset from // the start of the class because it is used by // src/node_postmortem_metadata.cc to calculate offsets and generate debug diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 21de10868564d3..04ae6e26131b4e 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -1,39 +1,134 @@ #include "node_snapshotable.h" #include "base_object-inl.h" +#include "debug_utils-inl.h" +#include "node_file.h" +#include "node_v8.h" namespace node { using v8::Local; using v8::Object; +using v8::SnapshotCreator; using v8::StartupData; +SnapshotableObject::SnapshotableObject(Environment* env, + Local wrap, + EmbedderObjectType type) + : BaseObject(env, wrap), type_(type) { + set_is_snapshotable(true); +} + +const char* SnapshotableObject::GetTypeNameChars() const { + switch (type_) { +#define V(PropertyName, NativeTypeName) \ + case EmbedderObjectType::k_##PropertyName: { \ + return NativeTypeName::type_name.c_str(); \ + } + SERIALIZABLE_OBJECT_TYPES(V) +#undef V + default: { UNREACHABLE(); } + } +} + +bool IsSnapshotableType(FastStringKey key) { +#define V(PropertyName, NativeTypeName) \ + if (key == NativeTypeName::type_name) { \ + return true; \ + } + SERIALIZABLE_OBJECT_TYPES(V) +#undef V + + return false; +} + void DeserializeNodeInternalFields(Local holder, int index, - v8::StartupData payload, + StartupData payload, void* env) { + per_process::Debug(DebugCategory::MKSNAPSHOT, + "Deserialize internal field %d of %p, size=%d\n", + static_cast(index), + (*holder), + static_cast(payload.raw_size)); if (payload.raw_size == 0) { holder->SetAlignedPointerInInternalField(index, nullptr); return; } - // No embedder object in the builtin snapshot yet. - UNREACHABLE(); + + Environment* env_ptr = static_cast(env); + const InternalFieldInfo* info = + reinterpret_cast(payload.data); + + switch (info->type) { +#define V(PropertyName, NativeTypeName) \ + case EmbedderObjectType::k_##PropertyName: { \ + per_process::Debug(DebugCategory::MKSNAPSHOT, \ + "Object %p is %s\n", \ + (*holder), \ + NativeTypeName::type_name.c_str()); \ + env_ptr->EnqueueDeserializeRequest( \ + NativeTypeName::Deserialize, holder, index, info->Copy()); \ + break; \ + } + SERIALIZABLE_OBJECT_TYPES(V) +#undef V + default: { UNREACHABLE(); } + } } StartupData SerializeNodeContextInternalFields(Local holder, int index, void* env) { - void* ptr = holder->GetAlignedPointerFromInternalField(index); - if (ptr == nullptr || ptr == env) { - return StartupData{nullptr, 0}; - } - if (ptr == env && index == ContextEmbedderIndex::kEnvironment) { + per_process::Debug(DebugCategory::MKSNAPSHOT, + "Serialize internal field, index=%d, holder=%p\n", + static_cast(index), + *holder); + void* ptr = holder->GetAlignedPointerFromInternalField(BaseObject::kSlot); + if (ptr == nullptr) { return StartupData{nullptr, 0}; } - // No embedder objects in the builtin snapshot yet. - UNREACHABLE(); - return StartupData{nullptr, 0}; + DCHECK(static_cast(ptr)->is_snapshotable()); + SnapshotableObject* obj = static_cast(ptr); + per_process::Debug(DebugCategory::MKSNAPSHOT, + "Object %p is %s, ", + *holder, + obj->GetTypeNameChars()); + InternalFieldInfo* info = obj->Serialize(index); + per_process::Debug(DebugCategory::MKSNAPSHOT, + "payload size=%d\n", + static_cast(info->length)); + return StartupData{reinterpret_cast(info), + static_cast(info->length)}; +} + +void SerializeBindingData(Environment* env, + SnapshotCreator* creator, + EnvSerializeInfo* info) { + size_t i = 0; + env->ForEachBindingData([&](FastStringKey key, + BaseObjectPtr binding) { + per_process::Debug(DebugCategory::MKSNAPSHOT, + "Serialize binding %i, %p, type=%s\n", + static_cast(i), + *(binding->object()), + key.c_str()); + + if (IsSnapshotableType(key)) { + size_t index = creator->AddData(env->context(), binding->object()); + per_process::Debug(DebugCategory::MKSNAPSHOT, + "Serialized with index=%d\n", + static_cast(index)); + info->bindings.push_back({key.c_str(), i, index}); + SnapshotableObject* ptr = static_cast(binding.get()); + ptr->PrepareForSerialization(env->context(), creator); + } else { + UNREACHABLE(); + } + + i++; + }); } } // namespace node diff --git a/src/node_snapshotable.h b/src/node_snapshotable.h index c45c1381b5554f..94a5a7a03ca731 100644 --- a/src/node_snapshotable.h +++ b/src/node_snapshotable.h @@ -4,9 +4,106 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "v8.h" +#include "base_object.h" +#include "util.h" + namespace node { +class Environment; +struct EnvSerializeInfo; + +#define SERIALIZABLE_OBJECT_TYPES(V) + +enum class EmbedderObjectType : uint8_t { + k_default = 0, +#define V(PropertyName, NativeType) k_##PropertyName, + SERIALIZABLE_OBJECT_TYPES(V) +#undef V +}; + +// When serializing an embedder object, we'll serialize the native states +// into a chunk that can be mapped into a subclass of InternalFieldInfo, +// and pass it into the V8 callback as the payload of StartupData. +// TODO(joyeecheung): the classification of types seem to be wrong. +// We'd need a type for each field of each class of native object. +// Maybe it's fine - we'll just use the type to invoke BaseObject constructors +// and specify that the BaseObject has only one field for us to serialize. +// And for non-BaseObject embedder objects, we'll use field-wise types. +// The memory chunk looks like this: +// +// [ type ] - EmbedderObjectType (a uint8_t) +// [ length ] - a size_t +// [ ... ] - custom bytes of size |length - header size| +struct InternalFieldInfo { + EmbedderObjectType type; + size_t length; + + InternalFieldInfo() = delete; + + static InternalFieldInfo* New(EmbedderObjectType type) { + return New(type, sizeof(InternalFieldInfo)); + } + + static InternalFieldInfo* New(EmbedderObjectType type, size_t length) { + InternalFieldInfo* result = + reinterpret_cast(::operator new(length)); + result->type = type; + result->length = length; + return result; + } + + InternalFieldInfo* Copy() const { + InternalFieldInfo* result = + reinterpret_cast(::operator new(length)); + memcpy(result, this, length); + return result; + } + + void Delete() { ::operator delete(this); } +}; + +// An interface for snapshotable native objects to inherit from. +// Use the SERIALIZABLE_OBJECT_METHODS() macro in the class to define +// the following methods to implement: +// +// - PrepareForSerialization(): This would be run prior to context +// serialization. Use this method to e.g. release references that +// can be re-initialized, or perform property store operations +// that needs a V8 context. +// - Serialize(): This would be called during context serialization, +// once for each embedder field of the object. +// Allocate and construct an InternalFieldInfo object that contains +// data that can be used to deserialize native states. +// - Deserialize(): This would be called after the context is +// deserialized and the object graph is complete, once for each +// embedder field of the object. Use this to restore native states +// in the object. +class SnapshotableObject : public BaseObject { + public: + SnapshotableObject(Environment* env, + v8::Local wrap, + EmbedderObjectType type = EmbedderObjectType::k_default); + const char* GetTypeNameChars() const; + + virtual void PrepareForSerialization(v8::Local context, + v8::SnapshotCreator* creator) = 0; + virtual InternalFieldInfo* Serialize(int index) = 0; + // We'll make sure that the type is set in the constructor + EmbedderObjectType type() { return type_; } + + private: + EmbedderObjectType type_; +}; + +#define SERIALIZABLE_OBJECT_METHODS() \ + void PrepareForSerialization(v8::Local context, \ + v8::SnapshotCreator* creator) override; \ + InternalFieldInfo* Serialize(int index) override; \ + static void Deserialize(v8::Local context, \ + v8::Local holder, \ + int index, \ + InternalFieldInfo* info); + v8::StartupData SerializeNodeContextInternalFields(v8::Local holder, int index, void* env); @@ -14,6 +111,11 @@ void DeserializeNodeInternalFields(v8::Local holder, int index, v8::StartupData payload, void* env); +void SerializeBindingData(Environment* env, + v8::SnapshotCreator* creator, + EnvSerializeInfo* info); + +bool IsSnapshotableType(FastStringKey key); } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS