From 3fce8a83d76fd0b4ec8770500de4291bf6a771ad Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 8 Apr 2020 17:14:00 +0200 Subject: [PATCH 1/8] src: introduce BaseObject base FunctionTemplate This enables us to tell whether a JS bindings object is associated with a `BaseObject` or not. --- src/async_wrap.cc | 1 + src/base_object-inl.h | 1 + src/base_object.h | 3 +++ src/env.cc | 11 +++++++++++ src/env.h | 1 + src/module_wrap.cc | 1 + src/node_crypto.cc | 9 +++++++++ src/node_i18n.cc | 1 + src/node_perf.cc | 1 + src/node_serdes.cc | 2 ++ src/node_trace_events.cc | 1 + src/node_util.cc | 1 + src/node_wasi.cc | 1 + 13 files changed, 34 insertions(+) diff --git a/src/async_wrap.cc b/src/async_wrap.cc index 42837e09818ec2..907d8c5223cd2c 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -469,6 +469,7 @@ Local AsyncWrap::GetConstructorTemplate(Environment* env) { if (tmpl.IsEmpty()) { tmpl = env->NewFunctionTemplate(nullptr); tmpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "AsyncWrap")); + tmpl->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(tmpl, "getAsyncId", AsyncWrap::GetAsyncId); env->SetProtoMethod(tmpl, "asyncReset", AsyncWrap::AsyncReset); env->SetProtoMethod(tmpl, "getProviderType", AsyncWrap::GetProviderType); diff --git a/src/base_object-inl.h b/src/base_object-inl.h index 60ccf81cc559c5..6220babe920f4f 100644 --- a/src/base_object-inl.h +++ b/src/base_object-inl.h @@ -156,6 +156,7 @@ BaseObject::MakeLazilyInitializedJSTemplate(Environment* env) { }; v8::Local t = env->NewFunctionTemplate(constructor); + t->Inherit(BaseObject::GetConstructorTemplate(env)); t->InstanceTemplate()->SetInternalFieldCount( BaseObject::kInternalFieldCount); return t; diff --git a/src/base_object.h b/src/base_object.h index 03c69976ee4a68..125c9f795f4474 100644 --- a/src/base_object.h +++ b/src/base_object.h @@ -98,6 +98,9 @@ class BaseObject : public MemoryRetainer { // a BaseObjectPtr to this object. inline void Detach(); + static v8::Local GetConstructorTemplate( + Environment* env); + protected: virtual inline void OnGCCollect(); diff --git a/src/env.cc b/src/env.cc index 3efa5c3b9c98ab..ff5212c0d1121f 100644 --- a/src/env.cc +++ b/src/env.cc @@ -270,6 +270,7 @@ void Environment::CreateProperties() { Local templ = FunctionTemplate::New(isolate()); templ->InstanceTemplate()->SetInternalFieldCount( BaseObject::kInternalFieldCount); + templ->Inherit(BaseObject::GetConstructorTemplate(this)); set_binding_data_ctor_template(templ); } @@ -1195,4 +1196,14 @@ Local BaseObject::WrappedObject() const { return object(); } +Local BaseObject::GetConstructorTemplate(Environment* env) { + Local tmpl = env->base_object_ctor_template(); + if (tmpl.IsEmpty()) { + tmpl = env->NewFunctionTemplate(nullptr); + tmpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "BaseObject")); + env->set_base_object_ctor_template(tmpl); + } + return tmpl; +} + } // namespace node diff --git a/src/env.h b/src/env.h index 146754d03ab728..af813ef720714c 100644 --- a/src/env.h +++ b/src/env.h @@ -392,6 +392,7 @@ constexpr size_t kFsStatsBufferLength = #define ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) \ V(async_wrap_ctor_template, v8::FunctionTemplate) \ V(async_wrap_object_ctor_template, v8::FunctionTemplate) \ + V(base_object_ctor_template, v8::FunctionTemplate) \ V(binding_data_ctor_template, v8::FunctionTemplate) \ V(compiled_fn_entry_template, v8::ObjectTemplate) \ V(dir_instance_template, v8::ObjectTemplate) \ diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 13dd331e6b23e1..d32dda14dcd4a1 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -691,6 +691,7 @@ void ModuleWrap::Initialize(Local target, tpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "ModuleWrap")); tpl->InstanceTemplate()->SetInternalFieldCount( ModuleWrap::kInternalFieldCount); + tpl->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(tpl, "link", Link); env->SetProtoMethod(tpl, "instantiate", Instantiate); diff --git a/src/node_crypto.cc b/src/node_crypto.cc index d53a6d2f2325fe..f08c89b427b307 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -454,6 +454,7 @@ void SecureContext::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); t->InstanceTemplate()->SetInternalFieldCount( SecureContext::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); Local secureContextString = FIXED_ONE_BYTE_STRING(env->isolate(), "SecureContext"); t->SetClassName(secureContextString); @@ -3242,6 +3243,7 @@ Local KeyObject::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); t->InstanceTemplate()->SetInternalFieldCount( KeyObject::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "init", Init); env->SetProtoMethodNoSideEffect(t, "getSymmetricKeySize", @@ -3476,6 +3478,7 @@ void CipherBase::Initialize(Environment* env, Local target) { t->InstanceTemplate()->SetInternalFieldCount( CipherBase::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "init", Init); env->SetProtoMethod(t, "initiv", InitIv); @@ -4090,6 +4093,7 @@ void Hmac::Initialize(Environment* env, Local target) { t->InstanceTemplate()->SetInternalFieldCount( Hmac::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "init", HmacInit); env->SetProtoMethod(t, "update", HmacUpdate); @@ -4202,6 +4206,7 @@ void Hash::Initialize(Environment* env, Local target) { t->InstanceTemplate()->SetInternalFieldCount( Hash::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "update", HashUpdate); env->SetProtoMethod(t, "digest", HashDigest); @@ -4458,6 +4463,7 @@ void Sign::Initialize(Environment* env, Local target) { t->InstanceTemplate()->SetInternalFieldCount( SignBase::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "init", SignInit); env->SetProtoMethod(t, "update", SignUpdate); @@ -4780,6 +4786,7 @@ void Verify::Initialize(Environment* env, Local target) { t->InstanceTemplate()->SetInternalFieldCount( SignBase::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "init", VerifyInit); env->SetProtoMethod(t, "update", VerifyUpdate); @@ -5090,6 +5097,7 @@ void DiffieHellman::Initialize(Environment* env, Local target) { t->InstanceTemplate()->SetInternalFieldCount( DiffieHellman::kInternalFieldCount); + t->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(t, "generateKeys", GenerateKeys); env->SetProtoMethod(t, "computeSecret", ComputeSecret); @@ -5448,6 +5456,7 @@ void ECDH::Initialize(Environment* env, Local target) { HandleScope scope(env->isolate()); Local t = env->NewFunctionTemplate(New); + t->Inherit(BaseObject::GetConstructorTemplate(env)); t->InstanceTemplate()->SetInternalFieldCount(ECDH::kInternalFieldCount); diff --git a/src/node_i18n.cc b/src/node_i18n.cc index 169374aa5de441..5382e469a4087c 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -811,6 +811,7 @@ void Initialize(Local target, // ConverterObject { Local t = FunctionTemplate::New(env->isolate()); + t->Inherit(BaseObject::GetConstructorTemplate(env)); t->InstanceTemplate()->SetInternalFieldCount( ConverterObject::kInternalFieldCount); Local converter_string = diff --git a/src/node_perf.cc b/src/node_perf.cc index 4b8bf2a8a7c913..40ea9daffa19f5 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -647,6 +647,7 @@ void Initialize(Local target, eldh->SetClassName(eldh_classname); eldh->InstanceTemplate()->SetInternalFieldCount( ELDHistogram::kInternalFieldCount); + eldh->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(eldh, "exceeds", ELDHistogramExceeds); env->SetProtoMethod(eldh, "min", ELDHistogramMin); env->SetProtoMethod(eldh, "max", ELDHistogramMax); diff --git a/src/node_serdes.cc b/src/node_serdes.cc index 86fb822dd5bfa9..5a47be10a42e83 100644 --- a/src/node_serdes.cc +++ b/src/node_serdes.cc @@ -452,6 +452,7 @@ void Initialize(Local target, ser->InstanceTemplate()->SetInternalFieldCount( SerializerContext::kInternalFieldCount); + ser->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(ser, "writeHeader", SerializerContext::WriteHeader); env->SetProtoMethod(ser, "writeValue", SerializerContext::WriteValue); @@ -479,6 +480,7 @@ void Initialize(Local target, des->InstanceTemplate()->SetInternalFieldCount( DeserializerContext::kInternalFieldCount); + des->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(des, "readHeader", DeserializerContext::ReadHeader); env->SetProtoMethod(des, "readValue", DeserializerContext::ReadValue); diff --git a/src/node_trace_events.cc b/src/node_trace_events.cc index 9adee9e458ccc0..58813a9083a560 100644 --- a/src/node_trace_events.cc +++ b/src/node_trace_events.cc @@ -131,6 +131,7 @@ void NodeCategorySet::Initialize(Local target, env->NewFunctionTemplate(NodeCategorySet::New); category_set->InstanceTemplate()->SetInternalFieldCount( NodeCategorySet::kInternalFieldCount); + category_set->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(category_set, "enable", NodeCategorySet::Enable); env->SetProtoMethod(category_set, "disable", NodeCategorySet::Disable); diff --git a/src/node_util.cc b/src/node_util.cc index db9b8ec8d65f51..96900037095f1f 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -326,6 +326,7 @@ void Initialize(Local target, weak_ref->InstanceTemplate()->SetInternalFieldCount( WeakReference::kInternalFieldCount); weak_ref->SetClassName(weak_ref_string); + weak_ref->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(weak_ref, "get", WeakReference::Get); env->SetProtoMethod(weak_ref, "incRef", WeakReference::IncRef); env->SetProtoMethod(weak_ref, "decRef", WeakReference::DecRef); diff --git a/src/node_wasi.cc b/src/node_wasi.cc index 909023d84e0235..125f44f01d905d 100644 --- a/src/node_wasi.cc +++ b/src/node_wasi.cc @@ -1825,6 +1825,7 @@ static void Initialize(Local target, auto wasi_wrap_string = FIXED_ONE_BYTE_STRING(env->isolate(), "WASI"); tmpl->InstanceTemplate()->SetInternalFieldCount(WASI::kInternalFieldCount); tmpl->SetClassName(wasi_wrap_string); + tmpl->Inherit(BaseObject::GetConstructorTemplate(env)); env->SetProtoMethod(tmpl, "args_get", WASI::ArgsGet); env->SetProtoMethod(tmpl, "args_sizes_get", WASI::ArgsSizesGet); From 1ce19a5ff8af7f42658b2f42191601f463b7712a Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 31 Mar 2020 23:27:15 +0200 Subject: [PATCH 2/8] src: collect external references for snapshot Gather external references from the different native modules that are loaded during bootstrapping, for inclusion in the snapshot. --- node.gyp | 3 +++ src/async_wrap.cc | 18 +++++++++++++ src/inspector_js_api.cc | 26 +++++++++++++++++++ src/node.cc | 15 ++++++++--- src/node_buffer.cc | 32 +++++++++++++++++++++++ src/node_credentials.cc | 20 +++++++++++++++ src/node_env_var.cc | 12 +++++++++ src/node_errors.cc | 11 ++++++++ src/node_i18n.cc | 15 +++++++++++ src/node_native_module_env.cc | 10 ++++++++ src/node_native_module_env.h | 1 - src/node_process_methods.cc | 28 ++++++++++++++++++++ src/node_task_queue.cc | 9 +++++++ src/node_trace_events.cc | 10 ++++++++ src/node_types.cc | 10 ++++++++ src/node_url.cc | 12 +++++++++ src/node_util.cc | 21 ++++++++++++++- src/snapshot_support-inl.h | 28 ++++++++++++++++++++ src/snapshot_support.cc | 37 +++++++++++++++++++++++++++ src/snapshot_support.h | 41 ++++++++++++++++++++++++++++++ src/string_decoder.cc | 7 +++++ src/timers.cc | 9 +++++++ tools/snapshot/snapshot_builder.cc | 8 +++--- 23 files changed, 373 insertions(+), 10 deletions(-) create mode 100644 src/snapshot_support-inl.h create mode 100644 src/snapshot_support.cc create mode 100644 src/snapshot_support.h diff --git a/node.gyp b/node.gyp index 3dadad15c9e193..bddba36ad716b8 100644 --- a/node.gyp +++ b/node.gyp @@ -616,6 +616,7 @@ 'src/pipe_wrap.cc', 'src/process_wrap.cc', 'src/signal_wrap.cc', + 'src/snapshot_support.cc', 'src/spawn_sync.cc', 'src/stream_base.cc', 'src/stream_pipe.cc', @@ -707,6 +708,8 @@ 'src/pipe_wrap.h', 'src/req_wrap.h', 'src/req_wrap-inl.h', + 'src/snapshot_support.h', + 'src/snapshot_support-inl.h', 'src/spawn_sync.h', 'src/stream_base.h', 'src/stream_base-inl.h', diff --git a/src/async_wrap.cc b/src/async_wrap.cc index 907d8c5223cd2c..08111e248b5bd6 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -24,6 +24,7 @@ #include "env-inl.h" #include "node_errors.h" #include "tracing/traced_value.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include "v8.h" @@ -580,6 +581,23 @@ void AsyncWrap::Initialize(Local target, ->GetFunction(env->context()).ToLocalChecked()).Check(); } +static ExternalReferences external_references { + __FILE__, + SetupHooks, + AsyncWrap::PushAsyncContext, + AsyncWrap::PopAsyncContext, + AsyncWrap::QueueDestroyAsyncId, + EnablePromiseHook, + DisablePromiseHook, + RegisterDestroyHook, + AsyncWrapObject::New, + AsyncWrap::GetAsyncId, + // AsyncReset is overloaded, pick the right one + static_cast& args)>( + AsyncWrap::AsyncReset), + AsyncWrap::GetProviderType, + PromiseWrap::getIsChainedPromise, +}; AsyncWrap::AsyncWrap(Environment* env, Local object, diff --git a/src/inspector_js_api.cc b/src/inspector_js_api.cc index 5c9ad5e946424b..a8b95fc40d3a9d 100644 --- a/src/inspector_js_api.cc +++ b/src/inspector_js_api.cc @@ -2,6 +2,7 @@ #include "inspector_agent.h" #include "inspector_io.h" #include "memory_tracker-inl.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include "v8.h" #include "v8-inspector.h" @@ -345,6 +346,31 @@ void Initialize(Local target, Local unused, JSBindingsConnection::Bind(env, target); } +static ExternalReferences external_references { + __FILE__, + InspectorConsoleCall, + SetConsoleExtensionInstaller, + CallAndPauseOnStart, + Open, + Url, + WaitForDebugger, + AsyncTaskScheduledWrapper, + InvokeAsyncTaskFnWithId<&Agent::AsyncTaskCanceled>, + InvokeAsyncTaskFnWithId<&Agent::AsyncTaskStarted>, + InvokeAsyncTaskFnWithId<&Agent::AsyncTaskFinished>, + RegisterAsyncHookWrapper, + IsEnabled, + JSBindingsConnection::New, + JSBindingsConnection::Dispatch, + // Disconnect is overloaded, pick the right one + static_cast& args)>( + JSBindingsConnection::Disconnect), + JSBindingsConnection::New, + JSBindingsConnection::Dispatch, + static_cast& args)>( + JSBindingsConnection::Disconnect), +}; + } // namespace } // namespace inspector } // namespace node diff --git a/src/node.cc b/src/node.cc index 240f1f2e3b00f3..d2b365cb0ca39f 100644 --- a/src/node.cc +++ b/src/node.cc @@ -39,6 +39,7 @@ #include "node_revert.h" #include "node_v8_platform-inl.h" #include "node_version.h" +#include "snapshot_support-inl.h" #if HAVE_OPENSSL #include "node_crypto.h" @@ -1028,16 +1029,14 @@ int Start(int argc, char** argv) { { Isolate::CreateParams params; const std::vector* indexes = nullptr; - std::vector external_references; + std::vector external_references = ExternalReferences::get_list(); + external_references.push_back(ExternalReferences::kEnd); 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.push_back(reinterpret_cast(nullptr)); params.external_references = external_references.data(); params.snapshot_blob = blob; indexes = NodeMainInstance::GetIsolateDataIndexes(); @@ -1062,6 +1061,14 @@ int Stop(Environment* env) { return 0; } +static ExternalReferences external_references { + __FILE__, + binding::GetLinkedBinding, + binding::GetInternalBinding, + MarkBootstrapComplete, +}; + + } // namespace node #if !HAVE_INSPECTOR diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 1ff60ad721753e..ecae817eae7b71 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -28,6 +28,7 @@ #include "string_bytes.h" #include "string_search.h" #include "util-inl.h" +#include "snapshot_support-inl.h" #include "v8.h" #include @@ -1222,6 +1223,37 @@ void Initialize(Local target, } } +static ExternalReferences external_references { + __FILE__, + SetBufferPrototype, + CreateFromString, + ByteLengthUtf8, + Copy, + Compare, + CompareOffset, + Fill, + IndexOfBuffer, + IndexOfNumber, + IndexOfString, + Swap16, + Swap32, + Swap64, + EncodeInto, + EncodeUtf8String, + StringSlice, + StringSlice, + StringSlice, + StringSlice, + StringSlice, + StringSlice, + StringWrite, + StringWrite, + StringWrite, + StringWrite, + StringWrite, + StringWrite, +}; + } // anonymous namespace } // namespace Buffer } // namespace node diff --git a/src/node_credentials.cc b/src/node_credentials.cc index ad0e1dbb9bb68e..8bbdc0db1b1fdb 100644 --- a/src/node_credentials.cc +++ b/src/node_credentials.cc @@ -1,5 +1,6 @@ #include "env-inl.h" #include "node_internals.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS @@ -401,6 +402,25 @@ static void Initialize(Local target, #endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS } +static ExternalReferences external_references { + __FILE__, + // SafeGetenv is overloaded, pick the right one + static_cast& args)>(SafeGetenv), +#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS + GetUid, + GetEUid, + GetGid, + GetEGid, + GetGroups, + InitGroups, + SetEGid, + SetEUid, + SetGid, + SetUid, + SetGroups, +#endif +}; + } // namespace credentials } // namespace node diff --git a/src/node_env_var.cc b/src/node_env_var.cc index 23eaad48586130..7922b07b5795b4 100644 --- a/src/node_env_var.cc +++ b/src/node_env_var.cc @@ -2,6 +2,7 @@ #include "env-inl.h" #include "node_errors.h" #include "node_process.h" +#include "snapshot_support-inl.h" #include // tzset(), _tzset() @@ -385,4 +386,15 @@ MaybeLocal CreateEnvVarProxy(Local context, Isolate* isolate) { PropertyHandlerFlags::kHasNoSideEffect)); return scope.EscapeMaybe(env_proxy_template->NewInstance(context)); } + + +static ExternalReferences external_references { + __FILE__, + EnvGetter, + EnvSetter, + EnvQuery, + EnvDeleter, + EnvEnumerator +}; + } // namespace node diff --git a/src/node_errors.cc b/src/node_errors.cc index 4e13c24e15e1d0..c010e0888fe956 100644 --- a/src/node_errors.cc +++ b/src/node_errors.cc @@ -7,6 +7,7 @@ #include "node_report.h" #include "node_process.h" #include "node_v8_platform-inl.h" +#include "snapshot_support-inl.h" #include "util-inl.h" namespace node { @@ -848,6 +849,16 @@ void Initialize(Local target, env->SetMethod(target, "triggerUncaughtException", TriggerUncaughtException); } +static ExternalReferences external_references { + __FILE__, + SetPrepareStackTraceCallback, + SetEnhanceStackForFatalException, + NoSideEffectsToString, + // TriggerUncaughtException is overloaded, pick the right one + static_cast& args)>( + TriggerUncaughtException), +}; + void DecorateErrorStack(Environment* env, const errors::TryCatchScope& try_catch) { Local exception = try_catch.Exception(); diff --git a/src/node_i18n.cc b/src/node_i18n.cc index 5382e469a4087c..4acc7f46053755 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -49,6 +49,7 @@ #include "node_buffer.h" #include "node_errors.h" #include "node_internals.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include "v8.h" @@ -825,6 +826,20 @@ void Initialize(Local target, env->SetMethod(target, "hasConverter", ConverterObject::Has); } +static ExternalReferences external_references { + __FILE__, + // Pick the right variant for overloaded names. + static_cast& args)>(ToUnicode), + static_cast& args)>(ToASCII), + GetStringWidth, + ICUErrorName, + static_cast& args)>(Transcode), + ConverterObject::Create, + ConverterObject::Decode, + ConverterObject::Has, +}; + + } // namespace i18n } // namespace node diff --git a/src/node_native_module_env.cc b/src/node_native_module_env.cc index be647b01c640b0..140b7d1fb234d6 100644 --- a/src/node_native_module_env.cc +++ b/src/node_native_module_env.cc @@ -1,4 +1,5 @@ #include "node_native_module_env.h" +#include "snapshot_support-inl.h" #include "env-inl.h" namespace node { @@ -202,6 +203,15 @@ void NativeModuleEnv::Initialize(Local target, target->SetIntegrityLevel(context, IntegrityLevel::kFrozen).FromJust(); } +static ExternalReferences external_references { + __FILE__, + NativeModuleEnv::ConfigStringGetter, + NativeModuleEnv::ModuleIdsGetter, + NativeModuleEnv::GetModuleCategories, + NativeModuleEnv::GetCacheUsage, + NativeModuleEnv::CompileFunction, +}; + } // namespace native_module } // namespace node diff --git a/src/node_native_module_env.h b/src/node_native_module_env.h index bc36be75109639..b9e8132adee0eb 100644 --- a/src/node_native_module_env.h +++ b/src/node_native_module_env.h @@ -37,7 +37,6 @@ class NativeModuleEnv { // in node_code_cache_stub.cc static void InitializeCodeCache(); - private: static void RecordResult(const char* id, NativeModuleLoader::Result result, Environment* env); diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index 88f4c1cfbd0249..ac483029dc0df1 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -5,6 +5,7 @@ #include "node_errors.h" #include "node_internals.h" #include "node_process.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include "uv.h" #include "v8.h" @@ -492,6 +493,33 @@ static void InitializeProcessMethods(Local target, env->SetMethod(target, "patchProcessObject", PatchProcessObject); } +static ExternalReferences external_references { + __FILE__, + DebugProcess, + DebugEnd, + // Abort is overloaded, pick the right one + static_cast& args)>(Abort), + CauseSegfault, + Chdir, + StartProfilerIdleNotifier, + StopProfilerIdleNotifier, + Umask, + RawDebug, + MemoryUsage, + CPUUsage, + Hrtime, + HrtimeBigInt, + ResourceUsage, + GetActiveHandles, + GetActiveRequests, + Kill, + Cwd, + binding::DLOpen, + ReallyExit, + Uptime, + PatchProcessObject, +}; + } // namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL(process_methods, diff --git a/src/node_task_queue.cc b/src/node_task_queue.cc index 3c7d9bae0884c5..1b9144bfeb523a 100644 --- a/src/node_task_queue.cc +++ b/src/node_task_queue.cc @@ -3,6 +3,7 @@ #include "node_errors.h" #include "node_internals.h" #include "node_process.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include "v8.h" @@ -145,6 +146,14 @@ static void Initialize(Local target, SetPromiseRejectCallback); } +static ExternalReferences external_references { + __FILE__, + EnqueueMicrotask, + SetTickCallback, + RunMicrotasks, + SetPromiseRejectCallback, +}; + } // namespace task_queue } // namespace node diff --git a/src/node_trace_events.cc b/src/node_trace_events.cc index 58813a9083a560..7fae020bbf0240 100644 --- a/src/node_trace_events.cc +++ b/src/node_trace_events.cc @@ -5,6 +5,7 @@ #include "node_internals.h" #include "node_v8_platform-inl.h" #include "tracing/agent.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include @@ -154,6 +155,15 @@ void NodeCategorySet::Initialize(Local target, binding->Get(context, trace).ToLocalChecked()).Check(); } +static ExternalReferences external_references { + __FILE__, + GetEnabledCategories, + SetTraceCategoryStateUpdateHandler, + NodeCategorySet::New, + NodeCategorySet::Enable, + NodeCategorySet::Disable, +}; + } // namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL(trace_events, diff --git a/src/node_types.cc b/src/node_types.cc index 9643a66668144e..c906aa5dfc8871 100644 --- a/src/node_types.cc +++ b/src/node_types.cc @@ -1,5 +1,6 @@ #include "env-inl.h" #include "node.h" +#include "snapshot_support-inl.h" using v8::Context; using v8::FunctionCallbackInfo; @@ -76,6 +77,15 @@ void InitializeTypes(Local target, env->SetMethodNoSideEffect(target, "isBoxedPrimitive", IsBoxedPrimitive); } +static ExternalReferences external_references { + __FILE__, +#define V(type) Is##type, + VALUE_METHOD_MAP(V) +#undef V + IsAnyArrayBuffer, + IsBoxedPrimitive, +}; + } // anonymous namespace } // namespace node diff --git a/src/node_url.cc b/src/node_url.cc index 27e89e8d9b7652..83b97acf3b9285 100644 --- a/src/node_url.cc +++ b/src/node_url.cc @@ -2,6 +2,7 @@ #include "base_object-inl.h" #include "node_errors.h" #include "node_i18n.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include @@ -2331,6 +2332,17 @@ void Initialize(Local target, PARSESTATES(XX) #undef XX } + +static ExternalReferences external_references { + __FILE__, + // Parse is overloaded, pick the right one + static_cast& args)>(Parse), + EncodeAuthSet, + ToUSVString, + DomainToASCII, + DomainToUnicode, + SetURLConstructor, +}; } // namespace std::string URL::ToFilePath() const { diff --git a/src/node_util.cc b/src/node_util.cc index 96900037095f1f..11dd7cc82184ce 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -1,6 +1,7 @@ +#include "base_object-inl.h" #include "node_errors.h" +#include "snapshot_support-inl.h" #include "util-inl.h" -#include "base_object-inl.h" namespace node { namespace util { @@ -336,6 +337,24 @@ void Initialize(Local target, env->SetMethod(target, "guessHandleType", GuessHandleType); } +static ExternalReferences external_references { + __FILE__, + GetHiddenValue, + SetHiddenValue, + GetPromiseDetails, + GetProxyDetails, + PreviewEntries, + GetOwnNonIndexProperties, + GetConstructorName, + Sleep, + ArrayBufferViewHasBuffer, + WeakReference::New, + WeakReference::Get, + WeakReference::IncRef, + WeakReference::DecRef, + GuessHandleType, +}; + } // namespace util } // namespace node diff --git a/src/snapshot_support-inl.h b/src/snapshot_support-inl.h new file mode 100644 index 00000000000000..a4320153a419bd --- /dev/null +++ b/src/snapshot_support-inl.h @@ -0,0 +1,28 @@ +#ifndef SRC_SNAPSHOT_SUPPORT_INL_H_ +#define SRC_SNAPSHOT_SUPPORT_INL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "snapshot_support.h" + +namespace node { + +template +ExternalReferences::ExternalReferences(const char* id, Args*... args) { + Register(id, this); + HandleArgs(args...); +} + +void ExternalReferences::HandleArgs() {} + +template +void ExternalReferences::HandleArgs(T* ptr, Args*... args) { + AddPointer(reinterpret_cast(ptr)); + HandleArgs(args...); +} + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_SNAPSHOT_SUPPORT_INL_H_ diff --git a/src/snapshot_support.cc b/src/snapshot_support.cc new file mode 100644 index 00000000000000..d583c833199010 --- /dev/null +++ b/src/snapshot_support.cc @@ -0,0 +1,37 @@ +#include "snapshot_support.h" // NOLINT(build/include_inline) +#include "snapshot_support-inl.h" +#include "util.h" + +namespace node { + +void ExternalReferences::AddPointer(intptr_t ptr) { + DCHECK_NE(ptr, kEnd); + references_.push_back(ptr); +} + +std::map* ExternalReferences::map() { + static std::map map_; + return &map_; +} + +std::vector ExternalReferences::get_list() { + static std::vector list; + if (list.empty()) { + for (const auto& entry : *map()) { + std::vector* source = &entry.second->references_; + list.insert(list.end(), source->begin(), source->end()); + source->clear(); + source->shrink_to_fit(); + } + } + return list; +} + +void ExternalReferences::Register(const char* id, ExternalReferences* self) { + auto result = map()->insert({id, this}); + CHECK(result.second); +} + +const intptr_t ExternalReferences::kEnd = reinterpret_cast(nullptr); + +} // namespace node diff --git a/src/snapshot_support.h b/src/snapshot_support.h new file mode 100644 index 00000000000000..ba4dbff0200e99 --- /dev/null +++ b/src/snapshot_support.h @@ -0,0 +1,41 @@ +#ifndef SRC_SNAPSHOT_SUPPORT_H_ +#define SRC_SNAPSHOT_SUPPORT_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "v8.h" +#include + +namespace node { + +class ExternalReferences { + public: + // Create static instances of this class to register a list of external + // references for usage in snapshotting. Usually, this includes all C++ + // binding functions. `id` can be any string, as long as it is unique + // (e.g. the current file name as retrieved by __FILE__). + template + inline ExternalReferences(const char* id, Args*... args); + + // Returns the list of all references collected so far, not yet terminated + // by kEnd. + static std::vector get_list(); + + static const intptr_t kEnd; // The end-of-list marker used by V8, nullptr. + + private: + void Register(const char* id, ExternalReferences* self); + static std::map* map(); + std::vector references_; + + void AddPointer(intptr_t ptr); + inline void HandleArgs(); + template + inline void HandleArgs(T* ptr, Args*... args); +}; + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_SNAPSHOT_SUPPORT_H_ diff --git a/src/string_decoder.cc b/src/string_decoder.cc index 6ec84e0e11ed31..2c4eed7bb328d5 100644 --- a/src/string_decoder.cc +++ b/src/string_decoder.cc @@ -4,6 +4,7 @@ #include "env-inl.h" #include "node_buffer.h" #include "string_bytes.h" +#include "snapshot_support-inl.h" #include "util.h" using v8::Array; @@ -320,6 +321,12 @@ void InitializeStringDecoder(Local target, env->SetMethod(target, "flush", FlushData); } +static ExternalReferences external_references { + __FILE__, + DecodeData, + FlushData +}; + } // anonymous namespace } // namespace node diff --git a/src/timers.cc b/src/timers.cc index fab1b12018a921..9ce2b05524ff02 100644 --- a/src/timers.cc +++ b/src/timers.cc @@ -1,4 +1,5 @@ #include "env-inl.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include "v8.h" @@ -58,6 +59,14 @@ void Initialize(Local target, env->immediate_info()->fields().GetJSArray()).Check(); } +static ExternalReferences external_references { + __FILE__, + GetLibuvNow, + SetupTimers, + ScheduleTimer, + ToggleTimerRef, + ToggleImmediateRef, +}; } // anonymous namespace } // namespace node diff --git a/tools/snapshot/snapshot_builder.cc b/tools/snapshot/snapshot_builder.cc index 8a97513ba905fc..391cdf01e09256 100644 --- a/tools/snapshot/snapshot_builder.cc +++ b/tools/snapshot/snapshot_builder.cc @@ -4,6 +4,7 @@ #include "node_internals.h" #include "node_main_instance.h" #include "node_v8_platform-inl.h" +#include "snapshot_support-inl.h" namespace node { @@ -63,16 +64,15 @@ const std::vector* NodeMainInstance::GetIsolateDataIndexes() { std::string SnapshotBuilder::Generate( const std::vector args, const std::vector exec_args) { - // TODO(joyeecheung): collect external references and set it in - // params.external_references. - std::vector external_references = { - reinterpret_cast(nullptr)}; Isolate* isolate = Isolate::Allocate(); per_process::v8_platform.Platform()->RegisterIsolate(isolate, uv_default_loop()); std::unique_ptr main_instance; std::string result; + std::vector external_references = ExternalReferences::get_list(); + external_references.push_back(ExternalReferences::kEnd); + { std::vector isolate_data_indexes; SnapshotCreator creator(isolate, external_references.data()); From 56f25562bc511f0baca853aed79e5682ef618036 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 10 Apr 2020 04:49:43 +0200 Subject: [PATCH 3/8] lib,src: fix process.features.cached_builtins when snapshotted This value is not a constant, in the sense that its value when running `node_mksnapshot` and when running the default `node` binary are different. --- lib/internal/bootstrap/node.js | 12 +++++++++++- src/node_config.cc | 16 ++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 2c58f912080733..55ca0cc4ccbac0 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -197,7 +197,17 @@ ObjectDefineProperty(process, 'features', { tls_sni: hasOpenSSL, tls_ocsp: hasOpenSSL, tls: hasOpenSSL, - cached_builtins: config.hasCachedBuiltins, + } +}); +ObjectDefineProperty(process.features, 'cached_builtins', { + enumerable: true, + configurable: true, + get() { + return config.hasCachedBuiltins(); + }, + set(v) { + delete this.cached_builtins; + this.cached_builtins = v; } }); diff --git a/src/node_config.cc b/src/node_config.cc index 6ee3164a134fe8..129f3c4f0a015d 100644 --- a/src/node_config.cc +++ b/src/node_config.cc @@ -4,17 +4,23 @@ #include "node_i18n.h" #include "node_native_module_env.h" #include "node_options.h" +#include "snapshot_support-inl.h" #include "util-inl.h" namespace node { using v8::Context; +using v8::FunctionCallbackInfo; using v8::Isolate; using v8::Local; using v8::Number; using v8::Object; using v8::Value; +static void HasCachedBuiltins(const FunctionCallbackInfo& args) { + args.GetReturnValue().Set(native_module::has_code_cache); +} + // The config binding is used to provide an internal view of compile time // config options that are required internally by lib/*.js code. This is an // alternative to dropping additional properties onto the process object as @@ -85,10 +91,16 @@ static void Initialize(Local target, READONLY_TRUE_PROPERTY(target, "hasDtrace"); #endif - READONLY_PROPERTY(target, "hasCachedBuiltins", - v8::Boolean::New(isolate, native_module::has_code_cache)); + // Make this a method instead of a constant in order for snapshotted builts + // to correctly report this. + env->SetMethodNoSideEffect(target, "hasCachedBuiltins", HasCachedBuiltins); } // InitConfig +static ExternalReferences external_references { + __FILE__, + HasCachedBuiltins +}; + } // namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL(config, node::Initialize) From 4d8456c265b03df91bdd617210dc27ece1270e18 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 10 Apr 2020 15:46:13 +0200 Subject: [PATCH 4/8] buffer: refactor zero-fill-field passing Instead of storing the zero-fill-field as a fixed `ArrayBuffer` on the binding, only load it via a function call once bootstrapping is complete. This makes sure that the value is set correctly after loading from a snapshot (i.e. that the accessor for the field is stored in the snapshot, not the field itself). --- lib/buffer.js | 24 +++++++++----- lib/internal/bootstrap/node.js | 2 +- lib/internal/bootstrap/pre_execution.js | 6 +++- src/api/environment.cc | 12 +++++++ src/node_buffer.cc | 44 ++++++++++++------------- src/node_internals.h | 3 +- 6 files changed, 56 insertions(+), 35 deletions(-) diff --git a/lib/buffer.js b/lib/buffer.js index 874227f0d44051..85e84fa64f37c6 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -56,7 +56,7 @@ const { swap64: _swap64, kMaxLength, kStringMaxLength, - zeroFill: bindingZeroFill + getZeroFillField } = internalBinding('buffer'); const { arraybuffer_untransferable_private_symbol, @@ -131,18 +131,22 @@ const constants = ObjectDefineProperties({}, { } }); +const encodingsMap = ObjectCreate(null); +for (let i = 0; i < encodings.length; ++i) + encodingsMap[encodings[i]] = i; + Buffer.poolSize = 8 * 1024; let poolSize, poolOffset, allocPool; // A toggle used to access the zero fill setting of the array buffer allocator // in C++. -// |zeroFill| can be undefined when running inside an isolate where we -// do not own the ArrayBuffer allocator. Zero fill is always on in that case. -const zeroFill = bindingZeroFill || [0]; - -const encodingsMap = ObjectCreate(null); -for (let i = 0; i < encodings.length; ++i) - encodingsMap[encodings[i]] = i; +// getZeroFillField() can return undefined when running inside an isolate where +// we do not own the ArrayBuffer allocator. Zero fill is always on in that case. +let zeroFill = new Uint32Array([0]); +function refreshZeroFillField() { + const fromBinding = getZeroFillField(); + if (fromBinding) zeroFill = fromBinding; +} function createUnsafeBuffer(size) { zeroFill[0] = 0; @@ -1207,7 +1211,9 @@ module.exports = { transcode, // Legacy kMaxLength, - kStringMaxLength + kStringMaxLength, + // Called and deleted by internal/bootstrap/pre_execution.js. + refreshZeroFillField }; ObjectDefineProperties(module.exports, { diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 55ca0cc4ccbac0..c038e2aae81ff2 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -312,7 +312,7 @@ function setupBuffer() { // Only after this point can C++ use Buffer::New() bufferBinding.setBufferPrototype(Buffer.prototype); delete bufferBinding.setBufferPrototype; - delete bufferBinding.zeroFill; + delete bufferBinding.getZeroFillField; ObjectDefineProperty(global, 'Buffer', { value: Buffer, diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js index 3d5e0061daa8d1..012416ec2bc73a 100644 --- a/lib/internal/bootstrap/pre_execution.js +++ b/lib/internal/bootstrap/pre_execution.js @@ -7,7 +7,8 @@ const { } = primordials; const { getOptionValue } = require('internal/options'); -const { Buffer } = require('buffer'); +const buffer = require('buffer'); +const { Buffer } = buffer; const { ERR_MANIFEST_ASSERT_INTEGRITY } = require('internal/errors').codes; const assert = require('internal/assert'); @@ -107,6 +108,9 @@ function patchProcessObject(expandArgv1) { addReadOnlyProcessAlias('traceDeprecation', '--trace-deprecation'); addReadOnlyProcessAlias('_breakFirstLine', '--inspect-brk', false); addReadOnlyProcessAlias('_breakNodeFirstLine', '--inspect-brk-node', false); + + buffer.refreshZeroFillField(); + delete buffer.refreshZeroFillField; } function addReadOnlyProcessAlias(name, option, enumerable = true) { diff --git a/src/api/environment.cc b/src/api/environment.cc index b9ca6ca7451926..705e127c7f2b7f 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -14,6 +14,7 @@ namespace node { using errors::TryCatchScope; using v8::Array; +using v8::ArrayBuffer; using v8::Context; using v8::EscapableHandleScope; using v8::FinalizationGroup; @@ -91,6 +92,17 @@ static void HostCleanupFinalizationGroupCallback( env->RegisterFinalizationGroupForCleanup(group); } +std::shared_ptr NodeArrayBufferAllocator::zero_fill_field() { + if (!zero_fill_field_bs_) { + zero_fill_field_bs_ = + ArrayBuffer::NewBackingStore(&zero_fill_field_, + sizeof(zero_fill_field_), + [](void*, size_t, void*){}, + nullptr); + } + return zero_fill_field_bs_; +} + void* NodeArrayBufferAllocator::Allocate(size_t size) { void* ret; if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers) diff --git a/src/node_buffer.cc b/src/node_buffer.cc index ecae817eae7b71..4d0d2e79962452 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1152,6 +1152,25 @@ void SetBufferPrototype(const FunctionCallbackInfo& args) { } +void GetZeroFillField(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + // This can be a nullptr when running inside an isolate where we + // do not own the ArrayBuffer allocator. + NodeArrayBufferAllocator* allocator = env->isolate_data()->node_allocator(); + if (allocator == nullptr) return; + + std::shared_ptr backing = allocator->zero_fill_field(); + Local array_buffer = + ArrayBuffer::New(env->isolate(), std::move(backing)); + array_buffer->SetPrivate( + env->context(), + env->arraybuffer_untransferable_private_symbol(), + True(env->isolate())).Check(); + args.GetReturnValue().Set(Uint32Array::New(array_buffer, 0, 1)); +} + + void Initialize(Local target, Local unused, Local context, @@ -1159,6 +1178,7 @@ void Initialize(Local target, Environment* env = Environment::GetCurrent(context); env->SetMethod(target, "setBufferPrototype", SetBufferPrototype); + env->SetMethod(target, "getZeroFillField", GetZeroFillField); env->SetMethodNoSideEffect(target, "createFromString", CreateFromString); env->SetMethodNoSideEffect(target, "byteLengthUtf8", ByteLengthUtf8); @@ -1198,34 +1218,12 @@ void Initialize(Local target, env->SetMethod(target, "hexWrite", StringWrite); env->SetMethod(target, "ucs2Write", StringWrite); env->SetMethod(target, "utf8Write", StringWrite); - - // It can be a nullptr when running inside an isolate where we - // do not own the ArrayBuffer allocator. - if (NodeArrayBufferAllocator* allocator = - env->isolate_data()->node_allocator()) { - uint32_t* zero_fill_field = allocator->zero_fill_field(); - std::unique_ptr backing = - ArrayBuffer::NewBackingStore(zero_fill_field, - sizeof(*zero_fill_field), - [](void*, size_t, void*){}, - nullptr); - Local array_buffer = - ArrayBuffer::New(env->isolate(), std::move(backing)); - array_buffer->SetPrivate( - env->context(), - env->arraybuffer_untransferable_private_symbol(), - True(env->isolate())).Check(); - CHECK(target - ->Set(env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "zeroFill"), - Uint32Array::New(array_buffer, 0, 1)) - .FromJust()); - } } static ExternalReferences external_references { __FILE__, SetBufferPrototype, + GetZeroFillField, CreateFromString, ByteLengthUtf8, Copy, diff --git a/src/node_internals.h b/src/node_internals.h index fa3ba022fe7e23..c962a33f5f8610 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -107,7 +107,7 @@ void PromiseRejectCallback(v8::PromiseRejectMessage message); class NodeArrayBufferAllocator : public ArrayBufferAllocator { public: - inline uint32_t* zero_fill_field() { return &zero_fill_field_; } + std::shared_ptr zero_fill_field(); void* Allocate(size_t size) override; // Defined in src/node.cc void* AllocateUninitialized(size_t size) override; @@ -127,6 +127,7 @@ class NodeArrayBufferAllocator : public ArrayBufferAllocator { private: uint32_t zero_fill_field_ = 1; // Boolean but exposed as uint32 to JS land. + std::shared_ptr zero_fill_field_bs_; std::atomic total_mem_usage_ {0}; }; From 2a70893a6f05b67d4434c69a59963342ebeeec51 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 14 Apr 2020 18:58:38 +0200 Subject: [PATCH 5/8] deps: V8: cherry-pick bb9f0c2b2fe9 Original commit message: [snapshot] Improve snapshot docs and error printing - Minor improvements to the documentation for snapshotting. - Add newlines to printed errors where necessary. Change-Id: I822e7e850adb67eae73b51c23cf34e40ba3106f0 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2144954 Reviewed-by: Toon Verwaest Commit-Queue: Toon Verwaest Cr-Commit-Position: refs/heads/master@{#67111} Refs: https://github.com/v8/v8/commit/bb9f0c2b2fe920a717794f3279758846f59f7840 --- deps/v8/include/v8.h | 9 +++++++++ deps/v8/src/snapshot/serializer-common.cc | 3 ++- deps/v8/src/snapshot/startup-serializer.cc | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/deps/v8/include/v8.h b/deps/v8/include/v8.h index 54bc4f08359125..0dcecf55f86634 100644 --- a/deps/v8/include/v8.h +++ b/deps/v8/include/v8.h @@ -9720,6 +9720,11 @@ class V8_EXPORT V8 { /** * Helper class to create a snapshot data blob. + * + * The Isolate used by a SnapshotCreator is owned by it, and will be entered + * and exited by the constructor and destructor, respectively; The destructor + * will also destroy the Isolate. Experimental language features, including + * those available by default, are not available while creating a snapshot. */ class V8_EXPORT SnapshotCreator { public: @@ -9748,6 +9753,10 @@ class V8_EXPORT SnapshotCreator { SnapshotCreator(const intptr_t* external_references = nullptr, StartupData* existing_blob = nullptr); + /** + * Destroy the snapshot creator, and exit and dispose of the Isolate + * associated with it. + */ ~SnapshotCreator(); /** diff --git a/deps/v8/src/snapshot/serializer-common.cc b/deps/v8/src/snapshot/serializer-common.cc index 1703af771769ac..8db4b6c58c18c6 100644 --- a/deps/v8/src/snapshot/serializer-common.cc +++ b/deps/v8/src/snapshot/serializer-common.cc @@ -76,7 +76,8 @@ ExternalReferenceEncoder::Value ExternalReferenceEncoder::Encode( if (maybe_index.IsNothing()) { void* addr = reinterpret_cast(address); v8::base::OS::PrintError("Unknown external reference %p.\n", addr); - v8::base::OS::PrintError("%s", ExternalReferenceTable::ResolveSymbol(addr)); + v8::base::OS::PrintError("%s\n", + ExternalReferenceTable::ResolveSymbol(addr)); v8::base::OS::Abort(); } Value result(maybe_index.FromJust()); diff --git a/deps/v8/src/snapshot/startup-serializer.cc b/deps/v8/src/snapshot/startup-serializer.cc index 4d6ce78b594dc3..8463373013d2d7 100644 --- a/deps/v8/src/snapshot/startup-serializer.cc +++ b/deps/v8/src/snapshot/startup-serializer.cc @@ -183,6 +183,7 @@ void SerializedHandleChecker::VisitRootPointers(Root root, PrintF("%s handle not serialized: ", root == Root::kGlobalHandles ? "global" : "eternal"); (*p).Print(); + PrintF("\n"); ok_ = false; } } From 52b71026e6f1b7899ef7403b02034895d0efad2d Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 14 Apr 2020 18:59:01 +0200 Subject: [PATCH 6/8] deps: V8: cherry-pick ea0719b8ed08 Original commit message: [snapshot] Do not defer ArrayBuffers during snapshotting ArrayBuffer instances are serialized by first re-assigning a index to the backing store field, then serializing the object, and then storing the actual backing store address again (and the same for the ArrayBufferExtension). If serialization of the object itself is deferred, the real backing store address is written into the snapshot, which cannot be processed when deserializing, leading to a crash. This fixes this by not deferring ArrayBuffer serialization and adding a DCHECK for the crash that previously occurred. Change-Id: Id9bea8268061bd0770cde7bfeb6695248978f994 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2144123 Commit-Queue: Jakob Gruber Reviewed-by: Dan Elphick Cr-Commit-Position: refs/heads/master@{#67114} Refs: https://github.com/v8/v8/commit/ea0719b8ed087d1f511e78595dcb596faa7638d0 --- deps/v8/src/snapshot/deserializer.h | 1 + deps/v8/src/snapshot/serializer-common.cc | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/deps/v8/src/snapshot/deserializer.h b/deps/v8/src/snapshot/deserializer.h index 871aa6b3a77303..3aac06ba7e633c 100644 --- a/deps/v8/src/snapshot/deserializer.h +++ b/deps/v8/src/snapshot/deserializer.h @@ -107,6 +107,7 @@ class V8_EXPORT_PRIVATE Deserializer : public SerializerDeserializer { } std::shared_ptr backing_store(size_t i) { + DCHECK_LT(i, backing_stores_.size()); return backing_stores_[i]; } diff --git a/deps/v8/src/snapshot/serializer-common.cc b/deps/v8/src/snapshot/serializer-common.cc index 8db4b6c58c18c6..5922139b8f9773 100644 --- a/deps/v8/src/snapshot/serializer-common.cc +++ b/deps/v8/src/snapshot/serializer-common.cc @@ -127,7 +127,14 @@ void SerializerDeserializer::Iterate(Isolate* isolate, RootVisitor* visitor) { } bool SerializerDeserializer::CanBeDeferred(HeapObject o) { - return !o.IsString() && !o.IsScript() && !o.IsJSTypedArray(); + // ArrayBuffer instances are serialized by first re-assigning a index + // to the backing store field, then serializing the object, and then + // storing the actual backing store address again (and the same for the + // ArrayBufferExtension). If serialization of the object itself is deferred, + // the real backing store address is written into the snapshot, which cannot + // be processed when deserializing. + return !o.IsString() && !o.IsScript() && !o.IsJSTypedArray() && + !o.IsJSArrayBuffer(); } void SerializerDeserializer::RestoreExternalReferenceRedirectors( From 4bc3d2cc7b650385a927de4f7301ef883d62cfbe Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 8 May 2020 02:29:49 +0800 Subject: [PATCH 7/8] deps: V8: cherry-pick 9baf2865671c Original commit message: rehash JSMap and JSSet during deserialization To rehash JSMap and JSSet, we simply replace the backing store with a new one created with the new hash. Bug: v8:9187 Refs: https://github.com/joyeecheung/v8/commit/9baf2865671ccd49b606b00db8e8c20705591c53 --- deps/v8/src/objects/heap-object.h | 2 +- deps/v8/src/objects/js-collection.h | 2 ++ deps/v8/src/objects/objects.cc | 38 +++++++++++++++++--- deps/v8/src/objects/ordered-hash-table.cc | 21 +++++++++++ deps/v8/src/objects/ordered-hash-table.h | 7 ++++ deps/v8/src/snapshot/deserializer.cc | 16 +++++++-- deps/v8/src/snapshot/deserializer.h | 5 +++ deps/v8/src/snapshot/object-deserializer.cc | 3 +- deps/v8/src/snapshot/partial-deserializer.cc | 2 +- deps/v8/test/cctest/test-serialize.cc | 14 +++++--- 10 files changed, 96 insertions(+), 14 deletions(-) diff --git a/deps/v8/src/objects/heap-object.h b/deps/v8/src/objects/heap-object.h index fcbb0ce8336bb7..c6cd4c3ae5480d 100644 --- a/deps/v8/src/objects/heap-object.h +++ b/deps/v8/src/objects/heap-object.h @@ -190,7 +190,7 @@ class HeapObject : public Object { bool CanBeRehashed() const; // Rehash the object based on the layout inferred from its map. - void RehashBasedOnMap(ReadOnlyRoots root); + void RehashBasedOnMap(Isolate* isolate); // Layout description. #define HEAP_OBJECT_FIELDS(V) \ diff --git a/deps/v8/src/objects/js-collection.h b/deps/v8/src/objects/js-collection.h index 17f9c3e198ba8b..a0350726c02db7 100644 --- a/deps/v8/src/objects/js-collection.h +++ b/deps/v8/src/objects/js-collection.h @@ -30,6 +30,7 @@ class JSSet : public TorqueGeneratedJSSet { public: static void Initialize(Handle set, Isolate* isolate); static void Clear(Isolate* isolate, Handle set); + void Rehash(Isolate* isolate); // Dispatched behavior. DECL_PRINTER(JSSet) @@ -56,6 +57,7 @@ class JSMap : public TorqueGeneratedJSMap { public: static void Initialize(Handle map, Isolate* isolate); static void Clear(Isolate* isolate, Handle map); + void Rehash(Isolate* isolate); // Dispatched behavior. DECL_PRINTER(JSMap) diff --git a/deps/v8/src/objects/objects.cc b/deps/v8/src/objects/objects.cc index 9b53019297a3c6..6b80bc8b1aea80 100644 --- a/deps/v8/src/objects/objects.cc +++ b/deps/v8/src/objects/objects.cc @@ -2285,9 +2285,8 @@ bool HeapObject::NeedsRehashing() const { case TRANSITION_ARRAY_TYPE: return TransitionArray::cast(*this).number_of_entries() > 1; case ORDERED_HASH_MAP_TYPE: - return OrderedHashMap::cast(*this).NumberOfElements() > 0; case ORDERED_HASH_SET_TYPE: - return OrderedHashSet::cast(*this).NumberOfElements() > 0; + return false; // We'll rehash from the JSMap or JSSet referencing them. case NAME_DICTIONARY_TYPE: case GLOBAL_DICTIONARY_TYPE: case NUMBER_DICTIONARY_TYPE: @@ -2297,6 +2296,8 @@ bool HeapObject::NeedsRehashing() const { case SMALL_ORDERED_HASH_MAP_TYPE: case SMALL_ORDERED_HASH_SET_TYPE: case SMALL_ORDERED_NAME_DICTIONARY_TYPE: + case JS_MAP_TYPE: + case JS_SET_TYPE: return true; default: return false; @@ -2306,10 +2307,13 @@ bool HeapObject::NeedsRehashing() const { bool HeapObject::CanBeRehashed() const { DCHECK(NeedsRehashing()); switch (map().instance_type()) { + case JS_MAP_TYPE: + case JS_SET_TYPE: + return true; case ORDERED_HASH_MAP_TYPE: case ORDERED_HASH_SET_TYPE: + UNREACHABLE(); // We'll rehash from the JSMap or JSSet referencing them. case ORDERED_NAME_DICTIONARY_TYPE: - // TODO(yangguo): actually support rehashing OrderedHash{Map,Set}. return false; case NAME_DICTIONARY_TYPE: case GLOBAL_DICTIONARY_TYPE: @@ -2333,7 +2337,8 @@ bool HeapObject::CanBeRehashed() const { return false; } -void HeapObject::RehashBasedOnMap(ReadOnlyRoots roots) { +void HeapObject::RehashBasedOnMap(Isolate* isolate) { + ReadOnlyRoots roots = ReadOnlyRoots(isolate); switch (map().instance_type()) { case HASH_TABLE_TYPE: UNREACHABLE(); @@ -2365,6 +2370,17 @@ void HeapObject::RehashBasedOnMap(ReadOnlyRoots roots) { case SMALL_ORDERED_HASH_SET_TYPE: DCHECK_EQ(0, SmallOrderedHashSet::cast(*this).NumberOfElements()); break; + case ORDERED_HASH_MAP_TYPE: + case ORDERED_HASH_SET_TYPE: + UNREACHABLE(); // We'll rehash from the JSMap or JSSet referencing them. + case JS_MAP_TYPE: { + JSMap::cast(*this).Rehash(isolate); + break; + } + case JS_SET_TYPE: { + JSSet::cast(*this).Rehash(isolate); + break; + } case SMALL_ORDERED_NAME_DICTIONARY_TYPE: DCHECK_EQ(0, SmallOrderedNameDictionary::cast(*this).NumberOfElements()); break; @@ -7740,6 +7756,13 @@ void JSSet::Clear(Isolate* isolate, Handle set) { set->set_table(*table); } +void JSSet::Rehash(Isolate* isolate) { + Handle table_handle(OrderedHashSet::cast(table()), isolate); + Handle new_table = + OrderedHashSet::Rehash(isolate, table_handle).ToHandleChecked(); + set_table(*new_table); +} + void JSMap::Initialize(Handle map, Isolate* isolate) { Handle table = isolate->factory()->NewOrderedHashMap(); map->set_table(*table); @@ -7751,6 +7774,13 @@ void JSMap::Clear(Isolate* isolate, Handle map) { map->set_table(*table); } +void JSMap::Rehash(Isolate* isolate) { + Handle table_handle(OrderedHashMap::cast(table()), isolate); + Handle new_table = + OrderedHashMap::Rehash(isolate, table_handle).ToHandleChecked(); + set_table(*new_table); +} + void JSWeakCollection::Initialize(Handle weak_collection, Isolate* isolate) { Handle table = EphemeronHashTable::New(isolate, 0); diff --git a/deps/v8/src/objects/ordered-hash-table.cc b/deps/v8/src/objects/ordered-hash-table.cc index 962224024ea805..a0a06e012ae0db 100644 --- a/deps/v8/src/objects/ordered-hash-table.cc +++ b/deps/v8/src/objects/ordered-hash-table.cc @@ -193,6 +193,13 @@ HeapObject OrderedHashMap::GetEmpty(ReadOnlyRoots ro_roots) { return ro_roots.empty_ordered_hash_map(); } +template +MaybeHandle OrderedHashTable::Rehash( + Isolate* isolate, Handle table) { + return OrderedHashTable::Rehash(isolate, table, + table->Capacity()); +} + template MaybeHandle OrderedHashTable::Rehash( Isolate* isolate, Handle table, int new_capacity) { @@ -249,6 +256,20 @@ MaybeHandle OrderedHashSet::Rehash(Isolate* isolate, new_capacity); } +MaybeHandle OrderedHashSet::Rehash( + Isolate* isolate, Handle table) { + return OrderedHashTable< + OrderedHashSet, OrderedHashSet::kEntrySizeWithoutChain>::Rehash(isolate, + table); +} + +MaybeHandle OrderedHashMap::Rehash( + Isolate* isolate, Handle table) { + return OrderedHashTable< + OrderedHashMap, OrderedHashMap::kEntrySizeWithoutChain>::Rehash(isolate, + table); +} + MaybeHandle OrderedHashMap::Rehash(Isolate* isolate, Handle table, int new_capacity) { diff --git a/deps/v8/src/objects/ordered-hash-table.h b/deps/v8/src/objects/ordered-hash-table.h index 590846f1302775..16609b8d1f50c8 100644 --- a/deps/v8/src/objects/ordered-hash-table.h +++ b/deps/v8/src/objects/ordered-hash-table.h @@ -138,6 +138,7 @@ class OrderedHashTable : public FixedArray { // The extra +1 is for linking the bucket chains together. static const int kEntrySize = entrysize + 1; + static const int kEntrySizeWithoutChain = entrysize; static const int kChainOffset = entrysize; static const int kNotFound = -1; @@ -200,6 +201,8 @@ class OrderedHashTable : public FixedArray { static MaybeHandle Allocate( Isolate* isolate, int capacity, AllocationType allocation = AllocationType::kYoung); + + static MaybeHandle Rehash(Isolate* isolate, Handle table); static MaybeHandle Rehash(Isolate* isolate, Handle table, int new_capacity); @@ -244,6 +247,8 @@ class V8_EXPORT_PRIVATE OrderedHashSet static MaybeHandle Rehash(Isolate* isolate, Handle table, int new_capacity); + static MaybeHandle Rehash(Isolate* isolate, + Handle table); static MaybeHandle Allocate( Isolate* isolate, int capacity, AllocationType allocation = AllocationType::kYoung); @@ -273,6 +278,8 @@ class V8_EXPORT_PRIVATE OrderedHashMap static MaybeHandle Rehash(Isolate* isolate, Handle table, int new_capacity); + static MaybeHandle Rehash(Isolate* isolate, + Handle table); Object ValueAt(int entry); // This takes and returns raw Address values containing tagged Object diff --git a/deps/v8/src/snapshot/deserializer.cc b/deps/v8/src/snapshot/deserializer.cc index 127cfae29e018c..93d01633293ede 100644 --- a/deps/v8/src/snapshot/deserializer.cc +++ b/deps/v8/src/snapshot/deserializer.cc @@ -70,7 +70,7 @@ void Deserializer::Initialize(Isolate* isolate) { void Deserializer::Rehash() { DCHECK(can_rehash() || deserializing_user_code()); for (HeapObject item : to_rehash_) { - item.RehashBasedOnMap(ReadOnlyRoots(isolate_)); + item.RehashBasedOnMap(isolate_); } } @@ -130,6 +130,14 @@ void Deserializer::DeserializeDeferredObjects() { } } } + + // When the deserialization of maps are deferred, they will be created + // as filler maps, and we postpone the post processing until the maps + // are also deserialized. + for (const auto& pair : fillers_to_post_process_) { + DCHECK(!pair.first.IsFiller()); + PostProcessNewObject(pair.first, pair.second); + } } void Deserializer::LogNewObjectEvents() { @@ -201,7 +209,11 @@ HeapObject Deserializer::PostProcessNewObject(HeapObject obj, DisallowHeapAllocation no_gc; if ((FLAG_rehash_snapshot && can_rehash_) || deserializing_user_code()) { - if (obj.IsString()) { + if (obj.IsFiller()) { + DCHECK_EQ(fillers_to_post_process_.find(obj), + fillers_to_post_process_.end()); + fillers_to_post_process_.insert({obj, space}); + } else if (obj.IsString()) { // Uninitialize hash field as we need to recompute the hash. String string = String::cast(obj); string.set_hash_field(String::kEmptyHashField); diff --git a/deps/v8/src/snapshot/deserializer.h b/deps/v8/src/snapshot/deserializer.h index 3aac06ba7e633c..fcae577d8f79e5 100644 --- a/deps/v8/src/snapshot/deserializer.h +++ b/deps/v8/src/snapshot/deserializer.h @@ -194,6 +194,11 @@ class V8_EXPORT_PRIVATE Deserializer : public SerializerDeserializer { // TODO(6593): generalize rehashing, and remove this flag. bool can_rehash_; std::vector to_rehash_; + // Store the objects whose maps are deferred and thus initialized as filler + // maps during deserialization, so that they can be processed later when the + // maps become available. + std::unordered_map + fillers_to_post_process_; #ifdef DEBUG uint32_t num_api_references_; diff --git a/deps/v8/src/snapshot/object-deserializer.cc b/deps/v8/src/snapshot/object-deserializer.cc index ab6ddf1d0c2234..e9fa501d6b7150 100644 --- a/deps/v8/src/snapshot/object-deserializer.cc +++ b/deps/v8/src/snapshot/object-deserializer.cc @@ -49,9 +49,10 @@ MaybeHandle ObjectDeserializer::Deserialize(Isolate* isolate) { LinkAllocationSites(); LogNewMapEvents(); result = handle(HeapObject::cast(root), isolate); - Rehash(); allocator()->RegisterDeserializedObjectsForBlackAllocation(); } + + Rehash(); CommitPostProcessedObjects(); return scope.CloseAndEscape(result); } diff --git a/deps/v8/src/snapshot/partial-deserializer.cc b/deps/v8/src/snapshot/partial-deserializer.cc index 8a215b6b871e94..e15cf6c678fbb8 100644 --- a/deps/v8/src/snapshot/partial-deserializer.cc +++ b/deps/v8/src/snapshot/partial-deserializer.cc @@ -57,12 +57,12 @@ MaybeHandle PartialDeserializer::Deserialize( // new code, which also has to be flushed from instruction cache. CHECK_EQ(start_address, code_space->top()); - if (FLAG_rehash_snapshot && can_rehash()) Rehash(); LogNewMapEvents(); result = handle(root, isolate); } + if (FLAG_rehash_snapshot && can_rehash()) Rehash(); SetupOffHeapArrayBufferBackingStores(); return result; diff --git a/deps/v8/test/cctest/test-serialize.cc b/deps/v8/test/cctest/test-serialize.cc index f37b6235041aaa..a7e74a159ef657 100644 --- a/deps/v8/test/cctest/test-serialize.cc +++ b/deps/v8/test/cctest/test-serialize.cc @@ -3715,7 +3715,7 @@ UNINITIALIZED_TEST(SnapshotCreatorIncludeGlobalProxy) { FreeCurrentEmbeddedBlob(); } -UNINITIALIZED_TEST(ReinitializeHashSeedNotRehashable) { +UNINITIALIZED_TEST(ReinitializeHashSeedJSCollectionRehashable) { DisableAlwaysOpt(); i::FLAG_rehash_snapshot = true; i::FLAG_hash_seed = 42; @@ -3733,13 +3733,16 @@ UNINITIALIZED_TEST(ReinitializeHashSeedNotRehashable) { CompileRun( "var m = new Map();" "m.set('a', 1);" - "m.set('b', 2);"); + "m.set('b', 2);" + "var s = new Set();" + "s.add(1)"); ExpectInt32("m.get('b')", 2); + ExpectTrue("s.has(1)"); creator.SetDefaultContext(context); } blob = creator.CreateBlob(v8::SnapshotCreator::FunctionCodeHandling::kClear); - CHECK(!blob.CanBeRehashed()); + CHECK(blob.CanBeRehashed()); } ReadOnlyHeap::ClearSharedHeapForTest(); @@ -3749,8 +3752,8 @@ UNINITIALIZED_TEST(ReinitializeHashSeedNotRehashable) { create_params.snapshot_blob = &blob; v8::Isolate* isolate = v8::Isolate::New(create_params); { - // Check that no rehashing has been performed. - CHECK_EQ(static_cast(42), + // Check that rehashing has been performed. + CHECK_EQ(static_cast(1337), HashSeed(reinterpret_cast(isolate))); v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handle_scope(isolate); @@ -3758,6 +3761,7 @@ UNINITIALIZED_TEST(ReinitializeHashSeedNotRehashable) { CHECK(!context.IsEmpty()); v8::Context::Scope context_scope(context); ExpectInt32("m.get('b')", 2); + ExpectTrue("s.has(1)"); } isolate->Dispose(); delete[] blob.data; From 8af28fe6d02aea2a12fd623d92c5c4f8e0e84ad3 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sat, 21 Mar 2020 11:27:38 +0100 Subject: [PATCH 8/8] src: include Environment in snapshot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This snapshots a lot more data for startup than what we did previously for increased startup performance. confidence improvement accuracy (*) (**) (***) misc/startup.js mode='process' script='benchmark/fixtures/require-cachable' dur=1 *** 9.73 % ±3.78% ±5.03% ±6.54% misc/startup.js mode='process' script='test/fixtures/semicolon' dur=1 *** 36.11 % ±3.91% ±5.23% ±6.86% --- node.gyp | 1 + src/aliased_buffer.h | 48 ++- src/api/environment.cc | 10 +- src/base_object-inl.h | 1 + src/base_object.h | 8 +- src/env-inl.h | 57 ++-- src/env.cc | 430 ++++++++++++++++++++++--- src/env.h | 57 ++-- src/node.cc | 20 +- src/node_main_instance.cc | 39 ++- src/node_main_instance.h | 7 +- src/node_native_module_env.cc | 5 + src/node_options.cc | 3 + src/node_options.h | 1 + src/node_perf.cc | 36 +++ src/node_perf_common.h | 24 +- src/node_snapshot_stub.cc | 2 +- src/node_worker.cc | 1 + src/snapshot_support-inl.h | 107 ++++++ src/snapshot_support.cc | 464 +++++++++++++++++++++++++++ src/snapshot_support.h | 218 +++++++++++++ test/cctest/test_snapshot_support.cc | 78 +++++ test/parallel/test-code-cache.js | 4 +- tools/snapshot/README.md | 2 +- tools/snapshot/snapshot_builder.cc | 36 ++- 25 files changed, 1507 insertions(+), 152 deletions(-) create mode 100644 test/cctest/test_snapshot_support.cc diff --git a/node.gyp b/node.gyp index bddba36ad716b8..9fe8874d691676 100644 --- a/node.gyp +++ b/node.gyp @@ -1157,6 +1157,7 @@ 'test/cctest/test_per_process.cc', 'test/cctest/test_platform.cc', 'test/cctest/test_json_utils.cc', + 'test/cctest/test_snapshot_support.cc', 'test/cctest/test_sockaddr.cc', 'test/cctest/test_traced_value.cc', 'test/cctest/test_util.cc', diff --git a/src/aliased_buffer.h b/src/aliased_buffer.h index e762e8ede8ebee..6095bb6cc8efdd 100644 --- a/src/aliased_buffer.h +++ b/src/aliased_buffer.h @@ -4,6 +4,8 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include +// TODO(addaleax): There really, really should be an aliased_buffer-inl.h. +#include "snapshot_support-inl.h" #include "util-inl.h" #include "v8.h" @@ -30,8 +32,10 @@ template ::value>> -class AliasedBufferBase { +class AliasedBufferBase final : public Snapshottable { public: + AliasedBufferBase() {} + AliasedBufferBase(v8::Isolate* isolate, const size_t count) : isolate_(isolate), count_(count), byte_offset_(0) { CHECK_GT(count, 0); @@ -243,11 +247,45 @@ class AliasedBufferBase { count_ = new_capacity; } + void Serialize(SnapshotCreateData* snapshot_data) const override { + v8::HandleScope handle_scope(isolate_); + snapshot_data->StartWriteEntry("AliasedBuffer"); + snapshot_data->WriteUint64(count_); + snapshot_data->WriteUint64(byte_offset_); + v8::Local arr = GetJSArray(); + snapshot_data->WriteObject(arr->CreationContext(), arr); + snapshot_data->EndWriteEntry(); + } + + AliasedBufferBase(v8::Local context, + SnapshotReadData* snapshot_data) + : isolate_(context->GetIsolate()) { + v8::HandleScope handle_scope(isolate_); + uint64_t count, byte_offset; + if (snapshot_data->StartReadEntry("AliasedBuffer").IsNothing() || + !snapshot_data->ReadUint64().To(&count) || + !snapshot_data->ReadUint64().To(&byte_offset)) { + return; + } + + count_ = count; + byte_offset_ = byte_offset; + + v8::Local field; + if (!snapshot_data->ReadObject(context).To(&field)) return; + js_array_.Reset(isolate_, field); + buffer_ = reinterpret_cast(static_cast( + field->Buffer()->GetBackingStore()->Data()) + + byte_offset_); + + snapshot_data->EndReadEntry(); + } + private: - v8::Isolate* isolate_; - size_t count_; - size_t byte_offset_; - NativeT* buffer_; + v8::Isolate* isolate_ = nullptr; + size_t count_ = 0; + size_t byte_offset_ = 0; + NativeT* buffer_ = nullptr; v8::Global js_array_; }; diff --git a/src/api/environment.cc b/src/api/environment.cc index 705e127c7f2b7f..b2ff6a6b565c03 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -5,6 +5,7 @@ #include "node_native_module_env.h" #include "node_platform.h" #include "node_v8_platform-inl.h" +#include "snapshot_support-inl.h" #include "uv.h" #if HAVE_INSPECTOR @@ -390,6 +391,12 @@ Environment* CreateEnvironment( env->set_abort_on_uncaught_exception(false); } + if (isolate_data->snapshot_data() != nullptr && + !isolate_data->snapshot_data()->errors().empty()) { + FreeEnvironment(env); + return nullptr; + } + #if HAVE_INSPECTOR if (inspector_parent_handle) { env->InitializeInspector( @@ -400,7 +407,8 @@ Environment* CreateEnvironment( } #endif - if (env->RunBootstrapping().IsEmpty()) { + if (env->isolate_data()->snapshot_data() == nullptr && + env->RunBootstrapping().IsEmpty()) { FreeEnvironment(env); return nullptr; } diff --git a/src/base_object-inl.h b/src/base_object-inl.h index 6220babe920f4f..f318025fd0cd2d 100644 --- a/src/base_object-inl.h +++ b/src/base_object-inl.h @@ -101,6 +101,7 @@ Environment* BaseObject::env() const { } BaseObject* BaseObject::FromJSObject(v8::Local value) { + DCHECK(value->IsObject()); v8::Local obj = value.As(); DCHECK_GE(obj->InternalFieldCount(), BaseObject::kSlot); return static_cast( diff --git a/src/base_object.h b/src/base_object.h index 125c9f795f4474..b7f35dc547dc02 100644 --- a/src/base_object.h +++ b/src/base_object.h @@ -25,6 +25,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "memory_tracker.h" +#include "snapshot_support.h" #include "v8.h" #include // std::remove_reference @@ -34,7 +35,7 @@ class Environment; template class BaseObjectPtrImpl; -class BaseObject : public MemoryRetainer { +class BaseObject : public MemoryRetainer, public Snapshottable { public: enum InternalFields { kSlot, kInternalFieldCount }; @@ -101,6 +102,11 @@ class BaseObject : public MemoryRetainer { static v8::Local GetConstructorTemplate( Environment* env); + static v8::StartupData SerializeInternalFields( + v8::Local object, int index, void* data); + virtual v8::StartupData SerializeInternalFields( + int index, SnapshotCreateData* snapshot_data) const; + protected: virtual inline void OnGCCollect(); diff --git a/src/env-inl.h b/src/env-inl.h index 853ba78de6d548..d0059e1c3dc9c6 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -73,33 +73,14 @@ inline worker::Worker* IsolateData::worker_context() const { return worker_context_; } +SnapshotReadData* IsolateData::snapshot_data() const { + return snapshot_data_; +} + 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_; } @@ -128,6 +109,10 @@ inline Environment* AsyncHooks::env() { return Environment::ForAsyncHooks(this); } +inline const Environment* AsyncHooks::env() const { + return Environment::ForAsyncHooks(const_cast(this)); +} + // Remember to keep this code aligned with pushAsyncContext() in JS. inline void AsyncHooks::push_async_context(double async_id, double trigger_async_id, @@ -238,9 +223,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 +247,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_; } @@ -467,15 +446,27 @@ inline void Environment::set_is_in_inspector_console_call(bool value) { } #endif +inline const AsyncHooks* Environment::async_hooks() const { + return &async_hooks_; +} + inline AsyncHooks* Environment::async_hooks() { return &async_hooks_; } +inline const ImmediateInfo* Environment::immediate_info() const { + return &immediate_info_; +} + inline ImmediateInfo* Environment::immediate_info() { return &immediate_info_; } -inline TickInfo* Environment::tick_info() { +inline const TickInfo* Environment::tick_info() const { + return &tick_info_; +} + +inline TickInfo* Environment::tick_info() { return &tick_info_; } @@ -926,6 +917,10 @@ inline performance::PerformanceState* Environment::performance_state() { return performance_state_.get(); } +const performance::PerformanceState* Environment::performance_state() const { + return performance_state_.get(); +} + inline std::unordered_map* Environment::performance_marks() { return &performance_marks_; @@ -1213,7 +1208,7 @@ BaseObject* CleanupHookCallback::GetBaseObject() const { } template -void Environment::ForEachBaseObject(T&& iterator) { +void Environment::ForEachBaseObject(T&& iterator) const { for (const auto& hook : cleanup_hooks_) { BaseObject* obj = hook.GetBaseObject(); if (obj != nullptr) diff --git a/src/env.cc b/src/env.cc index ff5212c0d1121f..103b5b208e9817 100644 --- a/src/env.cc +++ b/src/env.cc @@ -13,6 +13,7 @@ #include "node_v8_platform-inl.h" #include "node_worker.h" #include "req_wrap-inl.h" +#include "snapshot_support-inl.h" #include "stream_base.h" #include "tracing/agent.h" #include "tracing/traced_value.h" @@ -27,6 +28,7 @@ namespace node { using errors::TryCatchScope; +using v8::Array; using v8::ArrayBuffer; using v8::Boolean; using v8::Context; @@ -44,8 +46,8 @@ using v8::Number; using v8::Object; using v8::Private; using v8::Script; -using v8::SnapshotCreator; using v8::StackTrace; +using v8::StartupData; using v8::String; using v8::Symbol; using v8::TracingController; @@ -58,20 +60,15 @@ int const Environment::kNodeContextTag = 0x6e6f64; void* const Environment::kNodeContextTagPtr = const_cast( static_cast(&Environment::kNodeContextTag)); -std::vector IsolateData::Serialize(SnapshotCreator* creator) { - Isolate* isolate = creator->GetIsolate(); - std::vector indexes; - HandleScope handle_scope(isolate); - // XXX(joyeecheung): technically speaking, the indexes here should be - // consecutive and we could just return a range instead of an array, - // but that's not part of the V8 API contract so we use an array - // just to be safe. +void IsolateData::Serialize(SnapshotCreateData* snapshot_data) const { + HandleScope handle_scope(isolate_); + snapshot_data->StartWriteEntry("IsolateData"); #define VP(PropertyName, StringValue) V(Private, PropertyName) #define VY(PropertyName, StringValue) V(Symbol, PropertyName) #define VS(PropertyName, StringValue) V(String, PropertyName) #define V(TypeName, PropertyName) \ - indexes.push_back(creator->AddData(PropertyName##_.Get(isolate))); + snapshot_data->WriteContextIndependentObject(PropertyName()); PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) PER_ISOLATE_SYMBOL_PROPERTIES(VY) PER_ISOLATE_STRING_PROPERTIES(VS) @@ -80,13 +77,14 @@ std::vector IsolateData::Serialize(SnapshotCreator* creator) { #undef VS #undef VP for (size_t i = 0; i < AsyncWrap::PROVIDERS_LENGTH; i++) - indexes.push_back(creator->AddData(async_wrap_provider(i))); + snapshot_data->WriteContextIndependentObject(async_wrap_provider(i)); - return indexes; + snapshot_data->EndWriteEntry(); } -void IsolateData::DeserializeProperties(const std::vector* indexes) { - size_t i = 0; +void IsolateData::DeserializeProperties() { + if (snapshot_data_->StartReadEntry("IsolateData").IsNothing()) return; + HandleScope handle_scope(isolate_); #define VP(PropertyName, StringValue) V(Private, PropertyName) @@ -94,12 +92,10 @@ void IsolateData::DeserializeProperties(const std::vector* indexes) { #define VS(PropertyName, StringValue) V(String, PropertyName) #define V(TypeName, PropertyName) \ do { \ - MaybeLocal field = \ - isolate_->GetDataFromSnapshotOnce((*indexes)[i++]); \ - if (field.IsEmpty()) { \ - fprintf(stderr, "Failed to deserialize " #PropertyName "\n"); \ - } \ - PropertyName##_.Set(isolate_, field.ToLocalChecked()); \ + Local field; \ + if (!snapshot_data_->ReadContextIndependentObject().To(&field)) \ + return; \ + PropertyName##_.Set(isolate_, field); \ } while (0); PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP) PER_ISOLATE_SYMBOL_PROPERTIES(VY) @@ -110,13 +106,13 @@ void IsolateData::DeserializeProperties(const std::vector* indexes) { #undef VP for (size_t j = 0; j < AsyncWrap::PROVIDERS_LENGTH; j++) { - MaybeLocal field = - isolate_->GetDataFromSnapshotOnce((*indexes)[i++]); - if (field.IsEmpty()) { - fprintf(stderr, "Failed to deserialize AsyncWrap provider %zu\n", j); - } - async_wrap_providers_[j].Set(isolate_, field.ToLocalChecked()); + Local field; + if (!snapshot_data_->ReadContextIndependentObject().To(&field)) + return; + async_wrap_providers_[j].Set(isolate_, field); } + + snapshot_data_->EndReadEntry(); } void IsolateData::CreateProperties() { @@ -187,23 +183,25 @@ IsolateData::IsolateData(Isolate* isolate, uv_loop_t* event_loop, MultiIsolatePlatform* platform, ArrayBufferAllocator* node_allocator, - const std::vector* indexes) + SnapshotReadData* snapshot_data) : isolate_(isolate), event_loop_(event_loop), allocator_(isolate->GetArrayBufferAllocator()), node_allocator_(node_allocator == nullptr ? nullptr : node_allocator->GetImpl()), uses_node_allocator_(allocator_ == node_allocator_), - platform_(platform) { + platform_(platform), + snapshot_data_(snapshot_data) { CHECK_NOT_NULL(allocator_); options_.reset( new PerIsolateOptions(*(per_process::cli_options->per_isolate))); - if (indexes == nullptr) { + if (snapshot_data == nullptr) { CreateProperties(); } else { - DeserializeProperties(indexes); + snapshot_data->set_isolate(isolate); + DeserializeProperties(); } } @@ -288,6 +286,200 @@ void Environment::CreateProperties() { set_process_object(process_object); } +void Environment::DeserializeProperties() { + // If we have not run the bootstrapping code yet, serialization will fail, + // therefore this will always be true when deserializing. + set_has_run_bootstrapping_code(true); + + SnapshotReadData* snapshot_data = isolate_data()->snapshot_data(); + bool can_call_into_js; + if (!snapshot_data->ReadBool().To(&can_call_into_js)) return; + can_call_into_js_ = can_call_into_js; + + if (!snapshot_data->ReadUint32().To(&module_id_counter_)) return; + if (!snapshot_data->ReadUint32().To(&script_id_counter_)) return; + if (!snapshot_data->ReadUint32().To(&function_id_counter_)) return; + + Isolate* isolate = this->isolate(); + HandleScope handle_scope(isolate); + Local context = this->context(); + + if (snapshot_data->StartReadEntry("StrongPersistentTemplates").IsNothing()) + return; +#define V(PropertyName, TypeName) \ + do { \ + Local field; \ + if (!snapshot_data->ReadContextIndependentObject( \ + SnapshotReadData::kAllowEmpty).To(&field)) { \ + return; \ + } \ + PropertyName##_.Reset(isolate_, field); \ + } while (0); + ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) +#undef V + if (snapshot_data->EndReadEntry().IsNothing()) return; + + if (snapshot_data->StartReadEntry("StrongPersistentValues").IsNothing()) + return; +#define V(PropertyName, TypeName) \ + do { \ + Local field; \ + if (!snapshot_data->ReadObject(context, \ + SnapshotReadData::kAllowEmpty).To(&field)) { \ + return; \ + } \ + PropertyName##_.Reset(isolate_, field); \ + } while (0); + ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) +#undef V + if (snapshot_data->EndReadEntry().IsNothing()) return; + + if (snapshot_data->StartReadEntry("NativeModules").IsNothing()) return; + uint64_t expected_native_module_count; + if (!snapshot_data->ReadUint64().To(&expected_native_module_count)) return; + for (uint64_t i = 0; i < expected_native_module_count; i++) { + std::string str; + if (!snapshot_data->ReadString().To(&str)) return; + native_modules_in_snapshot.insert(str); + } + if (snapshot_data->EndReadEntry().IsNothing()) return; + + if (snapshot_data->StartReadEntry("BaseObjects").IsNothing()) return; + uint64_t expected_base_object_count; + if (!snapshot_data->ReadUint64().To(&expected_base_object_count)) return; + +#ifdef DEBUG + std::set deserialized_objects; +#endif + for (uint64_t i = 0; i < expected_base_object_count; i++) { + std::string name; + if (!snapshot_data->StartReadEntry(nullptr).To(&name)) return; + BaseObject* obj = BaseObjectDeserializer::Deserialize( + name, this, snapshot_data); + if (obj == nullptr) return; +#ifdef DEBUG + deserialized_objects.insert(obj); +#endif + } + +#ifdef DEBUG + // Make sure all BaseObject instances have been attached to this Environment. + ForEachBaseObject([&](const BaseObject* obj) { + deserialized_objects.erase(obj); + }); + CHECK(deserialized_objects.empty()); +#endif + if (snapshot_data->EndReadEntry().IsNothing()) return; + + if (snapshot_data->StartReadEntry("bindings").IsNothing()) return; + uint32_t num_bindings; + if (!snapshot_data->ReadUint32().To(&num_bindings)) return; + for (uint32_t i = 0; i < num_bindings; i++) { + std::string name; + BaseObjectPtr binding; + if (!snapshot_data->ReadString().To(&name) || + !snapshot_data->ReadBaseObjectPtr(context).To(&binding)) { + return; + } + bindings_.emplace(FastStringKey { name.c_str() }, binding); + // TODO(addaleax): Do this in a better way when moving BaseObject to the + // internal fields callback variant. + binding->Detach(); + } + + if (snapshot_data->EndReadEntry().IsNothing()) return; + + Local ctx; + if (!snapshot_data->ReadObject(context).To(&ctx)) return; + if (ctx != context) { + snapshot_data->add_error( + "Context from snapshot does not match context provided to Environment"); + } + snapshot_data->EndReadEntry(); +} + +void Environment::Serialize(SnapshotCreateData* snapshot_data) const { + // This method should only be called after CreateEnvironment() finished. + if (!has_run_bootstrapping_code()) { + snapshot_data->add_error("Environment has not been bootstrapped yet"); + return; + } + + snapshot_data->StartWriteEntry("Environment"); + async_hooks()->Serialize(snapshot_data); + immediate_info()->Serialize(snapshot_data); + tick_info()->Serialize(snapshot_data); + stream_base_state_.Serialize(snapshot_data); + should_abort_on_uncaught_toggle_.Serialize(snapshot_data); + performance_state()->Serialize(snapshot_data); + + snapshot_data->WriteBool(can_call_into_js()); + + snapshot_data->WriteUint32(module_id_counter_); + snapshot_data->WriteUint32(script_id_counter_); + snapshot_data->WriteUint32(function_id_counter_); + + Isolate* isolate = this->isolate(); + HandleScope handle_scope(isolate); + Local context = this->context(); + + snapshot_data->StartWriteEntry("StrongPersistentTemplates"); +#define V(PropertyName, TypeName) \ + snapshot_data->WriteContextIndependentObject(PropertyName()); + ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) +#undef V + snapshot_data->EndWriteEntry(); + + snapshot_data->StartWriteEntry("StrongPersistentValues"); +#define V(PropertyName, TypeName) \ + snapshot_data->WriteObject(context, PropertyName()); + ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) +#undef V + snapshot_data->EndWriteEntry(); + + snapshot_data->StartWriteEntry("NativeModules"); + std::vector native_modules; + native_modules.insert(native_modules.end(), + native_modules_with_cache.begin(), + native_modules_with_cache.end()); + native_modules.insert(native_modules.end(), + native_modules_without_cache.begin(), + native_modules_without_cache.end()); + // This one should currently always be empty, so this is just future-proofing. + native_modules.insert(native_modules.end(), + native_modules_in_snapshot.begin(), + native_modules_in_snapshot.end()); + snapshot_data->WriteUint64(native_modules.size()); + for (const std::string& str : native_modules) + snapshot_data->WriteString(str); + snapshot_data->EndWriteEntry(); + + snapshot_data->StartWriteEntry("BaseObjects"); + size_t expected_base_object_count = + initial_base_object_count_ + base_object_count(); + snapshot_data->WriteUint64(expected_base_object_count); + + size_t observed_base_object_count = 0; + ForEachBaseObject([&](const BaseObject* obj) { + observed_base_object_count++; + obj->Serialize(snapshot_data); + }); + CHECK_EQ(observed_base_object_count, expected_base_object_count); + snapshot_data->EndWriteEntry(); + + snapshot_data->StartWriteEntry("bindings"); + snapshot_data->WriteUint32(bindings_.size()); + for (const auto& binding : bindings_) { + snapshot_data->WriteString(binding.first.c_str()); + snapshot_data->WriteBaseObjectPtr(context, binding.second); + } + + snapshot_data->EndWriteEntry(); + + snapshot_data->WriteObject(context, context); + snapshot_data->EndWriteEntry(); +} + std::string GetExecPath(const std::vector& argv) { char exec_path_buf[2 * PATH_MAX]; size_t exec_path_len = sizeof(exec_path_buf); @@ -322,18 +514,27 @@ Environment::Environment(IsolateData* isolate_data, ThreadId thread_id) : isolate_(context->GetIsolate()), isolate_data_(isolate_data), - immediate_info_(context->GetIsolate()), - tick_info_(context->GetIsolate()), + context_(isolate_, context), + async_hooks_( + // Read the `Environment` start tag if deserializing from snapshot -- + // slightly awkward place for this, but it needs to happen before the + // AsyncHooks() constructor. Comma operator to the rescue! + (isolate_data->snapshot_data() != nullptr && + isolate_data->snapshot_data()->StartReadEntry("Environment") + .IsNothing(), + this)), + immediate_info_(this), + tick_info_(this), 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), flags_(flags), thread_id_(thread_id.id == static_cast(-1) ? - AllocateEnvironmentThreadId().id : thread_id.id), - context_(context->GetIsolate(), context) { + AllocateEnvironmentThreadId().id : thread_id.id) { + if (isolate_data->snapshot_data() != nullptr && + !isolate_data->snapshot_data()->errors().empty()) return; + // We'll be creating new objects so make sure we've entered the context. HandleScope handle_scope(isolate()); Context::Scope context_scope(context); @@ -383,8 +584,26 @@ Environment::Environment(IsolateData* isolate_data, }, this); - performance_state_ = - std::make_unique(isolate()); + if (isolate_data->snapshot_data() != nullptr) { + stream_base_state_ = AliasedInt32Array( + context, isolate_data->snapshot_data()); + should_abort_on_uncaught_toggle_ = AliasedUint32Array( + context, isolate_data->snapshot_data()); + performance_state_ = std::make_unique( + context, isolate_data->snapshot_data()); + } else { + stream_base_state_ = AliasedInt32Array( + isolate(), StreamBase::kNumStreamBaseStateFields); + should_abort_on_uncaught_toggle_ = AliasedUint32Array(isolate(), 1); + // By default, always abort when --abort-on-uncaught-exception was passed. + should_abort_on_uncaught_toggle_[0] = 1; + performance_state_ = std::make_unique( + isolate()); + } + + if (isolate_data->snapshot_data() != nullptr && + !isolate_data->snapshot_data()->errors().empty()) return; + performance_state_->Mark( performance::NODE_PERFORMANCE_MILESTONE_ENVIRONMENT); performance_state_->Mark(performance::NODE_PERFORMANCE_MILESTONE_NODE_START, @@ -409,16 +628,14 @@ 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(); } - // TODO(joyeecheung): deserialize when the snapshot covers the environment - // properties. - CreateProperties(); + if (isolate_data->snapshot_data() == nullptr) + CreateProperties(); + else + DeserializeProperties(); // 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. @@ -605,7 +822,8 @@ void Environment::CleanupHandles() { Isolate::DisallowJavascriptExecutionScope disallow_js(isolate(), Isolate::DisallowJavascriptExecutionScope::THROW_ON_FAILURE); - RunAndClearNativeImmediates(true /* skip SetUnrefImmediate()s */); + if (immediate_info()->fields().Length() > 0) // Check whether initialized + RunAndClearNativeImmediates(true /* skip SetUnrefImmediate()s */); for (ReqWrapBase* request : req_wrap_queue_) request->Cancel(); @@ -1006,16 +1224,40 @@ void ImmediateInfo::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("fields", fields_); } +void ImmediateInfo::Serialize(SnapshotCreateData* snapshot_data) const { + snapshot_data->StartWriteEntry("ImmediateInfo"); + fields_.Serialize(snapshot_data); + snapshot_data->EndWriteEntry(); +} + void TickInfo::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("fields", fields_); } +void TickInfo::Serialize(SnapshotCreateData* snapshot_data) const { + snapshot_data->StartWriteEntry("TickInfo"); + fields_.Serialize(snapshot_data); + snapshot_data->EndWriteEntry(); +} + void AsyncHooks::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("async_ids_stack", async_ids_stack_); tracker->TrackField("fields", fields_); tracker->TrackField("async_id_fields", async_id_fields_); } +void AsyncHooks::Serialize(SnapshotCreateData* snapshot_data) const { + snapshot_data->StartWriteEntry("AsyncHooks"); + async_ids_stack_.Serialize(snapshot_data); + fields_.Serialize(snapshot_data); + async_id_fields_.Serialize(snapshot_data); + + snapshot_data->WriteObject( + env()->context(), execution_async_resources_.Get(env()->isolate())); + + snapshot_data->EndWriteEntry(); +} + void AsyncHooks::grow_async_ids_stack() { async_ids_stack_.reserve(async_ids_stack_.Length() * 3); @@ -1206,4 +1448,102 @@ Local BaseObject::GetConstructorTemplate(Environment* env) { return tmpl; } +StartupData BaseObject::SerializeInternalFields( + Local object, int index, void* data) { + // TODO(addaleax): Not an ideal way to get the Environment. + Environment* env = Environment::GetCurrent(object->CreationContext()); + if (env == nullptr || !GetConstructorTemplate(env)->HasInstance(object)) + return {}; + const BaseObject* self = FromJSObject(object); + if (self == nullptr) return {}; // No BaseObject associated with `object`. + CHECK_EQ(self->object(), object); + return self->SerializeInternalFields( + index, static_cast(data)); +} + +StartupData BaseObject::SerializeInternalFields( + int index, SnapshotCreateData* snapshot_data) const { + if (index == BaseObject::kSlot) { + // The 0th slot points back to the BaseObject* itself. We don't need to + // serialize it, the serialization of the global handle should have already + // occurred, and when deserializing, the BaseObject* slot should be reset + // with the correct value anyway. + return {}; + } + snapshot_data->add_error( + SPrintF("Missing override of SerializeInternalFields() for BaseObject of " + "type %s for internal field %d", MemoryInfoName(), index)); + return {}; +} + +AsyncHooks::AsyncHooks(Environment* env) { + if (env->isolate_data()->snapshot_data() != nullptr) { + SnapshotReadData* snapshot_data = env->isolate_data()->snapshot_data(); + + if (snapshot_data->StartReadEntry("AsyncHooks").IsNothing()) return; + async_ids_stack_ = AliasedFloat64Array(env->context(), snapshot_data); + fields_ = AliasedUint32Array(env->context(), snapshot_data); + async_id_fields_ = AliasedFloat64Array(env->context(), snapshot_data); + + Local execution_async_resources; + if (!snapshot_data->ReadObject(env->context()) + .To(&execution_async_resources)) { + return; + } + execution_async_resources_.Reset(env->isolate(), execution_async_resources); + + snapshot_data->EndReadEntry(); + return; + } + + async_ids_stack_ = AliasedFloat64Array(env->isolate(), 16 * 2); + fields_ = AliasedUint32Array(env->isolate(), kFieldsCount); + async_id_fields_ = AliasedFloat64Array(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; +} + +TickInfo::TickInfo(Environment* env) { + if (env->isolate_data()->snapshot_data() != nullptr) { + SnapshotReadData* snapshot_data = env->isolate_data()->snapshot_data(); + + if (snapshot_data->StartReadEntry("TickInfo").IsNothing()) return; + fields_ = AliasedUint8Array(env->context(), snapshot_data); + + snapshot_data->EndReadEntry(); + return; + } + fields_ = AliasedUint8Array(env->isolate(), kFieldsCount); +} + +ImmediateInfo::ImmediateInfo(Environment* env) { + if (env->isolate_data()->snapshot_data() != nullptr) { + SnapshotReadData* snapshot_data = env->isolate_data()->snapshot_data(); + + if (snapshot_data->StartReadEntry("ImmediateInfo").IsNothing()) return; + fields_ = AliasedUint32Array(env->context(), snapshot_data); + + snapshot_data->EndReadEntry(); + return; + } + fields_ = AliasedUint32Array(env->isolate(), kFieldsCount); +} + } // namespace node diff --git a/src/env.h b/src/env.h index af813ef720714c..10cbcf6257115b 100644 --- a/src/env.h +++ b/src/env.h @@ -468,18 +468,19 @@ constexpr size_t kFsStatsBufferLength = V(url_constructor_function, v8::Function) class Environment; +class SnapshotReadData; -class IsolateData : public MemoryRetainer { +class IsolateData : public MemoryRetainer, public Snapshottable { public: IsolateData(v8::Isolate* isolate, uv_loop_t* event_loop, MultiIsolatePlatform* platform = nullptr, ArrayBufferAllocator* node_allocator = nullptr, - const std::vector* indexes = nullptr); + SnapshotReadData* snapshot_data = nullptr); SET_MEMORY_INFO_NAME(IsolateData) SET_SELF_SIZE(IsolateData) void MemoryInfo(MemoryTracker* tracker) const override; - std::vector Serialize(v8::SnapshotCreator* creator); + void Serialize(SnapshotCreateData* snapshot_data) const override; inline uv_loop_t* event_loop() const; inline MultiIsolatePlatform* platform() const; @@ -493,6 +494,8 @@ class IsolateData : public MemoryRetainer { inline worker::Worker* worker_context() const; inline void set_worker_context(worker::Worker* context); + inline SnapshotReadData* snapshot_data() const; + #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) #define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName) #define VS(PropertyName, StringValue) V(v8::String, PropertyName) @@ -516,7 +519,7 @@ class IsolateData : public MemoryRetainer { IsolateData& operator=(IsolateData&&) = delete; private: - void DeserializeProperties(const std::vector* indexes); + void DeserializeProperties(); void CreateProperties(); #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) @@ -543,6 +546,7 @@ class IsolateData : public MemoryRetainer { MultiIsolatePlatform* platform_; std::shared_ptr options_; worker::Worker* worker_context_ = nullptr; + SnapshotReadData* const snapshot_data_; }; struct ContextInfo { @@ -618,7 +622,7 @@ namespace per_process { extern std::shared_ptr system_environment; } -class AsyncHooks : public MemoryRetainer { +class AsyncHooks : public MemoryRetainer, public Snapshottable { public: SET_MEMORY_INFO_NAME(AsyncHooks) SET_SELF_SIZE(AsyncHooks) @@ -655,6 +659,7 @@ class AsyncHooks : public MemoryRetainer { inline void no_force_checks(); inline Environment* env(); + inline const Environment* env() const; inline void push_async_context(double async_id, double trigger_async_id, v8::Local execution_async_resource_); @@ -689,9 +694,11 @@ class AsyncHooks : public MemoryRetainer { double old_default_trigger_async_id_; }; + void Serialize(SnapshotCreateData* snapshot_data) const override; + + explicit AsyncHooks(Environment* env); + private: - friend class Environment; // So we can call the constructor. - inline AsyncHooks(); // 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 @@ -705,7 +712,7 @@ class AsyncHooks : public MemoryRetainer { v8::Global execution_async_resources_; }; -class ImmediateInfo : public MemoryRetainer { +class ImmediateInfo : public MemoryRetainer, public Snapshottable { public: inline AliasedUint32Array& fields(); inline uint32_t count() const; @@ -724,16 +731,17 @@ class ImmediateInfo : public MemoryRetainer { SET_SELF_SIZE(ImmediateInfo) void MemoryInfo(MemoryTracker* tracker) const override; - private: - friend class Environment; // So we can call the constructor. - inline explicit ImmediateInfo(v8::Isolate* isolate); + void Serialize(SnapshotCreateData* snapshot_data) const override; + explicit ImmediateInfo(Environment* env); + + private: enum Fields { kCount, kRefCount, kHasOutstanding, kFieldsCount }; AliasedUint32Array fields_; }; -class TickInfo : public MemoryRetainer { +class TickInfo : public MemoryRetainer, public Snapshottable { public: inline AliasedUint8Array& fields(); inline bool has_tick_scheduled() const; @@ -749,10 +757,11 @@ class TickInfo : public MemoryRetainer { TickInfo& operator=(TickInfo&&) = delete; ~TickInfo() = default; - private: - friend class Environment; // So we can call the constructor. - inline explicit TickInfo(v8::Isolate* isolate); + void Serialize(SnapshotCreateData* snapshot_data) const override; + + explicit TickInfo(Environment* env); + private: enum Fields { kHasTickScheduled = 0, kHasRejectionToWarn, kFieldsCount }; AliasedUint8Array fields_; @@ -823,7 +832,7 @@ class CleanupHookCallback { uint64_t insertion_order_counter_; }; -class Environment : public MemoryRetainer { +class Environment : public MemoryRetainer, public Snapshottable { public: Environment(const Environment&) = delete; Environment& operator=(const Environment&) = delete; @@ -836,7 +845,8 @@ class Environment : public MemoryRetainer { bool IsRootNode() const override { return true; } void MemoryInfo(MemoryTracker* tracker) const override; - void CreateProperties(); + void Serialize(SnapshotCreateData* snapshot_data) const override; + // Should be called before InitializeInspector() void InitializeDiagnostics(); #if HAVE_INSPECTOR @@ -944,8 +954,11 @@ class Environment : public MemoryRetainer { inline void IncreaseWaitingRequestCounter(); inline void DecreaseWaitingRequestCounter(); + inline const AsyncHooks* async_hooks() const; inline AsyncHooks* async_hooks(); + inline const ImmediateInfo* immediate_info() const; inline ImmediateInfo* immediate_info(); + inline const TickInfo* tick_info() const; inline TickInfo* tick_info(); inline uint64_t timer_base() const; inline std::shared_ptr env_vars(); @@ -990,6 +1003,7 @@ class Environment : public MemoryRetainer { std::set native_modules_with_cache; std::set native_modules_without_cache; + std::set native_modules_in_snapshot; std::unordered_multimap hash_to_module_map; std::unordered_map id_to_module_map; @@ -1004,6 +1018,7 @@ class Environment : public MemoryRetainer { EnabledDebugList* enabled_debug_list() { return &enabled_debug_list_; } inline performance::PerformanceState* performance_state(); + inline const performance::PerformanceState* performance_state() const; inline std::unordered_map* performance_marks(); void CollectUVExceptionInfo(v8::Local context, @@ -1251,6 +1266,9 @@ class Environment : public MemoryRetainer { void RunAndClearInterrupts(); private: + void CreateProperties(); + void DeserializeProperties(); + template inline void CreateImmediate(Fn&& cb, bool ref); @@ -1260,6 +1278,7 @@ class Environment : public MemoryRetainer { std::list loaded_addons_; v8::Isolate* const isolate_; IsolateData* const isolate_data_; + v8::Global context_; uv_timer_t timer_handle_; uv_check_t immediate_check_handle_; uv_idle_t immediate_idle_handle_; @@ -1443,15 +1462,13 @@ class Environment : public MemoryRetainer { DefaultProcessExitHandler }; template - void ForEachBaseObject(T&& iterator); + void ForEachBaseObject(T&& iterator) const; #define V(PropertyName, TypeName) v8::Global PropertyName ## _; ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) #undef V - v8::Global context_; - // Keeps the main script source alive is one was passed to LoadEnvironment(). // We should probably find a way to just use plain `v8::String`s created from // the source passed to LoadEnvironment() directly instead. diff --git a/src/node.cc b/src/node.cc index d2b365cb0ca39f..cc1b945a7650ca 100644 --- a/src/node.cc +++ b/src/node.cc @@ -117,6 +117,7 @@ #include #include +#include namespace node { @@ -983,6 +984,18 @@ InitializationResult InitializeOncePerProcess(int argc, char** argv) { UNREACHABLE(); } + if (per_process::cli_options->dump_snapshot) { + SnapshotReadData* snapshot_data = NodeMainInstance::GetSnapshotData(); + if (snapshot_data == nullptr) { + fprintf(stderr, "No snapshot data provided\n"); + } else { + snapshot_data->DumpToStderr(); + } + result.exit_code = 0; + result.early_return = true; + return result; + } + #if HAVE_OPENSSL { std::string extra_ca_certs; @@ -1028,10 +1041,11 @@ int Start(int argc, char** argv) { { Isolate::CreateParams params; - const std::vector* indexes = nullptr; std::vector external_references = ExternalReferences::get_list(); external_references.push_back(ExternalReferences::kEnd); + SnapshotReadData* snapshot_data = nullptr; + bool force_no_snapshot = per_process::cli_options->per_isolate->no_node_snapshot; if (!force_no_snapshot) { @@ -1039,7 +1053,7 @@ int Start(int argc, char** argv) { if (blob != nullptr) { params.external_references = external_references.data(); params.snapshot_blob = blob; - indexes = NodeMainInstance::GetIsolateDataIndexes(); + snapshot_data = NodeMainInstance::GetSnapshotData(); } } @@ -1048,7 +1062,7 @@ int Start(int argc, char** argv) { per_process::v8_platform.Platform(), result.args, result.exec_args, - indexes); + snapshot_data); result.exit_code = main_instance.Run(); } diff --git a/src/node_main_instance.cc b/src/node_main_instance.cc index 033ab56188bfee..6e46d8a70a9d63 100644 --- a/src/node_main_instance.cc +++ b/src/node_main_instance.cc @@ -4,6 +4,7 @@ #include "node_internals.h" #include "node_options-inl.h" #include "node_v8_platform-inl.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #if defined(LEAK_SANITIZER) #include @@ -23,11 +24,12 @@ using v8::Locker; using v8::Object; using v8::SealHandleScope; -NodeMainInstance::NodeMainInstance(Isolate* isolate, - uv_loop_t* event_loop, - MultiIsolatePlatform* platform, - const std::vector& args, - const std::vector& exec_args) +NodeMainInstance::NodeMainInstance( + Isolate* isolate, + uv_loop_t* event_loop, + MultiIsolatePlatform* platform, + const std::vector& args, + const std::vector& exec_args) : args_(args), exec_args_(exec_args), array_buffer_allocator_(nullptr), @@ -36,8 +38,9 @@ NodeMainInstance::NodeMainInstance(Isolate* isolate, isolate_data_(nullptr), owns_isolate_(false), deserialize_mode_(false) { - isolate_data_ = - std::make_unique(isolate_, event_loop, platform, nullptr); + // TODO(addaleax): Use CreateIsolateData. + isolate_data_ = std::make_unique( + isolate_, event_loop, platform, nullptr, nullptr); IsolateSettings misc; SetIsolateMiscHandlers(isolate_, misc); @@ -49,8 +52,8 @@ std::unique_ptr NodeMainInstance::Create( MultiIsolatePlatform* platform, const std::vector& args, const std::vector& exec_args) { - return std::unique_ptr( - new NodeMainInstance(isolate, event_loop, platform, args, exec_args)); + return std::unique_ptr(new NodeMainInstance( + isolate, event_loop, platform, args, exec_args)); } NodeMainInstance::NodeMainInstance( @@ -59,7 +62,7 @@ NodeMainInstance::NodeMainInstance( MultiIsolatePlatform* platform, const std::vector& args, const std::vector& exec_args, - const std::vector* per_isolate_data_indexes) + SnapshotReadData* snapshot_data) : args_(args), exec_args_(exec_args), array_buffer_allocator_(ArrayBufferAllocator::Create()), @@ -70,20 +73,23 @@ NodeMainInstance::NodeMainInstance( params->array_buffer_allocator = array_buffer_allocator_.get(); isolate_ = Isolate::Allocate(); CHECK_NOT_NULL(isolate_); + if (snapshot_data != nullptr) + snapshot_data->set_isolate(isolate_); // Register the isolate on the platform before the isolate gets initialized, // so that the isolate can access the platform during initialization. platform->RegisterIsolate(isolate_, event_loop); SetIsolateCreateParamsForNode(params); Isolate::Initialize(isolate_, *params); - deserialize_mode_ = per_isolate_data_indexes != nullptr; + deserialize_mode_ = snapshot_data != nullptr; // If the indexes are not nullptr, we are not deserializing CHECK_IMPLIES(deserialize_mode_, params->external_references != nullptr); + // TODO(addaleax): Use CreateIsolateData. isolate_data_ = std::make_unique(isolate_, event_loop, platform, array_buffer_allocator_.get(), - per_isolate_data_indexes); + snapshot_data); IsolateSettings s; SetIsolateMiscHandlers(isolate_, s); if (!deserialize_mode_) { @@ -207,6 +213,15 @@ NodeMainInstance::CreateMainEnvironment(int* exit_code) { exec_args_, EnvironmentFlags::kDefaultFlags) }; + if (deserialize_mode_) { + SnapshotReadData* snapshot_data = isolate_data_->snapshot_data(); + CHECK_NOT_NULL(snapshot_data); + if (snapshot_data->errors().empty()) + snapshot_data->Finish(); + + snapshot_data->PrintErrorsAndAbortIfAny(); + } + if (*exit_code != 0) { return env; } diff --git a/src/node_main_instance.h b/src/node_main_instance.h index b8178c2774e795..ecbb5092bc7900 100644 --- a/src/node_main_instance.h +++ b/src/node_main_instance.h @@ -7,12 +7,15 @@ #include #include "node.h" +#include "snapshot_support.h" #include "util.h" #include "uv.h" #include "v8.h" namespace node { +class SnapshotReadData; + // TODO(joyeecheung): align this with the Worker/WorkerThreadData class. // We may be able to create an abstract class to reuse some of the routines. class NodeMainInstance { @@ -51,7 +54,7 @@ class NodeMainInstance { MultiIsolatePlatform* platform, const std::vector& args, const std::vector& exec_args, - const std::vector* per_isolate_data_indexes = nullptr); + SnapshotReadData* snapshot_data = nullptr); ~NodeMainInstance(); // Start running the Node.js instances, return the exit code when finished. @@ -64,7 +67,7 @@ class NodeMainInstance { // If nullptr is returned, the binary is not built with embedded // snapshot. - static const std::vector* GetIsolateDataIndexes(); + static SnapshotReadData* GetSnapshotData(); static v8::StartupData* GetEmbeddedSnapshotBlob(); static const size_t kNodeContextIndex = 0; diff --git a/src/node_native_module_env.cc b/src/node_native_module_env.cc index 140b7d1fb234d6..402be4ab51d566 100644 --- a/src/node_native_module_env.cc +++ b/src/node_native_module_env.cc @@ -96,6 +96,11 @@ void NativeModuleEnv::GetCacheUsage(const FunctionCallbackInfo& args) { OneByteString(isolate, "compiledWithoutCache"), ToJsSet(context, env->native_modules_without_cache)) .FromJust(); + result + ->Set(env->context(), + OneByteString(isolate, "includedInSnapshot"), + ToJsSet(context, env->native_modules_in_snapshot)) + .FromJust(); args.GetReturnValue().Set(result); } diff --git a/src/node_options.cc b/src/node_options.cc index 3b9142c19e98a8..42f4e05094d315 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -649,6 +649,9 @@ PerProcessOptionsParser::PerProcessOptionsParser( AddOption("--v8-options", "print V8 command line options", &PerProcessOptions::print_v8_help); + AddOption("--dump-snapshot", + "", // No help because this is for Node.js core developers. + &PerProcessOptions::dump_snapshot); AddOption("--report-compact", "output compact single-line JSON", &PerProcessOptions::report_compact, diff --git a/src/node_options.h b/src/node_options.h index 539e41e67ac6ee..46316bec5bde27 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -216,6 +216,7 @@ class PerProcessOptions : public Options { bool print_help = false; bool print_v8_help = false; bool print_version = false; + bool dump_snapshot = false; #ifdef NODE_HAVE_I18N_SUPPORT std::string icu_data_dir; diff --git a/src/node_perf.cc b/src/node_perf.cc index 40ea9daffa19f5..13def082e251e6 100644 --- a/src/node_perf.cc +++ b/src/node_perf.cc @@ -4,6 +4,7 @@ #include "node_perf.h" #include "node_buffer.h" #include "node_process.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include @@ -53,6 +54,41 @@ void PerformanceState::Mark(enum PerformanceMilestone milestone, TRACE_EVENT_SCOPE_THREAD, ts / 1000); } +PerformanceState::PerformanceState(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.; +} + +PerformanceState::PerformanceState(Local context, + SnapshotReadData* snapshot_data) { + if (snapshot_data->StartReadEntry("PerformanceState").IsNothing()) return; + root = AliasedUint8Array(context, snapshot_data); + milestones = AliasedFloat64Array(context, snapshot_data); + observers = AliasedUint32Array(context, snapshot_data); + snapshot_data->EndReadEntry(); +} + +void PerformanceState::Serialize(SnapshotCreateData* snapshot_data) const { + snapshot_data->StartWriteEntry("PerformanceState"); + root.Serialize(snapshot_data); + milestones.Serialize(snapshot_data); + observers.Serialize(snapshot_data); + snapshot_data->EndWriteEntry(); +} + // Initialize the performance entry object properties inline void InitObject(const PerformanceEntry& entry, Local obj) { Environment* env = entry.env(); diff --git a/src/node_perf_common.h b/src/node_perf_common.h index 75d266afc257e9..fad3f8b9f71566 100644 --- a/src/node_perf_common.h +++ b/src/node_perf_common.h @@ -52,25 +52,11 @@ enum PerformanceEntryType { NODE_PERFORMANCE_ENTRY_TYPE_INVALID }; -class PerformanceState { +class PerformanceState final : public Snapshottable { 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.; - } + explicit PerformanceState(v8::Isolate* isolate); + PerformanceState(v8::Local context, + SnapshotReadData* snapshot_data); AliasedUint8Array root; AliasedFloat64Array milestones; @@ -81,6 +67,8 @@ class PerformanceState { void Mark(enum PerformanceMilestone milestone, uint64_t ts = PERFORMANCE_NOW()); + void Serialize(SnapshotCreateData* snapshot_data) const override; + private: struct performance_state_internal { // doubles first so that they are always sizeof(double)-aligned diff --git a/src/node_snapshot_stub.cc b/src/node_snapshot_stub.cc index fac03b0c87af5d..0ae3b041b2e379 100644 --- a/src/node_snapshot_stub.cc +++ b/src/node_snapshot_stub.cc @@ -10,7 +10,7 @@ v8::StartupData* NodeMainInstance::GetEmbeddedSnapshotBlob() { return nullptr; } -const std::vector* NodeMainInstance::GetIsolateDataIndexes() { +SnapshotReadData* NodeMainInstance::GetSnapshotData() { return nullptr; } diff --git a/src/node_worker.cc b/src/node_worker.cc index 1e1d9434cddb4c..dc76f86493d13d 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -5,6 +5,7 @@ #include "node_buffer.h" #include "node_options-inl.h" #include "node_perf.h" +#include "snapshot_support-inl.h" #include "util-inl.h" #include "async_wrap-inl.h" diff --git a/src/snapshot_support-inl.h b/src/snapshot_support-inl.h index a4320153a419bd..3d81839b5d2eb9 100644 --- a/src/snapshot_support-inl.h +++ b/src/snapshot_support-inl.h @@ -7,6 +7,113 @@ namespace node { +const std::vector& SnapshotDataBase::errors() const { + return state_.errors; +} + +std::vector SnapshotDataBase::storage() { + return storage_; +} + +SnapshotDataBase::SnapshotDataBase(std::vector&& storage) + : storage_(storage) {} + + +template +void SnapshotCreateData::WriteContextIndependentObject(v8::Local data) { + WriteTag(kContextIndependentObjectTag); + WriteIndex(data.IsEmpty() ? kEmptyIndex : creator()->AddData(data)); +} + +template +void SnapshotCreateData::WriteObject( + v8::Local context, v8::Local data) { + WriteTag(kObjectTag); + WriteIndex(data.IsEmpty() ? kEmptyIndex : creator()->AddData(context, data)); +} + +template +void SnapshotCreateData::WriteBaseObjectPtr( + v8::Local context, BaseObjectPtrImpl ptr) { + WriteObject(context, ptr ? ptr->object() : v8::Local()); +} + +v8::Isolate* SnapshotCreateData::isolate() { + return creator()->GetIsolate(); +} + +v8::SnapshotCreator* SnapshotCreateData::creator() { + return creator_; +} + +SnapshotCreateData::SnapshotCreateData(v8::SnapshotCreator* creator) + : creator_(creator) {} + +template +v8::Maybe> SnapshotReadData::ReadContextIndependentObject( + EmptyHandleMode mode) { + if (!ReadTag(kContextIndependentObjectTag)) + return v8::Nothing>(); + V8SnapshotIndex index; + if (!ReadIndex().To(&index)) return v8::Nothing>(); + if (index == kEmptyIndex) { + if (mode == kAllowEmpty) return v8::Just(v8::Local()); + add_error("Empty handle in serialized data was rejected"); + return v8::Nothing>(); + } + v8::MaybeLocal ret = isolate()->GetDataFromSnapshotOnce(index); + if (ret.IsEmpty()) { + add_error("Could not get context-independent object from snapshot"); + return v8::Nothing>(); + } + return v8::Just(ret.ToLocalChecked()); +} + +template +v8::Maybe> SnapshotReadData::ReadObject( + v8::Local context, EmptyHandleMode mode) { + if (!ReadTag(kObjectTag)) return v8::Nothing>(); + V8SnapshotIndex index; + if (!ReadIndex().To(&index)) return v8::Nothing>(); + if (index == kEmptyIndex) { + if (mode == kAllowEmpty) return v8::Just(v8::Local()); + add_error("Empty handle in serialized data was rejected"); + return v8::Nothing>(); + } + v8::MaybeLocal ret = context->GetDataFromSnapshotOnce(index); + if (ret.IsEmpty()) { + add_error("Could not get context-dependent object from snapshot"); + return v8::Nothing>(); + } + return v8::Just(ret.ToLocalChecked()); +} + +template +v8::Maybe> SnapshotReadData::ReadBaseObjectPtr( + v8::Local context, EmptyHandleMode mode) { + v8::Local obj; + if (!ReadObject(context, mode).To(&obj)) { + return v8::Nothing>(); + } + if (obj.IsEmpty()) return v8::Just(BaseObjectPtrImpl()); + BaseObject* base_obj; + if (!GetBaseObjectFromV8Object(context, obj).To(&base_obj)) { + return v8::Nothing>(); + } + return v8::Just(BaseObjectPtrImpl(base_obj)); +} + +v8::Isolate* SnapshotReadData::isolate() { + return isolate_; +} + +void SnapshotReadData::set_isolate(v8::Isolate* isolate) { + isolate_ = isolate; +} + +SnapshotReadData::SnapshotReadData(std::vector&& storage) + : SnapshotDataBase(std::move(storage)) {} + template ExternalReferences::ExternalReferences(const char* id, Args*... args) { Register(id, this); diff --git a/src/snapshot_support.cc b/src/snapshot_support.cc index d583c833199010..129e2e6bbeeae9 100644 --- a/src/snapshot_support.cc +++ b/src/snapshot_support.cc @@ -1,9 +1,449 @@ #include "snapshot_support.h" // NOLINT(build/include_inline) #include "snapshot_support-inl.h" +#include "debug_utils-inl.h" +#include "env-inl.h" +#include "json_utils.h" // EscapeJsonChars #include "util.h" +#include +#include // std::setw + +using v8::Context; +using v8::Just; +using v8::Local; +using v8::Maybe; +using v8::Nothing; +using v8::Object; namespace node { +Snapshottable::~Snapshottable() {} + +void Snapshottable::Serialize(SnapshotCreateData* snapshot_data) const { + snapshot_data->add_error("Unserializable object encountered"); +} + +#define SNAPSHOT_TAGS(V) \ + V(kStartEntry) \ + V(kEndEntry) \ + V(kBool) \ + V(kInt32) \ + V(kInt64) \ + V(kUint32) \ + V(kUint64) \ + V(kIndex) \ + V(kString) \ + V(kContextIndependentObject) \ + V(kObject) \ + +enum Tag { +#define V(name) name, + SNAPSHOT_TAGS(V) +#undef V +}; + +static std::string TagName(int tag) { +#define V(name) if (tag == name) return #name; + SNAPSHOT_TAGS(V) +#undef V + return SPrintF("(unknown tag %d)", tag); +} + +const uint8_t SnapshotDataBase::kContextIndependentObjectTag = + kContextIndependentObject; +const uint8_t SnapshotDataBase::kObjectTag = kObject; + +SnapshotDataBase::SaveStateScope::SaveStateScope( + SnapshotDataBase* snapshot_data) + : snapshot_data_(snapshot_data), state_(snapshot_data->state_) { +} + +SnapshotDataBase::SaveStateScope::~SaveStateScope() { + snapshot_data_->state_ = std::move(state_); +} + + +bool SnapshotDataBase::HasSpace(size_t length) const { + return storage_.size() - state_.current_index >= length; +} + +void SnapshotCreateData::WriteRawData(const uint8_t* data, size_t length) { + storage_.resize(storage_.size() + length); + memcpy(storage_.data() + state_.current_index, data, length); + state_.current_index += length; + CHECK_EQ(state_.current_index, storage_.size()); +} + +bool SnapshotReadData::ReadRawData(uint8_t* data, size_t length) { + if (UNLIKELY(!HasSpace(length))) { + add_error("Unexpected end of snapshot data input"); + return false; + } + memcpy(data, storage_.data() + state_.current_index, length); + state_.current_index += length; + return true; +} + +void SnapshotCreateData::WriteTag(uint8_t tag) { + WriteRawData(&tag, 1); +} + +bool SnapshotReadData::ReadTag(uint8_t expected) { + uint8_t actual; + if (!ReadRawData(&actual, 1)) return false; + if (actual != expected) { + add_error(SPrintF("Tried to read object of type %s, found type %s instead", + TagName(expected), TagName(actual))); + return false; + } + return true; +} + +Maybe SnapshotReadData::PeekTag() { + SaveStateScope state_scope(this); + uint8_t tag; + if (!ReadRawData(&tag, 1)) return Nothing(); + return Just(tag); +} + +void SnapshotCreateData::StartWriteEntry(const char* name) { + WriteTag(kStartEntry); + WriteString(name); + state_.entry_stack.push_back(name); +} + +void SnapshotCreateData::EndWriteEntry() { + if (state_.entry_stack.empty()) { + add_error("Attempting to end entry on empty stack, " + "more EndWriteEntry() than StartWriteEntry() calls"); + return; + } + + state_.entry_stack.pop_back(); + WriteTag(kEndEntry); +} + +void SnapshotCreateData::WriteBool(bool value) { + WriteTag(kBool); + uint8_t data = value ? 1 : 0; + WriteRawData(&data, 1); +} + +void SnapshotCreateData::WriteInt32(int32_t value) { + WriteTag(kInt32); + WriteRawData(reinterpret_cast(&value), sizeof(value)); +} + +void SnapshotCreateData::WriteInt64(int64_t value) { + WriteTag(kInt64); + WriteRawData(reinterpret_cast(&value), sizeof(value)); +} + +void SnapshotCreateData::WriteUint32(uint32_t value) { + WriteTag(kUint32); + WriteRawData(reinterpret_cast(&value), sizeof(value)); +} + +void SnapshotCreateData::WriteUint64(uint64_t value) { + WriteTag(kUint64); + WriteRawData(reinterpret_cast(&value), sizeof(value)); +} + +void SnapshotCreateData::WriteIndex(V8SnapshotIndex value) { + WriteTag(kIndex); + WriteRawData(reinterpret_cast(&value), sizeof(value)); +} + +void SnapshotCreateData::WriteString(const char* str, size_t length) { + WriteTag(kString); + if (length == static_cast(-1)) length = strlen(str); + WriteUint64(length); + WriteRawData(reinterpret_cast(str), length); +} + +void SnapshotCreateData::WriteString(const std::string& str) { + WriteString(str.c_str(), str.size()); +} + +v8::Maybe SnapshotReadData::StartReadEntry(const char* expected) { + if (!ReadTag(kStartEntry)) return Nothing(); + std::string actual; + if (!ReadString().To(&actual)) return Nothing(); + if (expected != nullptr && actual != expected) { + add_error(SPrintF("Tried to read start of entry %s, found entry %s", + expected, actual)); + return Nothing(); + } + state_.entry_stack.push_back(actual); + return Just(std::move(actual)); +} + +v8::Maybe SnapshotReadData::EndReadEntry() { + if (!ReadTag(kEndEntry)) return Nothing(); + if (state_.entry_stack.empty()) { + add_error("Attempting to end entry on empty stack, " + "more EndReadEntry() than StartReadEntry() calls"); + return Nothing(); + } + state_.entry_stack.pop_back(); + return Just(true); +} + +v8::Maybe SnapshotReadData::ReadBool() { + if (!ReadTag(kBool)) return Nothing(); + uint8_t value; + if (!ReadRawData(&value, 1)) return Nothing(); + return Just(static_cast(value)); +} + +v8::Maybe SnapshotReadData::ReadInt32() { + if (!ReadTag(kInt32)) return Nothing(); + int32_t value; + if (!ReadRawData(reinterpret_cast(&value), sizeof(value))) + return Nothing(); + return Just(value); +} + +v8::Maybe SnapshotReadData::ReadInt64() { + if (!ReadTag(kInt64)) return Nothing(); + int64_t value; + if (!ReadRawData(reinterpret_cast(&value), sizeof(value))) + return Nothing(); + return Just(value); +} + +v8::Maybe SnapshotReadData::ReadUint32() { + if (!ReadTag(kUint32)) return Nothing(); + uint32_t value; + if (!ReadRawData(reinterpret_cast(&value), sizeof(value))) + return Nothing(); + return Just(static_cast(value)); +} + +v8::Maybe SnapshotReadData::ReadUint64() { + if (!ReadTag(kUint64)) return Nothing(); + uint64_t value; + if (!ReadRawData(reinterpret_cast(&value), sizeof(value))) + return Nothing(); + return Just(value); +} + +v8::Maybe SnapshotReadData::ReadIndex() { + if (!ReadTag(kIndex)) return Nothing(); + size_t value; + if (!ReadRawData(reinterpret_cast(&value), sizeof(value))) + return Nothing(); + return Just(value); +} + +v8::Maybe SnapshotReadData::ReadString() { + if (!ReadTag(kString)) return Nothing(); + uint64_t size; + if (!ReadUint64().To(&size)) return Nothing(); + std::string str(size, '\0'); + if (!ReadRawData(reinterpret_cast(&str[0]), size)) + return Nothing(); + return Just(std::move(str)); +} + +v8::Maybe SnapshotReadData::Finish() { + if (!state_.entry_stack.empty()) { + add_error("Entries left on snapshot stack, EndReadEntry() missing"); + return Nothing(); + } + + if (state_.current_index != storage_.size()) { + add_error("Unexpected data past the end of the snapshot data"); + return Nothing(); + } + + storage_.clear(); + storage_.shrink_to_fit(); + state_ = State{}; + return Just(true); +} + +void SnapshotDataBase::add_error(const std::string& error) { + std::string message = "At ["; + message += std::to_string(state_.current_index); + message += "] "; + for (const std::string& entry : state_.entry_stack) { + message += entry; + message += ':'; + } + message += " "; + message += error; + state_.errors.emplace_back( + Error { state_.current_index, std::move(message) }); +} + +std::string SnapshotReadData::DumpLine::ToString() const { + std::ostringstream os; + os << std::setw(6) << index << ' '; + for (size_t i = 0; i < depth; i++) os << " "; + os << description; + return os.str(); +} + +void SnapshotDataBase::DumpToStderr() { + std::vector lines; + std::vector errors; + std::tie(lines, errors) = SnapshotReadData(storage()).Dump(); + + for (const SnapshotReadData::DumpLine& line : lines) + fprintf(stderr, "%s\n", line.ToString().c_str()); + if (!errors.empty()) { + fprintf(stderr, "Encountered %zu snapshot errors:\n", errors.size()); + for (const Error& error : errors) + fprintf(stderr, "%s\n", error.message.c_str()); + } +} + +void SnapshotDataBase::PrintErrorsAndAbortIfAny() { + if (errors().empty()) return; + + std::vector lines; + std::tie(lines, std::ignore) = SnapshotReadData(storage()).Dump(); + + fprintf(stderr, "Encountered %zu snapshot errors:\n", errors().size()); + for (const Error& error : errors()) { + fprintf(stderr, "%s\nAround:\n", error.message.c_str()); + size_t i; + for (i = 0; i < lines.size(); i++) { + if (lines[i].index >= error.index) break; + } + size_t start_print_range = std::max(i - 5, 0); + size_t end_print_range = std::min(i + 5, lines.size() - 1); + for (size_t j = start_print_range; j <= end_print_range; j++) { + fprintf(stderr, + "%c %s\n", + i == j + 1 ? '*' : ' ', // Mark the line presumed to be at fault. + lines[j].ToString().c_str()); + } + } + fprintf(stderr, + "(`node --dump-snapshot` dumps the full snapshot data contained " + "in a node binary)\n"); + fflush(stderr); + Abort(); +} + +std::pair, + std::vector> SnapshotReadData::Dump() { + SaveStateScope state_scope(this); + state_ = State{}; + + std::vector ret; + + while (state_.current_index < storage_.size() && errors().empty()) { + uint8_t tag; + if (!PeekTag().To(&tag)) { + ReadTag(0); // PeekTag() failing means EOF, this re-generates that error. + break; + } + + DumpLine line = { state_.current_index, state_.entry_stack.size(), "" }; + + switch (tag) { + case kStartEntry: { + std::string str; + if (!StartReadEntry(nullptr).To(&str)) break; + line.description = SPrintF("StartEntry: [%s]", str); + break; + } + case kEndEntry: { + if (EndReadEntry().IsNothing()) break; + line.description = "EndEntry"; + break; + } + case kBool: { + bool value; + if (!ReadBool().To(&value)) break; + line.description = SPrintF("Bool: %s", value); + break; + } + case kInt32: { + int32_t value; + if (!ReadInt32().To(&value)) break; + line.description = SPrintF("Int32: %s", value); + break; + } + case kInt64: { + int64_t value; + if (!ReadInt64().To(&value)) break; + line.description = SPrintF("Int64: %s", value); + break; + } + case kUint32: { + uint32_t value; + if (!ReadUint32().To(&value)) break; + line.description = SPrintF("Uint32: %s", value); + break; + } + case kUint64: { + uint64_t value; + if (!ReadUint64().To(&value)) break; + line.description = SPrintF("Uint64: %s", value); + break; + } + case kString: { + std::string value; + if (!ReadString().To(&value)) break; + if (value.size() > 120) { + line.description = SPrintF("String: '%s' ... '%s'", + EscapeJsonChars(value.substr(0, 90)), + EscapeJsonChars(value.substr(value.size() - 20))); + } else { + line.description = SPrintF("String: '%s'", EscapeJsonChars(value)); + } + break; + } + case kContextIndependentObjectTag: + case kObjectTag: + CHECK(ReadTag(tag)); + // fall-through + case kIndex: { + V8SnapshotIndex index; + if (!ReadIndex().To(&index)) break; + const char* type = tag == kContextIndependentObjectTag ? + "Context-independent object index" : + "Object index"; + if (index == kEmptyIndex) + line.description = SPrintF("%s: (empty)", type); + else + line.description = SPrintF("%s: %s", type, index); + break; + } + default: { + // This will indicate that this is an unknown tag. + line.description = TagName(tag); + add_error(SPrintF("Encountered unknown type tag %d", tag)); + break; + } + } + + if (!line.description.empty()) + ret.emplace_back(std::move(line)); + } + + return std::make_pair(ret, errors()); +} + + +Maybe SnapshotReadData::GetBaseObjectFromV8Object( + Local context, Local obj) { + Environment* env = Environment::GetCurrent(context); + if (!BaseObject::GetConstructorTemplate(env)->HasInstance(obj)) { + add_error("Deserialized object is not a BaseObject"); + return v8::Nothing(); + } + BaseObject* base_obj = BaseObject::FromJSObject(obj); + if (base_obj == nullptr) { + add_error("Deserialized BaseObject is empty"); + return v8::Nothing(); + } + return Just(base_obj); +} + void ExternalReferences::AddPointer(intptr_t ptr) { DCHECK_NE(ptr, kEnd); references_.push_back(ptr); @@ -34,4 +474,28 @@ void ExternalReferences::Register(const char* id, ExternalReferences* self) { const intptr_t ExternalReferences::kEnd = reinterpret_cast(nullptr); +BaseObjectDeserializer::BaseObjectDeserializer( + const std::string& name, Callback callback) { + auto result = map()->insert({name, callback}); + CHECK(result.second); +} + +BaseObject* BaseObjectDeserializer::Deserialize( + const std::string& name, + Environment* env, + SnapshotReadData* snapshot_data) { + Callback callback = (*map())[name]; + if (callback == nullptr) { + snapshot_data->add_error(SPrintF("Unknown BaseObject type %s", name)); + return nullptr; + } + return callback(env, snapshot_data); +} + +std::map* +BaseObjectDeserializer::map() { + static std::map map_; + return &map_; +} + } // namespace node diff --git a/src/snapshot_support.h b/src/snapshot_support.h index ba4dbff0200e99..d4a0d8abb04588 100644 --- a/src/snapshot_support.h +++ b/src/snapshot_support.h @@ -8,6 +8,205 @@ namespace node { +class BaseObject; +template +class BaseObjectPtrImpl; +class Environment; + +// We use V8SnapshotIndex when referring to indices in the sense of the V8 +// snapshot data, to avoid confusion with other kinds of indices. +using V8SnapshotIndex = size_t; + +// This serves as the abstract base class for the snapshot reader and writer +// classes. It includes the current snapshot blob, and a list of errors that +// have occurred while reading/writing the snapshot data. +class SnapshotDataBase { + public: + virtual ~SnapshotDataBase() = default; + + // kEmptyIndex is used when we want to serialize an empty v8::Local<> handle. + static constexpr V8SnapshotIndex kEmptyIndex = static_cast(-1); + + struct Error { + // The byte index into the snapshot where the error occurred. + size_t index; + // An error message. Currently, this includes the index, as well as the + // stack of entries that are used for grouping data in the snapshot. + std::string message; + }; + + // Add a single new error. Index and entry stack are added automatically. + void add_error(const std::string& error); + // Return the list of current errors. + inline const std::vector& errors() const; + + // Return a copy of the underlying byte array. + inline std::vector storage(); + + // Print a human-readable representation of all errors, and abort the process + // if any exist. + void PrintErrorsAndAbortIfAny(); + // Dump the full snapshot, including errors, to stderr. + void DumpToStderr(); + + protected: + explicit inline SnapshotDataBase(std::vector&& storage); + SnapshotDataBase() = default; + + // Checks whether `length` more data is available in the byte array. + // This mostly makes sense for reading data. + bool HasSpace(size_t length) const; + + std::vector storage_; + + struct State { + size_t current_index = 0; + std::vector errors; + std::vector entry_stack; + }; + State state_; + + // Defined here so that they can be used by the v8::Local<> writing + // and reading functions. + static const uint8_t kContextIndependentObjectTag; + static const uint8_t kObjectTag; + + // Used internally to temporarily save the current State of this object. + class SaveStateScope { + public: + explicit SaveStateScope(SnapshotDataBase* snapshot_data); + ~SaveStateScope(); + + private: + SnapshotDataBase* snapshot_data_; + State state_; + }; +}; + +class SnapshotCreateData final : public SnapshotDataBase { + public: + // Start writing an entry. This must be matched by an EndWriteEntry() call. + // The name is mostly used for debugging, but can also be used to figure out + // how to deserialize the data for this entry. + void StartWriteEntry(const char* name); + void EndWriteEntry(); + + // Write data of a given type into the snapshot. When reading, the read + // function for the corresponding must be used. + void WriteBool(bool value); + void WriteInt32(int32_t value); + void WriteInt64(int64_t value); + void WriteUint32(uint32_t value); + void WriteUint64(uint64_t value); + void WriteIndex(V8SnapshotIndex value); + void WriteString(const char* str, size_t length = static_cast(-1)); + void WriteString(const std::string& str); + + // Write a V8 object into the snapshot. Use the ContextIndependent variant + // for writing ObjectTemplate, FunctionTemplate and External objects. + // For other values, their source context must be provided. + template + inline void WriteContextIndependentObject(v8::Local data); + template + inline void WriteObject(v8::Local context, v8::Local data); + template + inline void WriteBaseObjectPtr( + v8::Local context, BaseObjectPtrImpl ptr); + + inline v8::SnapshotCreator* creator(); + inline v8::Isolate* isolate(); + + explicit inline SnapshotCreateData(v8::SnapshotCreator* creator); + + private: + void WriteTag(uint8_t tag); + void WriteRawData(const uint8_t* data, size_t length); + + v8::SnapshotCreator* creator_; +}; + +class SnapshotReadData final : public SnapshotDataBase { + public: + enum EmptyHandleMode { + kAllowEmpty, + kRejectEmpty + }; + + // Start reading an entry. If `expected_name` is a string, it will be matched + // against the string provided to StartWriteEntry(), and if there is a + // mismatch, this function will fail. If no such check is desired, for example + // because the type of the next entry is dynamic, `nullptr` can be passed. + // The entry type as written by StartWriteEntry() is returned. + v8::Maybe StartReadEntry(const char* expected_name); + v8::Maybe EndReadEntry(); + + // Read data from the snapshot. If the read function doesn't match the write + // function at this position in the snapshot, the function will fail and + // return an empty Maybe. + v8::Maybe ReadBool(); + v8::Maybe ReadInt32(); + v8::Maybe ReadInt64(); + v8::Maybe ReadUint32(); + v8::Maybe ReadUint64(); + v8::Maybe ReadIndex(); + v8::Maybe ReadString(); + + // Read V8 objects from the snapshot, matching the writing counterparts of + // these functions. If `mode` is `kRejectEmpty`, reading empty handles is + // regarded as a failure. Pass `kAllowEmpty` if it is expected that the + // corresponding write call may write an empty object. + // These functions do not use `v8::MaybeLocal` in order to be able to + // distinguish between successfully reading an empty handle and unsuccessfully + // reading data. + template + inline v8::Maybe> ReadContextIndependentObject( + EmptyHandleMode mode = kRejectEmpty); + template + inline v8::Maybe> ReadObject( + v8::Local context, EmptyHandleMode mode = kRejectEmpty); + template + inline v8::Maybe> ReadBaseObjectPtr( + v8::Local context, EmptyHandleMode mode = kRejectEmpty); + + // Ensure that the snapshot is finished at this point, and no further data + // is included in the snapshot. + v8::Maybe Finish(); + + struct DumpLine { + size_t index; + size_t depth; + std::string description; + + std::string ToString() const; + }; + // Return a structured description of the snapshot contents. + std::pair, std::vector> Dump(); + + inline v8::Isolate* isolate(); + inline void set_isolate(v8::Isolate*); + + explicit inline SnapshotReadData(std::vector&& storage); + + private: + bool ReadTag(uint8_t tag); + v8::Maybe PeekTag(); + bool ReadRawData(uint8_t* data, size_t length); + + v8::Maybe GetBaseObjectFromV8Object( + v8::Local context, v8::Local obj); + + v8::Isolate* isolate_; +}; + +class Snapshottable { + public: + virtual ~Snapshottable() = 0; + + // Subclasses are expected to override this. The default implementation only + // adds an error to the snapshot data. + virtual void Serialize(SnapshotCreateData* snapshot_data) const; +}; + class ExternalReferences { public: // Create static instances of this class to register a list of external @@ -34,6 +233,25 @@ class ExternalReferences { inline void HandleArgs(T* ptr, Args*... args); }; +class BaseObjectDeserializer { + public: + typedef BaseObject* (*Callback)(Environment*, SnapshotReadData*); + + // Create static instances of this class to register a callback used for + // deserializing specific types of BaseObjects. This instance will be used if + // `name` matches the name passed to `StartWriteEntry()` when creating the + // snapshot. `name` must be unique. + BaseObjectDeserializer(const std::string& name, Callback callback); + + static BaseObject* Deserialize( + const std::string& name, + Environment* env, + SnapshotReadData* snapshot_data); + + private: + static std::map* map(); +}; + } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/test/cctest/test_snapshot_support.cc b/test/cctest/test_snapshot_support.cc new file mode 100644 index 00000000000000..2a66e13412ac5c --- /dev/null +++ b/test/cctest/test_snapshot_support.cc @@ -0,0 +1,78 @@ +#include "snapshot_support-inl.h" +#include "gtest/gtest.h" + +TEST(SnapshotSupportTest, DumpWithoutErrors) { + node::SnapshotCreateData data_w(nullptr); + + data_w.StartWriteEntry("Outer"); + data_w.WriteUint32(10); + data_w.StartWriteEntry("Inner"); + data_w.WriteString("Hello!"); + data_w.EndWriteEntry(); + data_w.EndWriteEntry(); + + node::SnapshotReadData data_r(data_w.storage()); + + std::vector lines; + std::vector errors; + std::tie(lines, errors) = data_r.Dump(); + + EXPECT_TRUE(errors.empty()); + EXPECT_EQ(lines.size(), 6u); + while (lines.size() < 6) lines.emplace_back(); // Avoid OOB test crahes. + EXPECT_EQ(lines[0].index, 0u); + EXPECT_EQ(lines[0].depth, 0u); + EXPECT_EQ(lines[0].description, "StartEntry: [Outer]"); + EXPECT_EQ(lines[1].depth, 1u); + EXPECT_EQ(lines[1].description, "Uint32: 10"); + EXPECT_EQ(lines[2].depth, 1u); + EXPECT_EQ(lines[2].description, "StartEntry: [Inner]"); + EXPECT_EQ(lines[3].depth, 2u); + EXPECT_EQ(lines[3].description, "String: 'Hello!'"); + EXPECT_EQ(lines[4].depth, 2u); + EXPECT_EQ(lines[4].description, "EndEntry"); + EXPECT_EQ(lines[5].depth, 1u); + EXPECT_EQ(lines[5].description, "EndEntry"); +} + +TEST(SnapshotSupportTest, DumpWithErrors) { + node::SnapshotCreateData data_w(nullptr); + + data_w.StartWriteEntry("Outer"); + data_w.WriteUint32(10); + data_w.StartWriteEntry("Inner"); + data_w.WriteString("Hello!"); + data_w.EndWriteEntry(); + data_w.EndWriteEntry(); + + std::vector storage = data_w.storage(); + storage.at(21)++; // Invalidate storage data in some way. + node::SnapshotReadData data_r(std::move(storage)); + + data_r.DumpToStderr(); + std::vector lines; + std::vector errors; + std::tie(lines, errors) = data_r.Dump(); + + // The expectations here may need to be updated if the snapshot format + // changes. + EXPECT_EQ(lines.size(), 5u); + while (lines.size() < 5) lines.emplace_back(); // Avoid OOB test crahes. + EXPECT_EQ(lines[0].index, 0u); + EXPECT_EQ(lines[0].depth, 0u); + EXPECT_EQ(lines[0].description, "StartEntry: [Outer]"); + EXPECT_EQ(lines[1].depth, 1u); + EXPECT_EQ(lines[1].description, "Uint32: 10"); + EXPECT_EQ(lines[2].depth, 1u); + EXPECT_EQ(lines[2].description, "EndEntry"); + EXPECT_EQ(lines[3].depth, 0u); + EXPECT_EQ(lines[3].description, "String: 'Inner'"); + EXPECT_EQ(lines[4].depth, 0u); + EXPECT_EQ(lines[4].description, "String: 'Hello!'"); + + EXPECT_EQ(errors.size(), 1u); + while (errors.size() < 1) errors.emplace_back(); // Avoid OOB test crashes. + EXPECT_EQ(errors[0].message, + "At [54] Attempting to end entry on empty stack, more EndReadEntry() " + "than StartReadEntry() calls"); +} diff --git a/test/parallel/test-code-cache.js b/test/parallel/test-code-cache.js index 3c4488c557d524..fe3d242a86e96e 100644 --- a/test/parallel/test-code-cache.js +++ b/test/parallel/test-code-cache.js @@ -22,7 +22,8 @@ for (const key of canBeRequired) { // The computation has to be delayed until we have done loading modules const { compiledWithoutCache, - compiledWithCache + compiledWithCache, + includedInSnapshot } = getCacheUsage(); const loadedModules = process.moduleLoadList @@ -59,6 +60,7 @@ if (!process.features.cached_builtins) { const wrong = []; for (const key of loadedModules) { + if (includedInSnapshot.has(key)) continue; if (cannotBeRequired.has(key) && !compiledWithoutCache.has(key)) { wrong.push(`"${key}" should've been compiled **without** code cache`); } diff --git a/tools/snapshot/README.md b/tools/snapshot/README.md index 34dc574d56cc30..5ab1df698d26d7 100644 --- a/tools/snapshot/README.md +++ b/tools/snapshot/README.md @@ -23,7 +23,7 @@ into the Node.js executable, `libnode` is first built with these unresolved symbols: - `node::NodeMainInstance::GetEmbeddedSnapshotBlob` -- `node::NodeMainInstance::GetIsolateDataIndexes` +- `node::NodeMainInstance::GetSnapshotData` Then the `node_mksnapshot` executable is built with C++ files in this directory, as well as `src/node_snapshot_stub.cc` which defines the unresolved diff --git a/tools/snapshot/snapshot_builder.cc b/tools/snapshot/snapshot_builder.cc index 391cdf01e09256..5f16ecc3a6fbce 100644 --- a/tools/snapshot/snapshot_builder.cc +++ b/tools/snapshot/snapshot_builder.cc @@ -22,12 +22,13 @@ void WriteVector(std::stringstream* ss, const T* vec, size_t size) { } std::string FormatBlob(v8::StartupData* blob, - const std::vector& isolate_data_indexes) { + SnapshotCreateData* snapshot_data) { std::stringstream ss; ss << R"(#include #include "node_main_instance.h" -#include "v8.h" +#include "env.h" +#include "snapshot_support-inl.h" // This file is generated by tools/snapshot. Do not edit. @@ -47,13 +48,17 @@ static v8::StartupData blob = { blob_data, blob_size }; return &blob; } -static const std::vector isolate_data_indexes { +static SnapshotReadData snapshot_data { + { )"; - WriteVector(&ss, isolate_data_indexes.data(), isolate_data_indexes.size()); - ss << R"(}; + std::vector raw_data = snapshot_data->storage(); + WriteVector(&ss, raw_data.data(), raw_data.size()); + ss << R"( + } +}; -const std::vector* NodeMainInstance::GetIsolateDataIndexes() { - return &isolate_data_indexes; +SnapshotReadData* NodeMainInstance::GetSnapshotData() { + return &snapshot_data; } } // namespace node )"; @@ -69,13 +74,15 @@ std::string SnapshotBuilder::Generate( uv_default_loop()); std::unique_ptr main_instance; std::string result; + int exit_code = 0; std::vector external_references = ExternalReferences::get_list(); external_references.push_back(ExternalReferences::kEnd); { - std::vector isolate_data_indexes; SnapshotCreator creator(isolate, external_references.data()); + SnapshotCreateData snapshot_data(&creator); + DeleteFnPtr env; { main_instance = NodeMainInstance::Create(isolate, @@ -85,20 +92,27 @@ std::string SnapshotBuilder::Generate( 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)); + env = main_instance->CreateMainEnvironment(&exit_code); + env->isolate_data()->Serialize(&snapshot_data); + env->Serialize(&snapshot_data); + snapshot_data.PrintErrorsAndAbortIfAny(); + size_t index = creator.AddContext(env->context(), + { BaseObject::SerializeInternalFields, &snapshot_data }); CHECK_EQ(index, NodeMainInstance::kNodeContextIndex); + CHECK_EQ(exit_code, 0); } // Must be out of HandleScope StartupData blob = creator.CreateBlob(SnapshotCreator::FunctionCodeHandling::kClear); + snapshot_data.PrintErrorsAndAbortIfAny(); CHECK(blob.CanBeRehashed()); // Must be done while the snapshot creator isolate is entered i.e. the // creator is still alive. + env.reset(); main_instance->Dispose(); - result = FormatBlob(&blob, isolate_data_indexes); + result = FormatBlob(&blob, &snapshot_data); delete[] blob.data; }