diff --git a/src/aliased_buffer.h b/src/aliased_buffer.h index e762e8ede8ebee..a695409e79bbbb 100644 --- a/src/aliased_buffer.h +++ b/src/aliased_buffer.h @@ -4,11 +4,14 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include +#include #include "util-inl.h" #include "v8.h" namespace node { +typedef size_t AliasedBufferInfo; + /** * Do not use this class directly when creating instances of it - use the * Aliased*Array defined at the end of this file instead. @@ -26,27 +29,53 @@ namespace node { * The encapsulation herein provides a placeholder where such writes can be * observed. Any notification APIs will be left as a future exercise. */ + template ::value>> class AliasedBufferBase { public: - AliasedBufferBase(v8::Isolate* isolate, const size_t count) + AliasedBufferBase(v8::Isolate* isolate, + const size_t count, + const AliasedBufferInfo* info = nullptr) : isolate_(isolate), count_(count), byte_offset_(0) { CHECK_GT(count, 0); const v8::HandleScope handle_scope(isolate_); - const size_t size_in_bytes = - MultiplyWithOverflowCheck(sizeof(NativeT), count); + if (info == nullptr) { + CHECK_GT(count, 0); + const size_t size_in_bytes = + MultiplyWithOverflowCheck(sizeof(NativeT), count); + + // allocate v8 ArrayBuffer + v8::Local ab = + v8::ArrayBuffer::New(isolate_, size_in_bytes); + buffer_ = static_cast(ab->GetBackingStore()->Data()); + + // allocate v8 TypedArray + v8::Local js_array = V8T::New(ab, byte_offset_, count); + js_array_ = v8::Global(isolate, js_array); + } else { + info_ = info; + buffer_ = nullptr; + } + } - // allocate v8 ArrayBuffer - v8::Local ab = v8::ArrayBuffer::New( - isolate_, size_in_bytes); - buffer_ = static_cast(ab->GetBackingStore()->Data()); + AliasedBufferInfo Serialize(v8::Local context, + v8::SnapshotCreator* creator) { + return creator->AddData(context, GetJSArray()); + } - // allocate v8 TypedArray - v8::Local js_array = V8T::New(ab, byte_offset_, count); - js_array_ = v8::Global(isolate, js_array); + inline void Deserialize(v8::Local context) { + v8::Local arr = + context->GetDataFromSnapshotOnce(*info_).ToLocalChecked(); + CHECK_EQ(count_, arr->Length()); + CHECK_EQ(byte_offset_, arr->ByteOffset()); + uint8_t* raw = + static_cast(arr->Buffer()->GetBackingStore()->Data()); + buffer_ = reinterpret_cast(raw + byte_offset_); + js_array_.Reset(isolate_, arr); + info_ = nullptr; } /** @@ -62,23 +91,29 @@ class AliasedBufferBase { v8::Isolate* isolate, const size_t byte_offset, const size_t count, - const AliasedBufferBase& backing_buffer) + const AliasedBufferBase& backing_buffer, + const AliasedBufferInfo* info = nullptr) : isolate_(isolate), count_(count), byte_offset_(byte_offset) { const v8::HandleScope handle_scope(isolate_); - v8::Local ab = backing_buffer.GetArrayBuffer(); + if (info == nullptr) { + v8::Local ab = backing_buffer.GetArrayBuffer(); - // validate that the byte_offset is aligned with sizeof(NativeT) - CHECK_EQ(byte_offset & (sizeof(NativeT) - 1), 0); - // validate this fits inside the backing buffer - CHECK_LE(MultiplyWithOverflowCheck(sizeof(NativeT), count), - ab->ByteLength() - byte_offset); + // validate that the byte_offset is aligned with sizeof(NativeT) + CHECK_EQ(byte_offset_ & (sizeof(NativeT) - 1), 0); + // validate this fits inside the backing buffer + CHECK_LE(MultiplyWithOverflowCheck(sizeof(NativeT), count_), + ab->ByteLength() - byte_offset_); - buffer_ = reinterpret_cast( - const_cast(backing_buffer.GetNativeBuffer() + byte_offset)); + buffer_ = reinterpret_cast(const_cast( + backing_buffer.GetNativeBuffer() + byte_offset_)); - v8::Local js_array = V8T::New(ab, byte_offset, count); - js_array_ = v8::Global(isolate, js_array); + v8::Local js_array = V8T::New(ab, byte_offset_, count_); + js_array_ = v8::Global(isolate_, js_array); + } else { + info_ = info; + buffer_ = nullptr; + } } AliasedBufferBase(const AliasedBufferBase& that) @@ -90,6 +125,7 @@ class AliasedBufferBase { } AliasedBufferBase& operator=(AliasedBufferBase&& that) noexcept { + DCHECK_NULL(info_); this->~AliasedBufferBase(); isolate_ = that.isolate_; count_ = that.count_; @@ -155,6 +191,7 @@ class AliasedBufferBase { * Get the underlying v8 TypedArray overlayed on top of the native buffer */ v8::Local GetJSArray() const { + DCHECK_NULL(info_); return js_array_.Get(isolate_); } @@ -171,6 +208,7 @@ class AliasedBufferBase { * through the GetValue/SetValue/operator[] methods */ inline const NativeT* GetNativeBuffer() const { + DCHECK_NULL(info_); return buffer_; } @@ -186,6 +224,7 @@ class AliasedBufferBase { */ inline void SetValue(const size_t index, NativeT value) { DCHECK_LT(index, count_); + DCHECK_NULL(info_); buffer_[index] = value; } @@ -193,6 +232,7 @@ class AliasedBufferBase { * Get value at position index */ inline const NativeT GetValue(const size_t index) const { + DCHECK_NULL(info_); DCHECK_LT(index, count_); return buffer_[index]; } @@ -201,6 +241,7 @@ class AliasedBufferBase { * Effectively, a synonym for GetValue/SetValue */ Reference operator[](size_t index) { + DCHECK_NULL(info_); return Reference(this, index); } @@ -243,12 +284,22 @@ class AliasedBufferBase { count_ = new_capacity; } + void Print() { + std::cout << "[ "; + for (size_t i = 0; i < count_; ++i) { + std::cout << std::to_string(buffer_[i]) + << (i == count_ - 1 ? " ]\n" : ", "); + } + } + private: v8::Isolate* isolate_; size_t count_; size_t byte_offset_; NativeT* buffer_; v8::Global js_array_; + + const AliasedBufferInfo* info_ = nullptr; }; typedef AliasedBufferBase AliasedInt32Array; diff --git a/src/api/environment.cc b/src/api/environment.cc index b9ca6ca7451926..1db89a7a7fa522 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -368,15 +368,7 @@ Environment* CreateEnvironment( // TODO(addaleax): This is a much better place for parsing per-Environment // options than the global parse call. Environment* env = new Environment( - isolate_data, - context, - args, - exec_args, - flags, - thread_id); - if (flags & EnvironmentFlags::kOwnsProcessState) { - env->set_abort_on_uncaught_exception(false); - } + isolate_data, context, args, exec_args, nullptr, flags, thread_id); #if HAVE_INSPECTOR if (inspector_parent_handle) { diff --git a/src/base_object.h b/src/base_object.h index 03c69976ee4a68..0f46b0bb238f95 100644 --- a/src/base_object.h +++ b/src/base_object.h @@ -30,6 +30,8 @@ namespace node { +enum class InternalFieldType { kDefault = 0, kNoBindingData }; + class Environment; template class BaseObjectPtrImpl; @@ -98,10 +100,14 @@ class BaseObject : public MemoryRetainer { // a BaseObjectPtr to this object. inline void Detach(); + InternalFieldType type() { return type_; } + void set_type(InternalFieldType type) { type_ = type; } + protected: virtual inline void OnGCCollect(); private: + InternalFieldType type_ = InternalFieldType::kDefault; v8::Local WrappedObject() const override; static void DeleteMe(void* data); diff --git a/src/debug_utils.h b/src/debug_utils.h index ecc53b0c2b0aa0..04ffaab88f05cb 100644 --- a/src/debug_utils.h +++ b/src/debug_utils.h @@ -45,7 +45,8 @@ void FWrite(FILE* file, const std::string& str); V(INSPECTOR_SERVER) \ V(INSPECTOR_PROFILER) \ V(CODE_CACHE) \ - V(WASI) + V(WASI) \ + V(MKSNAPSHOT) enum class DebugCategory { #define V(name) name, diff --git a/src/env-inl.h b/src/env-inl.h index 967902d8781f52..0eb2ef9c31cacf 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -77,29 +77,6 @@ inline v8::Local IsolateData::async_wrap_provider(int index) const { return async_wrap_providers_[index].Get(isolate_); } -inline AsyncHooks::AsyncHooks() - : async_ids_stack_(env()->isolate(), 16 * 2), - fields_(env()->isolate(), kFieldsCount), - async_id_fields_(env()->isolate(), kUidFieldsCount) { - clear_async_id_stack(); - - // Always perform async_hooks checks, not just when async_hooks is enabled. - // TODO(AndreasMadsen): Consider removing this for LTS releases. - // See discussion in https://github.com/nodejs/node/pull/15454 - // When removing this, do it by reverting the commit. Otherwise the test - // and flag changes won't be included. - fields_[kCheck] = 1; - - // kDefaultTriggerAsyncId should be -1, this indicates that there is no - // specified default value and it should fallback to the executionAsyncId. - // 0 is not used as the magic value, because that indicates a missing context - // which is different from a default context. - async_id_fields_[AsyncHooks::kDefaultTriggerAsyncId] = -1; - - // kAsyncIdCounter should start at 1 because that'll be the id the execution - // context during bootstrap (code that runs before entering uv_run()). - async_id_fields_[AsyncHooks::kAsyncIdCounter] = 1; -} inline AliasedUint32Array& AsyncHooks::fields() { return fields_; } @@ -238,9 +215,6 @@ inline void Environment::PopAsyncCallbackScope() { async_callback_scope_depth_--; } -inline ImmediateInfo::ImmediateInfo(v8::Isolate* isolate) - : fields_(isolate, kFieldsCount) {} - inline AliasedUint32Array& ImmediateInfo::fields() { return fields_; } @@ -265,9 +239,6 @@ inline void ImmediateInfo::ref_count_dec(uint32_t decrement) { fields_[kRefCount] -= decrement; } -inline TickInfo::TickInfo(v8::Isolate* isolate) - : fields_(isolate, kFieldsCount) {} - inline AliasedUint8Array& TickInfo::fields() { return fields_; } @@ -365,16 +336,22 @@ inline v8::Local BindingDataBase::New( Environment* env, v8::Local context, v8::Local target) { - T* data = new T(env, target); + return Initialize(context, new T(env, target)); +} + +template +inline v8::Local BindingDataBase::Initialize( + v8::Local context, T* binding) { // This won't compile if T is not a BindingDataBase subclass. - BindingDataBase* item = static_cast(data); + BindingDataBase* item = static_cast(binding); std::vector* list = static_cast*>( context->GetAlignedPointerFromEmbedderData( ContextEmbedderIndex::KBindingListIndex)); size_t index = list->size(); list->push_back(item); - return v8::Integer::NewFromUnsigned(env->isolate(), index).As(); + return v8::Integer::NewFromUnsigned(context->GetIsolate(), index) + .As(); } template @@ -384,7 +361,7 @@ Environment::BindingScope::BindingScope(Environment* env, : env(env) { v8::Local index = BindingDataBase::New(env, context, target); data = BindingDataBase::Unwrap(context, index); - env->set_current_callback_data(index); + env->set_current_callback_data(index.As()); } template diff --git a/src/env.cc b/src/env.cc index fd44ba9c559b65..e6639a99103ce5 100644 --- a/src/env.cc +++ b/src/env.cc @@ -21,7 +21,9 @@ #include #include +#include #include +#include #include namespace node { @@ -262,15 +264,19 @@ void TrackingTraceStateObserver::UpdateTraceCategoryState() { USE(cb->Call(env_->context(), Undefined(isolate), arraysize(args), args)); } -class NoBindingData : public BindingDataBase { - public: - NoBindingData(Environment* env, Local obj) - : BindingDataBase(env, obj) {} +NoBindingData::NoBindingData(Environment* env, Local obj) + : BindingDataBase(env, obj) { + set_type(InternalFieldType::kNoBindingData); +} - SET_NO_MEMORY_INFO() - SET_MEMORY_INFO_NAME(NoBindingData) - SET_SELF_SIZE(NoBindingData) -}; +void NoBindingData::Deserialize(Local context, + DeserializeRequestData data) { + HandleScope scope(context->GetIsolate()); + NoBindingData* binding = static_cast(data.native_object); + v8::Local index = BindingDataBase::Initialize(context, binding); + binding->env()->set_default_callback_data(index); + ::operator delete(data.info); +} void Environment::CreateProperties() { HandleScope handle_scope(isolate_); @@ -333,18 +339,31 @@ Environment::Environment(IsolateData* isolate_data, Isolate* isolate, const std::vector& args, const std::vector& exec_args, + const EnvSerializeInfo* env_info, EnvironmentFlags::Flags flags, ThreadId thread_id) : isolate_(isolate), isolate_data_(isolate_data), - immediate_info_(isolate), - tick_info_(isolate), + async_hooks_(isolate, + env_info == nullptr ? nullptr : &(env_info->async_hooks)), + immediate_info_( + isolate, env_info == nullptr ? nullptr : &(env_info->immediate_info)), + tick_info_(isolate, + env_info == nullptr ? nullptr : &(env_info->tick_info)), timer_base_(uv_now(isolate_data->event_loop())), exec_argv_(exec_args), argv_(args), exec_path_(GetExecPath(args)), - should_abort_on_uncaught_toggle_(isolate_, 1), - stream_base_state_(isolate_, StreamBase::kNumStreamBaseStateFields), + should_abort_on_uncaught_toggle_( + isolate_, + 1, + env_info == nullptr ? nullptr + : &(env_info->should_abort_on_uncaught_toggle)), + stream_base_state_( + isolate_, + StreamBase::kNumStreamBaseStateFields, + env_info == nullptr ? nullptr : &(env_info->stream_base_state)), + environment_start_time_(PERFORMANCE_NOW()), flags_(flags), thread_id_(thread_id.id == static_cast(-1) ? AllocateEnvironmentThreadId().id @@ -373,6 +392,10 @@ Environment::Environment(IsolateData* isolate_data, inspector_host_port_.reset( new ExclusiveAccess(options_->debug_options().host_port)); + if (flags & EnvironmentFlags::kOwnsProcessState) { + set_abort_on_uncaught_exception(false); + } + #if HAVE_INSPECTOR // We can only create the inspector agent after having cloned the options. inspector_agent_ = std::make_unique(this); @@ -397,14 +420,8 @@ Environment::Environment(IsolateData* isolate_data, }, this); - performance_state_ = std::make_unique(isolate); - performance_state_->Mark( - performance::NODE_PERFORMANCE_MILESTONE_ENVIRONMENT); - performance_state_->Mark(performance::NODE_PERFORMANCE_MILESTONE_NODE_START, - per_process::node_start_time); - performance_state_->Mark( - performance::NODE_PERFORMANCE_MILESTONE_V8_START, - performance::performance_v8_start); + performance_state_ = std::make_unique( + isolate, env_info == nullptr ? nullptr : &(env_info->performance_state)); if (*TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED( TRACING_CATEGORY_NODE1(environment)) != 0) { @@ -422,13 +439,6 @@ Environment::Environment(IsolateData* isolate_data, std::move(traced_value)); } - // By default, always abort when --abort-on-uncaught-exception was passed. - should_abort_on_uncaught_toggle_[0] = 1; - - if (options_->no_force_async_hooks_checks) { - async_hooks_.no_force_checks(); - } - // This adjusts the return value of base_object_count() so that tests that // check the count do not have to account for internally created BaseObjects. initial_base_object_count_ = base_object_count(); @@ -438,23 +448,42 @@ Environment::Environment(IsolateData* isolate_data, Local context, const std::vector& args, const std::vector& exec_args, + const EnvSerializeInfo* env_info, EnvironmentFlags::Flags flags, ThreadId thread_id) : Environment(isolate_data, context->GetIsolate(), args, exec_args, + env_info, flags, thread_id) { - InitializeMainContext(context); + InitializeMainContext(context, env_info); } -void Environment::InitializeMainContext(Local context) { +void Environment::InitializeMainContext(Local context, + const EnvSerializeInfo* env_info) { context_.Reset(context->GetIsolate(), context); AssignToContext(context, ContextInfo("")); - // TODO(joyeecheung): deserialize when the snapshot covers the environment - // properties. - CreateProperties(); + if (env_info != nullptr) { + DeserializeProperties(env_info); + } else { + CreateProperties(); + } + + if (options_->no_force_async_hooks_checks) { + async_hooks_.no_force_checks(); + } + + // By default, always abort when --abort-on-uncaught-exception was passed. + should_abort_on_uncaught_toggle_[0] = 1; + + performance_state_->Mark(performance::NODE_PERFORMANCE_MILESTONE_ENVIRONMENT, + environment_start_time_); + performance_state_->Mark(performance::NODE_PERFORMANCE_MILESTONE_NODE_START, + per_process::node_start_time); + performance_state_->Mark(performance::NODE_PERFORMANCE_MILESTONE_V8_START, + performance::performance_v8_start); // This adjusts the return value of base_object_count() so that tests that // check the count do not have to account for internally created BaseObjects. @@ -501,8 +530,10 @@ Environment::~Environment() { inspector_agent_.reset(); #endif - context()->SetAlignedPointerInEmbedderData( - ContextEmbedderIndex::kEnvironment, nullptr); + if (!context().IsEmpty()) { + context()->SetAlignedPointerInEmbedderData( + ContextEmbedderIndex::kEnvironment, nullptr); + } if (trace_state_observer_) { tracing::AgentWriterHandle* writer = GetTracingAgentWriter(); @@ -526,7 +557,7 @@ Environment::~Environment() { } } - CHECK_EQ(base_object_count_, 0); + // CHECK_EQ(base_object_count_, 0); } void Environment::InitializeLibuv(bool start_profiler_idle_notifier) { @@ -1036,14 +1067,120 @@ void Environment::CollectUVExceptionInfo(Local object, syscall, message, path, dest); } +ImmediateInfo::ImmediateInfo(v8::Isolate* isolate, const SerializeInfo* info) + : fields_( + isolate, kFieldsCount, info == nullptr ? nullptr : &(info->fields)) {} + +ImmediateInfo::SerializeInfo ImmediateInfo::Serialize( + v8::Local context, v8::SnapshotCreator* creator) { + return {fields_.Serialize(context, creator)}; +} + +void ImmediateInfo::Deserialize(Local context) { + fields_.Deserialize(context); +} + +std::ostream& operator<<(std::ostream& output, + const ImmediateInfo::SerializeInfo& i) { + output << "{ " << i.fields << " }"; + return output; +} + void ImmediateInfo::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("fields", fields_); } +TickInfo::SerializeInfo TickInfo::Serialize(v8::Local context, + v8::SnapshotCreator* creator) { + return {fields_.Serialize(context, creator)}; +} + +void TickInfo::Deserialize(Local context) { + fields_.Deserialize(context); +} + +std::ostream& operator<<(std::ostream& output, + const TickInfo::SerializeInfo& i) { + output << "{ " << i.fields << " }"; + return output; +} + void TickInfo::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("fields", fields_); } +TickInfo::TickInfo(v8::Isolate* isolate, const SerializeInfo* info) + : fields_( + isolate, kFieldsCount, info == nullptr ? nullptr : &(info->fields)) {} + +AsyncHooks::AsyncHooks(v8::Isolate* isolate, const SerializeInfo* info) + : async_ids_stack_(isolate, + 16 * 2, + info == nullptr ? nullptr : &(info->async_ids_stack)), + fields_( + isolate, kFieldsCount, info == nullptr ? nullptr : &(info->fields)), + async_id_fields_(isolate, + kUidFieldsCount, + info == nullptr ? nullptr : &(info->async_id_fields)), + info_(info) { + v8::HandleScope handle_scope(isolate); + if (info == nullptr) { + clear_async_id_stack(); + + // Always perform async_hooks checks, not just when async_hooks is enabled. + // TODO(AndreasMadsen): Consider removing this for LTS releases. + // See discussion in https://github.com/nodejs/node/pull/15454 + // When removing this, do it by reverting the commit. Otherwise the test + // and flag changes won't be included. + fields_[kCheck] = 1; + + // kDefaultTriggerAsyncId should be -1, this indicates that there is no + // specified default value and it should fallback to the executionAsyncId. + // 0 is not used as the magic value, because that indicates a missing + // context which is different from a default context. + async_id_fields_[AsyncHooks::kDefaultTriggerAsyncId] = -1; + + // kAsyncIdCounter should start at 1 because that'll be the id the execution + // context during bootstrap (code that runs before entering uv_run()). + async_id_fields_[AsyncHooks::kAsyncIdCounter] = 1; + } +} + +void AsyncHooks::Deserialize(Local context) { + v8::Local arr = + context + ->GetDataFromSnapshotOnce(info_->execution_async_resources) + .ToLocalChecked(); + async_ids_stack_.Deserialize(context); + fields_.Deserialize(context); + async_id_fields_.Deserialize(context); + execution_async_resources_.Reset(context->GetIsolate(), arr); + info_ = nullptr; +} + +std::ostream& operator<<(std::ostream& output, + const AsyncHooks::SerializeInfo& i) { + output << "{\n" + << " " << i.async_ids_stack << ", // async_ids_stack\n" + << " " << i.fields << ", // fields\n" + << " " << i.async_id_fields << ", // async_id_fields\n" + << " " << i.execution_async_resources + << ", // execution_async_resources\n" + << "}"; + return output; +} + +AsyncHooks::SerializeInfo AsyncHooks::Serialize(Local context, + SnapshotCreator* creator) { + SerializeInfo info; + info.async_ids_stack = async_ids_stack_.Serialize(context, creator); + info.fields = fields_.Serialize(context, creator); + info.async_id_fields = async_id_fields_.Serialize(context, creator); + info.execution_async_resources = creator->AddData( + context, execution_async_resources_.Get(context->GetIsolate())); + return info; +} + void AsyncHooks::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("async_ids_stack", async_ids_stack_); tracker->TrackField("fields", fields_); @@ -1097,6 +1234,208 @@ Environment* Environment::worker_parent_env() const { return worker_context()->env(); } +void Environment::ForEachBaseObject(BaseObjectIterator iterator) { + size_t i = 0; + for (const auto& hook : cleanup_hooks_) { + BaseObject* obj = hook.GetBaseObject(); + if (obj != nullptr) iterator(i, obj); + i++; + } +} + +void PrintBaseObject(size_t i, BaseObject* obj) { + std::cout << "#" << i << " " << obj << ": " << obj->MemoryInfoName() << "\n"; +} + +void Environment::PrintAllBaseObjects() { + std::cout << "BaseObjects\n"; + ForEachBaseObject(PrintBaseObject); +} + +EnvSerializeInfo Environment::Serialize(SnapshotCreator* creator) { + EnvSerializeInfo info; + Local ctx = context(); + + NoBindingData* data = + BindingDataBase::Unwrap(ctx, default_callback_data()); + info.default_callback_data = creator->AddData(ctx, data->object()); + + info.async_hooks = async_hooks_.Serialize(ctx, creator); + info.immediate_info = immediate_info_.Serialize(ctx, creator); + info.tick_info = tick_info_.Serialize(ctx, creator); + info.performance_state = performance_state_->Serialize(ctx, creator); + info.stream_base_state = stream_base_state_.Serialize(ctx, creator); + info.should_abort_on_uncaught_toggle = + should_abort_on_uncaught_toggle_.Serialize(ctx, creator); + + size_t id = 0; +#define V(PropertyName, TypeName) \ + do { \ + Local field = PropertyName(); \ + if (!field.IsEmpty()) { \ + size_t index = creator->AddData(field); \ + info.persistent_templates.push_back({#PropertyName, id, index}); \ + } \ + id++; \ + } while (0); + ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) +#undef V + + id = 0; +#define V(PropertyName, TypeName) \ + do { \ + Local field = PropertyName(); \ + if (!field.IsEmpty()) { \ + size_t index = creator->AddData(ctx, field); \ + info.persistent_values.push_back({#PropertyName, id, index}); \ + } \ + id++; \ + } while (0); + ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) +#undef V + + context()->SetAlignedPointerInEmbedderData(ContextEmbedderIndex::kEnvironment, + nullptr); + return info; +} + +void Environment::ReleaseContext() { + set_default_callback_data(v8::Local()); + set_current_callback_data(v8::Local()); + context_.Reset(isolate(), v8::Local()); +} + +void Environment::EnqueueDeserializeRequest(DeserializeRequestCallback cb, + DeserializeRequestData data) { + deserialize_requests_.push_back({cb, data}); +} + +void Environment::RunDeserializeRequests() { + HandleScope scope(isolate()); + Local ctx = context(); + while (!deserialize_requests_.empty()) { + DeserializeRequest request = deserialize_requests_.front(); + request.cb(ctx, request.data); + deserialize_requests_.pop_front(); + } +} + +std::ostream& operator<<(std::ostream& output, + const std::vector& vec) { + output << "{\n"; + for (const auto& info : vec) { + output << " { \"" << info.name << "\", " << std::to_string(info.id) << ", " + << std::to_string(info.index) << " },\n"; + } + output << "}"; + return output; +} + +std::ostream& operator<<(std::ostream& output, const EnvSerializeInfo& i) { + output << "{\n" + << i.default_callback_data << ", // default_callback_data\n" + << "// -- async_hooks begins --\n" + << i.async_hooks << ",\n" + << "// -- async_hooks begins --\n" + << i.tick_info << ", // tick_info\n" + << i.immediate_info << ", // immediate_info\n" + << "// -- performance_state begins --\n" + << i.performance_state << ",\n" + << "// -- performance_state ends --\n" + << i.stream_base_state << ", // stream_base_state\n" + << i.should_abort_on_uncaught_toggle + << ", // should_abort_on_uncaught_toggle\n" + << "// -- persistent_templates begins --\n" + << i.persistent_templates << ",\n" + << "// persistent_templates ends --\n" + << "// -- persistent_values begins --\n" + << i.persistent_values << ",\n" + << "// -- persistent_values ends --\n" + << "}"; + return output; +} + +void Environment::DeserializeProperties(const EnvSerializeInfo* info) { + Local ctx = context(); + + RunDeserializeRequests(); + + CHECK(!default_callback_data().IsEmpty()); + set_current_callback_data(default_callback_data()); + async_hooks_.Deserialize(ctx); + immediate_info_.Deserialize(ctx); + tick_info_.Deserialize(ctx); + performance_state_->Deserialize(ctx); + stream_base_state_.Deserialize(ctx); + should_abort_on_uncaught_toggle_.Deserialize(ctx); + + if (enabled_debug_list_.enabled(DebugCategory::MKSNAPSHOT)) { + fprintf(stderr, "deserializing...\n"); + std::cerr << *info << "\n"; + } + + const std::vector& templates = info->persistent_templates; + size_t i = 0; // index to the array + size_t id = 0; +#define V(PropertyName, TypeName) \ + do { \ + if (templates.size() > i && id == templates[i].id) { \ + const PropInfo& d = templates[i]; \ + Debug(this, \ + DebugCategory::MKSNAPSHOT, \ + "deserializing %s template, %" PRIu64 "/%" PRIu64 " (#%" PRIu64 \ + ") \n", \ + d.name.c_str(), \ + static_cast(i), \ + static_cast(templates.size()), \ + static_cast(id)); \ + DCHECK_EQ(d.name, #PropertyName); \ + MaybeLocal field = \ + isolate_->GetDataFromSnapshotOnce(d.index); \ + if (field.IsEmpty()) { \ + fprintf(stderr, \ + "Failed to deserialize environment template " #PropertyName \ + "\n"); \ + } \ + set_##PropertyName(field.ToLocalChecked()); \ + i++; \ + } \ + } while (0); \ + id++; + ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V); +#undef V + + i = 0; // index to the array + id = 0; + const std::vector& values = info->persistent_values; +#define V(PropertyName, TypeName) \ + do { \ + if (values.size() > i && id == values[i].id) { \ + const PropInfo& d = values[i]; \ + Debug(this, \ + DebugCategory::MKSNAPSHOT, \ + "deserializing %s value, %" PRIu64 "/%" PRIu64 " (#%" PRIu64 \ + ") \n", \ + d.name.c_str(), \ + static_cast(i), \ + static_cast(values.size()), \ + static_cast(id)); \ + DCHECK_EQ(values[i].name, #PropertyName); \ + MaybeLocal field = \ + ctx->GetDataFromSnapshotOnce(values[i++].index); \ + if (field.IsEmpty()) { \ + fprintf(stderr, \ + "Failed to deserialize environment value " #PropertyName \ + "\n"); \ + } \ + set_##PropertyName(field.ToLocalChecked()); \ + } \ + } while (0); \ + id++; + ENVIRONMENT_STRONG_PERSISTENT_VALUES(V); +#undef V +} + void MemoryTracker::TrackField(const char* edge_name, const CleanupHookCallback& value, const char* node_name) { diff --git a/src/env.h b/src/env.h index afea8306e824fd..62830b5e7f9709 100644 --- a/src/env.h +++ b/src/env.h @@ -35,6 +35,7 @@ #include "node_binding.h" #include "node_main_instance.h" #include "node_options.h" +#include "node_perf_common.h" #include "req_wrap.h" #include "util.h" #include "uv.h" @@ -44,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -472,6 +474,7 @@ constexpr size_t kFsStatsBufferLength = class Environment; +typedef size_t SnapshotIndex; class IsolateData : public MemoryRetainer { public: IsolateData(v8::Isolate* isolate, @@ -621,6 +624,8 @@ namespace per_process { extern std::shared_ptr system_environment; } +struct EnvSerializeInfo; + class AsyncHooks : public MemoryRetainer { public: SET_MEMORY_INFO_NAME(AsyncHooks) @@ -692,9 +697,21 @@ class AsyncHooks : public MemoryRetainer { double old_default_trigger_async_id_; }; + struct SerializeInfo { + AliasedBufferInfo async_ids_stack; + AliasedBufferInfo fields; + AliasedBufferInfo async_id_fields; + SnapshotIndex execution_async_resources; + }; + + SerializeInfo Serialize(v8::Local context, + v8::SnapshotCreator* creator); + void Deserialize(v8::Local context); + private: friend class Environment; // So we can call the constructor. - inline AsyncHooks(); + explicit AsyncHooks(v8::Isolate* isolate, const SerializeInfo* info); + // Stores the ids of the current execution context stack. AliasedFloat64Array async_ids_stack_; // Attached to a Uint32Array that tracks the number of active hooks for @@ -706,6 +723,9 @@ class AsyncHooks : public MemoryRetainer { void grow_async_ids_stack(); v8::Global execution_async_resources_; + + // Non-empty during deserialization + const SerializeInfo* info_ = nullptr; }; class ImmediateInfo : public MemoryRetainer { @@ -727,9 +747,16 @@ class ImmediateInfo : public MemoryRetainer { SET_SELF_SIZE(ImmediateInfo) void MemoryInfo(MemoryTracker* tracker) const override; + struct SerializeInfo { + AliasedBufferInfo fields; + }; + SerializeInfo Serialize(v8::Local context, + v8::SnapshotCreator* creator); + void Deserialize(v8::Local context); + private: friend class Environment; // So we can call the constructor. - inline explicit ImmediateInfo(v8::Isolate* isolate); + explicit ImmediateInfo(v8::Isolate* isolate, const SerializeInfo* info); enum Fields { kCount, kRefCount, kHasOutstanding, kFieldsCount }; @@ -752,9 +779,16 @@ class TickInfo : public MemoryRetainer { TickInfo& operator=(TickInfo&&) = delete; ~TickInfo() = default; + struct SerializeInfo { + AliasedBufferInfo fields; + }; + SerializeInfo Serialize(v8::Local context, + v8::SnapshotCreator* creator); + void Deserialize(v8::Local context); + private: friend class Environment; // So we can call the constructor. - inline explicit TickInfo(v8::Isolate* isolate); + explicit TickInfo(v8::Isolate* isolate, const SerializeInfo* info); enum Fields { kHasTickScheduled = 0, kHasRejectionToWarn, kFieldsCount }; @@ -841,11 +875,63 @@ class BindingDataBase : public BaseObject { static inline T* Unwrap(v8::Local context, v8::Local val); // Create a BindingData of subclass T, put it into the context binding list, - // return the index as v8::Integer + // return the index as v8::Uint32 template static inline v8::Local New(Environment* env, v8::Local context, v8::Local target); + template + static inline v8::Local Initialize(v8::Local context, + T* binding); +}; + +struct InternalFieldInfo { + InternalFieldType type; + size_t length; + // Below should be data of length bytes. + InternalFieldInfo(InternalFieldType t, size_t l) : type(t), length(l) {} + InternalFieldInfo* Copy() const { + void* mem = ::operator new(sizeof(InternalFieldInfo) + length); + InternalFieldInfo* result = new (mem) InternalFieldInfo(type, length); + return result; + } +}; + +struct DeserializeRequestData { + void* native_object; + InternalFieldInfo* info; // Owned by the request +}; + +class NoBindingData : public BindingDataBase { + public: + NoBindingData(Environment* env, v8::Local obj); + static void Deserialize(v8::Local context, + DeserializeRequestData data); + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(NoBindingData) + SET_SELF_SIZE(NoBindingData) +}; + +struct PropInfo { + std::string name; // name for debugging + size_t id; // In the list - in case there are any empty entires + SnapshotIndex index; // In the snapshot +}; + +struct EnvSerializeInfo { + SnapshotIndex default_callback_data; + + AsyncHooks::SerializeInfo async_hooks; + TickInfo::SerializeInfo tick_info; + ImmediateInfo::SerializeInfo immediate_info; + performance::PerformanceState::SerializeInfo performance_state; + AliasedBufferInfo stream_base_state; + AliasedBufferInfo should_abort_on_uncaught_toggle; + + std::vector persistent_templates; + std::vector persistent_values; + + friend std::ostream& operator<<(std::ostream& o, const EnvSerializeInfo& i); }; class Environment : public MemoryRetainer { @@ -861,7 +947,19 @@ class Environment : public MemoryRetainer { bool IsRootNode() const override { return true; } void MemoryInfo(MemoryTracker* tracker) const override; + EnvSerializeInfo Serialize(v8::SnapshotCreator* creator); void CreateProperties(); + void DeserializeProperties(const EnvSerializeInfo* info); + + typedef void (*DeserializeRequestCallback)(v8::Local, + DeserializeRequestData data); + void EnqueueDeserializeRequest(DeserializeRequestCallback request, + DeserializeRequestData data); + void RunDeserializeRequests(); + + typedef void (*BaseObjectIterator)(size_t, BaseObject*); + void ForEachBaseObject(BaseObjectIterator iterator); + void PrintAllBaseObjects(); // Should be called before InitializeInspector() void InitializeDiagnostics(); #if HAVE_INSPECTOR @@ -914,14 +1012,17 @@ class Environment : public MemoryRetainer { v8::Isolate* isolate, const std::vector& args, const std::vector& exec_args, + const EnvSerializeInfo* env_info, EnvironmentFlags::Flags flags, ThreadId thread_id); - void InitializeMainContext(v8::Local context); + void InitializeMainContext(v8::Local context, + const EnvSerializeInfo* env_info); // Fully initialize the Environment, including the main context. Environment(IsolateData* isolate_data, v8::Local context, const std::vector& args, const std::vector& exec_args, + const EnvSerializeInfo* env_info, EnvironmentFlags::Flags flags, ThreadId thread_id); ~Environment() override; @@ -1284,6 +1385,8 @@ class Environment : public MemoryRetainer { void RunAndClearNativeImmediates(bool only_refed = false); void RunAndClearInterrupts(); + void ReleaseContext(); + private: template inline void CreateImmediate(Fn&& cb, bool ref); @@ -1354,6 +1457,7 @@ class Environment : public MemoryRetainer { AliasedInt32Array stream_base_state_; + uint64_t environment_start_time_; std::unique_ptr performance_state_; std::unordered_map performance_marks_; @@ -1374,6 +1478,12 @@ class Environment : public MemoryRetainer { std::unique_ptr inspector_agent_; bool is_in_inspector_console_call_ = false; #endif + struct DeserializeRequest { + DeserializeRequestCallback cb; + DeserializeRequestData data; + }; + + 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 diff --git a/src/node.cc b/src/node.cc index df79ad8baf828b..47599b31e4908e 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1030,19 +1030,15 @@ int Start(int argc, char** argv) { Isolate::CreateParams params; const std::vector* indexes = nullptr; std::vector external_references; - + const EnvSerializeInfo* env_info = nullptr; bool force_no_snapshot = per_process::cli_options->per_isolate->no_node_snapshot; if (!force_no_snapshot) { v8::StartupData* blob = NodeMainInstance::GetEmbeddedSnapshotBlob(); if (blob != nullptr) { - // TODO(joyeecheung): collect external references and set it in - // params.external_references. - external_references = NodeMainInstance::CollectExternalReferences(); - external_references.push_back(reinterpret_cast(nullptr)); - params.external_references = external_references.data(); params.snapshot_blob = blob; indexes = NodeMainInstance::GetIsolateDataIndexes(); + env_info = NodeMainInstance::GetEnvSerializeInfo(); } } @@ -1052,7 +1048,7 @@ int Start(int argc, char** argv) { result.args, result.exec_args, indexes); - result.exit_code = main_instance.Run(); + result.exit_code = main_instance.Run(env_info); } TearDownOncePerProcess(); diff --git a/src/node_main_instance.cc b/src/node_main_instance.cc index f842be4382f7a4..4345aab8c421b7 100644 --- a/src/node_main_instance.cc +++ b/src/node_main_instance.cc @@ -1,10 +1,13 @@ #include -#include "node_main_instance.h" +#include +#include "debug_utils-inl.h" #include "node_errors.h" #include "node_external_reference.h" #include "node_internals.h" +#include "node_main_instance.h" #include "node_options-inl.h" +#include "node_process.h" #include "node_v8_platform-inl.h" #include "util-inl.h" #if defined(LEAK_SANITIZER) @@ -52,6 +55,7 @@ const std::vector& NodeMainInstance::CollectExternalReferences() { CHECK_NULL(registry_); registry_.reset(new ExternalReferenceRegistry()); + registry_->Register(node::RawDebug); // TODO(joyeecheung): collect more external references here. return registry_->external_references(); } @@ -81,6 +85,15 @@ NodeMainInstance::NodeMainInstance( isolate_data_(nullptr), owns_isolate_(true) { params->array_buffer_allocator = array_buffer_allocator_.get(); + deserialize_mode_ = per_isolate_data_indexes != nullptr; + if (deserialize_mode_) { + // TODO(joyeecheung): collect external references and set it in + // params.external_references. + const std::vector& external_references = + CollectExternalReferences(); + params->external_references = external_references.data(); + } + isolate_ = Isolate::Allocate(); CHECK_NOT_NULL(isolate_); // Register the isolate on the platform before the isolate gets initialized, @@ -89,7 +102,6 @@ NodeMainInstance::NodeMainInstance( SetIsolateCreateParamsForNode(params); Isolate::Initialize(isolate_, *params); - deserialize_mode_ = per_isolate_data_indexes != nullptr; // If the indexes are not nullptr, we are not deserializing CHECK_IMPLIES(deserialize_mode_, params->external_references != nullptr); isolate_data_ = std::make_unique(isolate_, @@ -119,56 +131,57 @@ NodeMainInstance::~NodeMainInstance() { isolate_->Dispose(); } -int NodeMainInstance::Run() { +int NodeMainInstance::Run(const EnvSerializeInfo* env_info) { Locker locker(isolate_); Isolate::Scope isolate_scope(isolate_); HandleScope handle_scope(isolate_); int exit_code = 0; DeleteFnPtr env = - CreateMainEnvironment(&exit_code); + CreateMainEnvironment(&exit_code, env_info); CHECK_NOT_NULL(env); - Context::Scope context_scope(env->context()); + { + Context::Scope context_scope(env->context()); - if (exit_code == 0) { - LoadEnvironment(env.get()); + if (exit_code == 0) { + LoadEnvironment(env.get()); - env->set_trace_sync_io(env->options()->trace_sync_io); + env->set_trace_sync_io(env->options()->trace_sync_io); - { - SealHandleScope seal(isolate_); - bool more; - env->performance_state()->Mark( - node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START); - do { - uv_run(env->event_loop(), UV_RUN_DEFAULT); + { + SealHandleScope seal(isolate_); + bool more; + env->performance_state()->Mark( + node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_START); + do { + uv_run(env->event_loop(), UV_RUN_DEFAULT); - per_process::v8_platform.DrainVMTasks(isolate_); + per_process::v8_platform.DrainVMTasks(isolate_); - more = uv_loop_alive(env->event_loop()); - if (more && !env->is_stopping()) continue; + more = uv_loop_alive(env->event_loop()); + if (more && !env->is_stopping()) continue; - if (!uv_loop_alive(env->event_loop())) { - EmitBeforeExit(env.get()); - } + if (!uv_loop_alive(env->event_loop())) { + EmitBeforeExit(env.get()); + } - // Emit `beforeExit` if the loop became alive either after emitting - // event, or after running some callbacks. - more = uv_loop_alive(env->event_loop()); - } while (more == true && !env->is_stopping()); - env->performance_state()->Mark( - node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT); - } + // Emit `beforeExit` if the loop became alive either after emitting + // event, or after running some callbacks. + more = uv_loop_alive(env->event_loop()); + } while (more == true && !env->is_stopping()); + env->performance_state()->Mark( + node::performance::NODE_PERFORMANCE_MILESTONE_LOOP_EXIT); + } - env->set_trace_sync_io(false); - exit_code = EmitExit(env.get()); - } + env->set_trace_sync_io(false); + exit_code = EmitExit(env.get()); + } - ResetStdio(); + ResetStdio(); - // TODO(addaleax): Neither NODE_SHARED_MODE nor HAVE_INSPECTOR really - // make sense here. + // TODO(addaleax): Neither NODE_SHARED_MODE nor HAVE_INSPECTOR really + // make sense here. #if HAVE_INSPECTOR && defined(__POSIX__) && !defined(NODE_SHARED_MODE) struct sigaction act; memset(&act, 0, sizeof(act)); @@ -183,12 +196,41 @@ int NodeMainInstance::Run() { #if defined(LEAK_SANITIZER) __lsan_do_leak_check(); #endif + } return exit_code; } +void DeserializeNodeInternalFields(Local holder, + int index, + v8::StartupData payload, + void* env) { + if (payload.raw_size == 0) { + holder->SetAlignedPointerInInternalField(index, nullptr); + return; + } + Environment* env_ptr = static_cast(env); + const InternalFieldInfo* info = + reinterpret_cast(payload.data); + switch (info->type) { + case InternalFieldType::kNoBindingData: { + per_process::Debug(DebugCategory::MKSNAPSHOT, + "Deserialize NoBindingData\n"); + NoBindingData* data = new NoBindingData(env_ptr, holder); + env_ptr->EnqueueDeserializeRequest(NoBindingData::Deserialize, + {data, info->Copy()}); + } + case InternalFieldType::kDefault: { + per_process::Debug(DebugCategory::MKSNAPSHOT, "Deserialize default\n"); + break; + } + default: { UNREACHABLE(); } + } +} + DeleteFnPtr -NodeMainInstance::CreateMainEnvironment(int* exit_code) { +NodeMainInstance::CreateMainEnvironment(int* exit_code, + const EnvSerializeInfo* env_info) { *exit_code = 0; // Reset the exit code to 0 HandleScope handle_scope(isolate_); @@ -200,32 +242,52 @@ NodeMainInstance::CreateMainEnvironment(int* exit_code) { } Local context; + DeleteFnPtr env; + if (deserialize_mode_) { - context = - Context::FromSnapshot(isolate_, kNodeContextIndex).ToLocalChecked(); + env.reset(new Environment(isolate_data_.get(), + isolate_, + args_, + exec_args_, + env_info, + EnvironmentFlags::kDefaultFlags, + {})); + context = Context::FromSnapshot(isolate_, + kNodeContextIndex, + {DeserializeNodeInternalFields, env.get()}) + .ToLocalChecked(); + InitializeContextRuntime(context); IsolateSettings s; SetIsolateErrorHandlers(isolate_, s); } else { context = NewContext(isolate_); + Context::Scope context_scope(context); + // TODO(joyeecheung): make this part of the boostrap context-independent + env.reset(new Environment(isolate_data_.get(), + context, + args_, + exec_args_, + nullptr, + EnvironmentFlags::kDefaultFlags, + {})); } CHECK(!context.IsEmpty()); Context::Scope context_scope(context); - DeleteFnPtr env { CreateEnvironment( - isolate_data_.get(), - context, - args_, - exec_args_, - EnvironmentFlags::kDefaultFlags) }; - - if (*exit_code != 0) { - return env; + if (deserialize_mode_) { + env->InitializeMainContext(context, env_info); + } else { + env->InitializeMainContext(context, nullptr); } - if (env == nullptr) { - *exit_code = 1; +#if HAVE_INSPECTOR + env->InitializeInspector({}); +#endif + + if (env->RunBootstrapping().IsEmpty()) { + return nullptr; } return env; diff --git a/src/node_main_instance.h b/src/node_main_instance.h index de1f4dca2b7cc6..6e38e95c26635c 100644 --- a/src/node_main_instance.h +++ b/src/node_main_instance.h @@ -14,6 +14,7 @@ namespace node { class ExternalReferenceRegistry; +struct EnvSerializeInfo; // TODO(joyeecheung): align this with the Worker/WorkerThreadData class. // We may be able to create an abstract class to reuse some of the routines. @@ -57,17 +58,18 @@ class NodeMainInstance { ~NodeMainInstance(); // Start running the Node.js instances, return the exit code when finished. - int Run(); + int Run(const EnvSerializeInfo* env_info); IsolateData* isolate_data() { return isolate_data_.get(); } DeleteFnPtr CreateMainEnvironment( - int* exit_code); + int* exit_code, const EnvSerializeInfo* env_info); // If nullptr is returned, the binary is not built with embedded // snapshot. static const std::vector* GetIsolateDataIndexes(); static v8::StartupData* GetEmbeddedSnapshotBlob(); + static const EnvSerializeInfo* GetEnvSerializeInfo(); static const std::vector& CollectExternalReferences(); static const size_t kNodeContextIndex = 0; diff --git a/src/node_perf.cc b/src/node_perf.cc index 4b8bf2a8a7c913..b26ddd6aa2a3bc 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -44,6 +44,52 @@ const uint64_t timeOrigin = PERFORMANCE_NOW(); const double timeOriginTimestamp = GetCurrentTimeInMicroseconds(); uint64_t performance_v8_start; +PerformanceState::PerformanceState(Isolate* isolate, + const PerformanceState::SerializeInfo* info) + : root(isolate, + sizeof(performance_state_internal), + info == nullptr ? nullptr : &(info->root)), + milestones(isolate, + offsetof(performance_state_internal, milestones), + NODE_PERFORMANCE_MILESTONE_INVALID, + root, + info == nullptr ? nullptr : &(info->milestones)), + observers(isolate, + offsetof(performance_state_internal, observers), + NODE_PERFORMANCE_ENTRY_TYPE_INVALID, + root, + info == nullptr ? nullptr : &(info->observers)) { + if (info == nullptr) { + for (size_t i = 0; i < milestones.Length(); i++) milestones[i] = -1.; + } +} + +PerformanceState::SerializeInfo PerformanceState::Serialize( + v8::Local context, v8::SnapshotCreator* creator) { + SerializeInfo info{root.Serialize(context, creator), + milestones.Serialize(context, creator), + observers.Serialize(context, creator)}; + return info; +} + +void PerformanceState::Deserialize(v8::Local context) { + root.Deserialize(context); + // This is just done to set up the pointers, we will actually reset + // all the milestones after deserialization. + milestones.Deserialize(context); + observers.Deserialize(context); +} + +std::ostream& operator<<(std::ostream& o, + const PerformanceState::SerializeInfo& i) { + o << "{\n" + << " " << i.root << ", // root\n" + << " " << i.milestones << ", // milestones\n" + << " " << i.observers << ", // observers\n" + << "}"; + return o; +} + void PerformanceState::Mark(enum PerformanceMilestone milestone, uint64_t ts) { this->milestones[milestone] = ts; @@ -117,8 +163,8 @@ void PerformanceEntry::Notify(Environment* env, Local object) { Context::Scope scope(env->context()); AliasedUint32Array& observers = env->performance_state()->observers; - if (type != NODE_PERFORMANCE_ENTRY_TYPE_INVALID && - observers[type]) { + if (!env->performance_entry_callback().IsEmpty() && + type != NODE_PERFORMANCE_ENTRY_TYPE_INVALID && observers[type]) { node::MakeCallback(env->isolate(), object.As(), env->performance_entry_callback(), diff --git a/src/node_perf_common.h b/src/node_perf_common.h index 75d266afc257e9..6c4c98813e71f0 100644 --- a/src/node_perf_common.h +++ b/src/node_perf_common.h @@ -8,6 +8,7 @@ #include "v8.h" #include +#include #include #include @@ -54,23 +55,17 @@ enum PerformanceEntryType { class PerformanceState { public: - explicit PerformanceState(v8::Isolate* isolate) : - root( - isolate, - sizeof(performance_state_internal)), - milestones( - isolate, - offsetof(performance_state_internal, milestones), - NODE_PERFORMANCE_MILESTONE_INVALID, - root), - observers( - isolate, - offsetof(performance_state_internal, observers), - NODE_PERFORMANCE_ENTRY_TYPE_INVALID, - root) { - for (size_t i = 0; i < milestones.Length(); i++) - milestones[i] = -1.; - } + struct SerializeInfo { + AliasedBufferInfo root; + AliasedBufferInfo milestones; + AliasedBufferInfo observers; + }; + + explicit PerformanceState(v8::Isolate* isolate, const SerializeInfo* info); + SerializeInfo Serialize(v8::Local context, + v8::SnapshotCreator* creator); + void Deserialize(v8::Local context); + friend std::ostream& operator<<(std::ostream& o, const SerializeInfo& i); AliasedUint8Array root; AliasedFloat64Array milestones; diff --git a/src/node_snapshot_stub.cc b/src/node_snapshot_stub.cc index fac03b0c87af5d..9d7b085994bf9f 100644 --- a/src/node_snapshot_stub.cc +++ b/src/node_snapshot_stub.cc @@ -14,4 +14,8 @@ const std::vector* NodeMainInstance::GetIsolateDataIndexes() { return nullptr; } +const EnvSerializeInfo* NodeMainInstance::GetEnvSerializeInfo() { + return nullptr; +} + } // namespace node diff --git a/tools/snapshot/snapshot_builder.cc b/tools/snapshot/snapshot_builder.cc index 7d292391ab334a..1e3c620341d5f1 100644 --- a/tools/snapshot/snapshot_builder.cc +++ b/tools/snapshot/snapshot_builder.cc @@ -1,6 +1,7 @@ #include "snapshot_builder.h" #include #include +#include "debug_utils-inl.h" #include "env-inl.h" #include "node_external_reference.h" #include "node_internals.h" @@ -12,6 +13,8 @@ namespace node { using v8::Context; using v8::HandleScope; using v8::Isolate; +using v8::Local; +using v8::Object; using v8::SnapshotCreator; using v8::StartupData; @@ -23,11 +26,16 @@ void WriteVector(std::stringstream* ss, const T* vec, size_t size) { } std::string FormatBlob(v8::StartupData* blob, - const std::vector& isolate_data_indexes) { + const std::vector& isolate_data_indexes, + const EnvSerializeInfo& env_info) { std::stringstream ss; + // TODO(joyeechenug): aliased_buffer.h implicitly depends on util-inl.h, + // fix it. ss << R"(#include +#include "env.h" #include "node_main_instance.h" +#include "util-inl.h" #include "v8.h" // This file is generated by tools/snapshot. Do not edit. @@ -56,17 +64,59 @@ static const std::vector isolate_data_indexes { const std::vector* NodeMainInstance::GetIsolateDataIndexes() { return &isolate_data_indexes; } + +static const EnvSerializeInfo env_info )" + << env_info << R"(; + +const EnvSerializeInfo* NodeMainInstance::GetEnvSerializeInfo() { + return &env_info; +} + } // namespace node )"; return ss.str(); } +static v8::StartupData SerializeNodeContextInternalFields(Local holder, + int index, + void* env) { + void* ptr = holder->GetAlignedPointerFromInternalField(index); + if (ptr == nullptr) { + return StartupData{reinterpret_cast(0), 0}; + } + // TODO(joyee): add more types for other objects with embedder fields. + if (index == BaseObject::kSlot) { + BaseObject* obj = static_cast(ptr); + switch (obj->type()) { + case InternalFieldType::kNoBindingData: { + per_process::Debug(DebugCategory::MKSNAPSHOT, + "Serializing NoBindingData with index %d at %p\n", + index, + ptr); + InternalFieldInfo* info = new InternalFieldInfo{obj->type(), 0}; + return StartupData{reinterpret_cast(info), sizeof(*info)}; + } + case InternalFieldType::kDefault: { + per_process::Debug(DebugCategory::MKSNAPSHOT, + "Serializing default with index %d at %p\n", + index, + ptr); + InternalFieldInfo* info = new InternalFieldInfo{obj->type(), 0}; + return StartupData{reinterpret_cast(info), sizeof(*info)}; + }; + default: { UNREACHABLE(); } + } + } + // TODO(joyee): add more types for other objects with embedder fields. + InternalFieldInfo* info = + new InternalFieldInfo{InternalFieldType::kDefault, 0}; + return StartupData{reinterpret_cast(info), sizeof(*info)}; +} + std::string SnapshotBuilder::Generate( const std::vector args, const std::vector exec_args) { - const std::vector& external_references = - NodeMainInstance::CollectExternalReferences(); Isolate* isolate = Isolate::Allocate(); per_process::v8_platform.Platform()->RegisterIsolate(isolate, uv_default_loop()); @@ -75,7 +125,12 @@ std::string SnapshotBuilder::Generate( { std::vector isolate_data_indexes; + EnvSerializeInfo env_info; + + const std::vector& external_references = + NodeMainInstance::CollectExternalReferences(); SnapshotCreator creator(isolate, external_references.data()); + Environment* env; { main_instance = NodeMainInstance::Create(isolate, @@ -83,22 +138,43 @@ std::string SnapshotBuilder::Generate( per_process::v8_platform.Platform(), args, exec_args); + HandleScope scope(isolate); creator.SetDefaultContext(Context::New(isolate)); isolate_data_indexes = main_instance->isolate_data()->Serialize(&creator); - size_t index = creator.AddContext(NewContext(isolate)); + Local context = NewContext(isolate); + Context::Scope context_scope(context); + + env = new Environment(main_instance->isolate_data(), + context, + args, + exec_args, + nullptr, + node::EnvironmentFlags::kDefaultFlags, + {}); + if (per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) { + env->PrintAllBaseObjects(); + printf("Environment = %p\n", env); + } + env_info = env->Serialize(&creator); + size_t index = creator.AddContext( + context, {SerializeNodeContextInternalFields, env}); CHECK_EQ(index, NodeMainInstance::kNodeContextIndex); + // env->RunCleanup(); // will be necessary when we initiliaze libuv } + env->ReleaseContext(); // Must be out of HandleScope StartupData blob = creator.CreateBlob(SnapshotCreator::FunctionCodeHandling::kClear); CHECK(blob.CanBeRehashed()); // Must be done while the snapshot creator isolate is entered i.e. the // creator is still alive. + env->set_stopping(true); + delete env; main_instance->Dispose(); - result = FormatBlob(&blob, isolate_data_indexes); + result = FormatBlob(&blob, isolate_data_indexes, env_info); delete[] blob.data; }