From c0f1430ed00ffaadaa10431b74ee238bcd6285c8 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 16 Aug 2022 23:22:32 +0800 Subject: [PATCH 1/4] vm: make ContextifyContext template context-independent Instead of creating an object template for every ContextifyContext, we now create one object template that can be reused by all contexts. The native pointer can be obtained through an embdder pointer field in the creation context of the receiver in the interceptors, because the interceptors are only meant to be invoked on the global object of the contextified contexts. This makes the ContextifyContext template context-independent and therefore snapshotable. --- src/env.cc | 13 +- src/env.h | 3 +- src/node_context_data.h | 7 +- src/node_contextify.cc | 226 +++++++++++++++++++--------------- src/node_contextify.h | 21 ++-- src/node_external_reference.h | 7 +- 6 files changed, 164 insertions(+), 113 deletions(-) diff --git a/src/env.cc b/src/env.cc index c0c9f17cdc71aa..de09c4dec7d69e 100644 --- a/src/env.cc +++ b/src/env.cc @@ -6,6 +6,7 @@ #include "memory_tracker-inl.h" #include "node_buffer.h" #include "node_context_data.h" +#include "node_contextify.h" #include "node_errors.h" #include "node_internals.h" #include "node_options-inl.h" @@ -444,6 +445,8 @@ void IsolateData::CreateProperties() { #undef V // TODO(legendecas): eagerly create per isolate templates. + set_contextify_global_template( + contextify::ContextifyContext::CreateGlobalTemplate(isolate_)); } IsolateData::IsolateData(Isolate* isolate, @@ -512,13 +515,19 @@ void TrackingTraceStateObserver::UpdateTraceCategoryState() { void Environment::AssignToContext(Local context, const ContextInfo& info) { - ContextEmbedderTag::TagNodeContext(context); context->SetAlignedPointerInEmbedderData(ContextEmbedderIndex::kEnvironment, this); // Used to retrieve bindings context->SetAlignedPointerInEmbedderData( ContextEmbedderIndex::kBindingListIndex, &(this->bindings_)); + // ContextifyContexts will update this to a pointer to the native object. + context->SetAlignedPointerInEmbedderData( + ContextEmbedderIndex::kContextifyContext, nullptr); + + // This must not be done before other context fields are initialized. + ContextEmbedderTag::TagNodeContext(context); + #if HAVE_INSPECTOR inspector_agent()->ContextCreated(context, info); #endif // HAVE_INSPECTOR @@ -769,6 +778,8 @@ void Environment::InitializeMainContext(Local context, const EnvSerializeInfo* env_info) { context_.Reset(context->GetIsolate(), context); AssignToContext(context, ContextInfo("")); + context->SetAlignedPointerInEmbedderData( + ContextEmbedderIndex::kContextifyContext, nullptr); if (env_info != nullptr) { DeserializeProperties(env_info); } else { diff --git a/src/env.h b/src/env.h index 42c5ef888b2f49..ef5850ee3b3aef 100644 --- a/src/env.h +++ b/src/env.h @@ -349,6 +349,7 @@ class NoArrayBufferZeroFillScope { V(nistcurve_string, "nistCurve") \ V(node_string, "node") \ V(nsname_string, "nsname") \ + V(object_string, "Object") \ V(ocsp_request_string, "OCSPRequest") \ V(oncertcb_string, "oncertcb") \ V(onchange_string, "onchange") \ @@ -477,6 +478,7 @@ class NoArrayBufferZeroFillScope { V(binding_data_ctor_template, v8::FunctionTemplate) \ V(blob_constructor_template, v8::FunctionTemplate) \ V(blocklist_constructor_template, v8::FunctionTemplate) \ + V(contextify_global_template, v8::ObjectTemplate) \ V(compiled_fn_entry_template, v8::ObjectTemplate) \ V(dir_instance_template, v8::ObjectTemplate) \ V(fd_constructor_template, v8::ObjectTemplate) \ @@ -560,7 +562,6 @@ class NoArrayBufferZeroFillScope { V(primordials_safe_weak_set_prototype_object, v8::Object) \ V(promise_hook_handler, v8::Function) \ V(promise_reject_callback, v8::Function) \ - V(script_data_constructor_function, v8::Function) \ V(snapshot_serialize_callback, v8::Function) \ V(snapshot_deserialize_callback, v8::Function) \ V(snapshot_deserialize_main, v8::Function) \ diff --git a/src/node_context_data.h b/src/node_context_data.h index 632b648a0a1ef7..3fe425bdf4cff6 100644 --- a/src/node_context_data.h +++ b/src/node_context_data.h @@ -32,11 +32,15 @@ namespace node { #define NODE_CONTEXT_ALLOW_CODE_GENERATION_FROM_STRINGS_INDEX 36 #endif +#ifndef NODE_CONTEXT_CONTEXTIFY_CONTEXT_INDEX +#define NODE_CONTEXT_CONTEXTIFY_CONTEXT_INDEX 37 +#endif + // NODE_CONTEXT_TAG must be greater than any embedder indexes so that a single // check on the number of embedder data fields can assure the presence of all // embedder indexes. #ifndef NODE_CONTEXT_TAG -#define NODE_CONTEXT_TAG 37 +#define NODE_CONTEXT_TAG 38 #endif enum ContextEmbedderIndex { @@ -46,6 +50,7 @@ enum ContextEmbedderIndex { kBindingListIndex = NODE_BINDING_LIST_INDEX, kAllowCodeGenerationFromStrings = NODE_CONTEXT_ALLOW_CODE_GENERATION_FROM_STRINGS_INDEX, + kContextifyContext = NODE_CONTEXT_CONTEXTIFY_CONTEXT_INDEX, kContextTag = NODE_CONTEXT_TAG, }; diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 49a049e24abf2a..e9930972865e44 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -113,12 +113,26 @@ ContextifyContext::ContextifyContext( const ContextOptions& options) : env_(env), microtask_queue_wrap_(options.microtask_queue_wrap) { - MaybeLocal v8_context = CreateV8Context(env, sandbox_obj, options); + Local object_template = env->contextify_global_template(); + if (object_template.IsEmpty()) { + object_template = CreateGlobalTemplate(env->isolate()); + env->set_contextify_global_template(object_template); + } + + MicrotaskQueue* queue = + microtask_queue() + ? microtask_queue().get() + : env->isolate()->GetCurrentContext()->GetMicrotaskQueue(); - // Allocation failure, maximum call stack size reached, termination, etc. - if (v8_context.IsEmpty()) return; + Local v8_context; + if (!(CreateV8Context(env->isolate(), object_template, queue) + .ToLocal(&v8_context)) || + !InitializeContext(v8_context, env, sandbox_obj, options)) { + // Allocation failure, maximum call stack size reached, termination, etc. + return; + } - context_.Reset(env->isolate(), v8_context.ToLocalChecked()); + context_.Reset(env->isolate(), v8_context); context_.SetWeak(this, WeakCallback, WeakCallbackType::kParameter); env->AddCleanupHook(CleanupHook, this); } @@ -140,41 +154,13 @@ void ContextifyContext::CleanupHook(void* arg) { delete self; } - -// This is an object that just keeps an internal pointer to this -// ContextifyContext. It's passed to the NamedPropertyHandler. If we -// pass the main JavaScript context object we're embedded in, then the -// NamedPropertyHandler will store a reference to it forever and keep it -// from getting gc'd. -MaybeLocal ContextifyContext::CreateDataWrapper(Environment* env) { - Local wrapper; - if (!env->script_data_constructor_function() - ->NewInstance(env->context()) - .ToLocal(&wrapper)) { - return MaybeLocal(); - } - - wrapper->SetAlignedPointerInInternalField(ContextifyContext::kSlot, this); - return wrapper; -} - -MaybeLocal ContextifyContext::CreateV8Context( - Environment* env, - Local sandbox_obj, - const ContextOptions& options) { - EscapableHandleScope scope(env->isolate()); - Local function_template = - FunctionTemplate::New(env->isolate()); - - function_template->SetClassName(sandbox_obj->GetConstructorName()); +Local ContextifyContext::CreateGlobalTemplate( + Isolate* isolate) { + Local function_template = FunctionTemplate::New(isolate); Local object_template = function_template->InstanceTemplate(); - Local data_wrapper; - if (!CreateDataWrapper(env).ToLocal(&data_wrapper)) - return MaybeLocal(); - NamedPropertyHandlerConfiguration config( PropertyGetterCallback, PropertySetterCallback, @@ -182,7 +168,7 @@ MaybeLocal ContextifyContext::CreateV8Context( PropertyDeleterCallback, PropertyEnumeratorCallback, PropertyDefinerCallback, - data_wrapper, + {}, PropertyHandlerFlags::kHasNoSideEffect); IndexedPropertyHandlerConfiguration indexed_config( @@ -192,33 +178,48 @@ MaybeLocal ContextifyContext::CreateV8Context( IndexedPropertyDeleterCallback, PropertyEnumeratorCallback, IndexedPropertyDefinerCallback, - data_wrapper, + {}, PropertyHandlerFlags::kHasNoSideEffect); object_template->SetHandler(config); object_template->SetHandler(indexed_config); - Local ctx = Context::New( - env->isolate(), - nullptr, // extensions - object_template, - {}, // global object - {}, // deserialization callback - microtask_queue() ? - microtask_queue().get() : - env->isolate()->GetCurrentContext()->GetMicrotaskQueue()); + + return object_template; +} + +MaybeLocal ContextifyContext::CreateV8Context( + Isolate* isolate, + Local object_template, + MicrotaskQueue* queue) { + EscapableHandleScope scope(isolate); + + Local ctx = Context::New(isolate, + nullptr, // extensions + object_template, + {}, // global object + {}, // deserialization callback + queue); if (ctx.IsEmpty()) return MaybeLocal(); - // Only partially initialize the context - the primordials are left out - // and only initialized when necessary. - if (InitializeContextRuntime(ctx).IsNothing()) { - return MaybeLocal(); - } - if (ctx.IsEmpty()) { - return MaybeLocal(); + return scope.Escape(ctx); +} + +bool ContextifyContext::InitializeContext(Local ctx, + Environment* env, + Local sandbox_obj, + const ContextOptions& options) { + HandleScope scope(env->isolate()); + + // This only initializes part of the context. The primordials are + // only initilaized when needed because even deserializing them slows + // things down significantly and they are only needed in rare occasions + // in the vm contexts. + if (InitializeContextRuntime(ctx).IsNothing()) { + return false; } - Local context = env->context(); - ctx->SetSecurityToken(context->GetSecurityToken()); + Local main_context = env->context(); + ctx->SetSecurityToken(main_context->GetSecurityToken()); // We need to tie the lifetime of the sandbox object with the lifetime of // newly created context. We do this by making them hold references to each @@ -227,11 +228,9 @@ MaybeLocal ContextifyContext::CreateV8Context( // directly in an Object, we instead hold onto the new context's global // object instead (which then has a reference to the context). ctx->SetEmbedderData(ContextEmbedderIndex::kSandboxObject, sandbox_obj); - sandbox_obj->SetPrivate(context, - env->contextify_global_private_symbol(), - ctx->Global()); + sandbox_obj->SetPrivate( + main_context, env->contextify_global_private_symbol(), ctx->Global()); - Utf8Value name_val(env->isolate(), options.name); // Delegate the code generation validation to // node::ModifyCodeGenerationFromStrings. ctx->AllowCodeGenerationFromStrings(false); @@ -240,30 +239,39 @@ MaybeLocal ContextifyContext::CreateV8Context( ctx->SetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration, options.allow_code_gen_wasm); + Utf8Value name_val(env->isolate(), options.name); ContextInfo info(*name_val); - if (!options.origin.IsEmpty()) { Utf8Value origin_val(env->isolate(), options.origin); info.origin = *origin_val; } + { + Context::Scope context_scope(ctx); + Local ctor_name = sandbox_obj->GetConstructorName(); + if (!ctor_name->Equals(ctx, env->object_string()).FromMaybe(false) && + ctx->Global() + ->DefineOwnProperty( + ctx, + v8::Symbol::GetToStringTag(env->isolate()), + ctor_name, + static_cast(v8::DontEnum)) + .IsNothing()) { + return false; + } + } + env->AssignToContext(ctx, info); - return scope.Escape(ctx); + // This should only be done after the initial initializations of the context + // global object is finished. + ctx->SetAlignedPointerInEmbedderData(ContextEmbedderIndex::kContextifyContext, + this); + return true; } - void ContextifyContext::Init(Environment* env, Local target) { - Isolate* isolate = env->isolate(); Local context = env->context(); - - Local function_template = - NewFunctionTemplate(isolate, nullptr); - function_template->InstanceTemplate()->SetInternalFieldCount( - ContextifyContext::kInternalFieldCount); - env->set_script_data_constructor_function( - function_template->GetFunction(env->context()).ToLocalChecked()); - SetMethod(context, target, "makeContext", MakeContext); SetMethod(context, target, "isContext", IsContext); SetMethod(context, target, "compileFunction", CompileFunction); @@ -274,6 +282,17 @@ void ContextifyContext::RegisterExternalReferences( registry->Register(MakeContext); registry->Register(IsContext); registry->Register(CompileFunction); + registry->Register(PropertyGetterCallback); + registry->Register(PropertySetterCallback); + registry->Register(PropertyDescriptorCallback); + registry->Register(PropertyDeleterCallback); + registry->Register(PropertyEnumeratorCallback); + registry->Register(PropertyDefinerCallback); + registry->Register(IndexedPropertyGetterCallback); + registry->Register(IndexedPropertySetterCallback); + registry->Register(IndexedPropertyDescriptorCallback); + registry->Register(IndexedPropertyDeleterCallback); + registry->Register(IndexedPropertyDefinerCallback); } // makeContext(sandbox, name, origin, strings, wasm); @@ -323,8 +342,8 @@ void ContextifyContext::MakeContext(const FunctionCallbackInfo& args) { return; } - if (context_ptr->context().IsEmpty()) - return; + Local new_context = context_ptr->context(); + if (new_context.IsEmpty()) return; sandbox->SetPrivate( env->context(), @@ -371,10 +390,20 @@ ContextifyContext* ContextifyContext::ContextFromContextifiedSandbox( // static template ContextifyContext* ContextifyContext::Get(const PropertyCallbackInfo& args) { - Local data = args.Data(); + Local context; + if (!args.This()->GetCreationContext().ToLocal(&context)) { + return nullptr; + } + if (!ContextEmbedderTag::IsNodeContext(context)) { + return nullptr; + } return static_cast( - data.As()->GetAlignedPointerFromInternalField( - ContextifyContext::kSlot)); + context->GetAlignedPointerFromEmbedderData( + ContextEmbedderIndex::kContextifyContext)); +} + +bool ContextifyContext::IsStillInitializing(const ContextifyContext* ctx) { + return ctx == nullptr || ctx->context_.IsEmpty(); } // static @@ -384,8 +413,7 @@ void ContextifyContext::PropertyGetterCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; Local context = ctx->context(); Local sandbox = ctx->sandbox(); @@ -413,8 +441,7 @@ void ContextifyContext::PropertySetterCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; Local context = ctx->context(); PropertyAttribute attributes = PropertyAttribute::None; @@ -466,8 +493,7 @@ void ContextifyContext::PropertyDescriptorCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; Local context = ctx->context(); @@ -489,8 +515,7 @@ void ContextifyContext::PropertyDefinerCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; Local context = ctx->context(); Isolate* isolate = context->GetIsolate(); @@ -550,8 +575,7 @@ void ContextifyContext::PropertyDeleterCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; Maybe success = ctx->sandbox()->Delete(ctx->context(), property); @@ -569,8 +593,7 @@ void ContextifyContext::PropertyEnumeratorCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; Local properties; @@ -587,8 +610,7 @@ void ContextifyContext::IndexedPropertyGetterCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; ContextifyContext::PropertyGetterCallback( Uint32ToName(ctx->context(), index), args); @@ -602,8 +624,7 @@ void ContextifyContext::IndexedPropertySetterCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; ContextifyContext::PropertySetterCallback( Uint32ToName(ctx->context(), index), value, args); @@ -616,8 +637,7 @@ void ContextifyContext::IndexedPropertyDescriptorCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; ContextifyContext::PropertyDescriptorCallback( Uint32ToName(ctx->context(), index), args); @@ -631,8 +651,7 @@ void ContextifyContext::IndexedPropertyDefinerCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; ContextifyContext::PropertyDefinerCallback( Uint32ToName(ctx->context(), index), desc, args); @@ -645,8 +664,7 @@ void ContextifyContext::IndexedPropertyDeleterCallback( ContextifyContext* ctx = ContextifyContext::Get(args); // Still initializing - if (ctx->context_.IsEmpty()) - return; + if (IsStillInitializing(ctx)) return; Maybe success = ctx->sandbox()->Delete(ctx->context(), index); @@ -891,8 +909,8 @@ void ContextifyScript::RunInContext(const FunctionCallbackInfo& args) { bool break_on_first_line = args[4]->IsTrue(); // Do the eval within the context - Context::Scope context_scope(context); - EvalMachine(env, + EvalMachine(context, + env, timeout, display_errors, break_on_sigint, @@ -901,13 +919,16 @@ void ContextifyScript::RunInContext(const FunctionCallbackInfo& args) { args); } -bool ContextifyScript::EvalMachine(Environment* env, +bool ContextifyScript::EvalMachine(Local context, + Environment* env, const int64_t timeout, const bool display_errors, const bool break_on_sigint, const bool break_on_first_line, std::shared_ptr mtask_queue, const FunctionCallbackInfo& args) { + Context::Scope context_scope(context); + if (!env->can_call_into_js()) return false; if (!ContextifyScript::InstanceOf(env, args.Holder())) { @@ -916,6 +937,7 @@ bool ContextifyScript::EvalMachine(Environment* env, "Script methods can only be called on script instances."); return false; } + TryCatchScope try_catch(env); Isolate::SafeForTerminationScope safe_for_termination(env->isolate()); ContextifyScript* wrapped_script; @@ -934,7 +956,7 @@ bool ContextifyScript::EvalMachine(Environment* env, bool timed_out = false; bool received_signal = false; auto run = [&]() { - MaybeLocal result = script->Run(env->context()); + MaybeLocal result = script->Run(context); if (!result.IsEmpty() && mtask_queue) mtask_queue->PerformCheckpoint(env->isolate()); return result; diff --git a/src/node_contextify.h b/src/node_contextify.h index c9b5fc0f62dcfc..088b5c5a56192c 100644 --- a/src/node_contextify.h +++ b/src/node_contextify.h @@ -43,17 +43,20 @@ struct ContextOptions { class ContextifyContext { public: - enum InternalFields { kSlot, kInternalFieldCount }; ContextifyContext(Environment* env, v8::Local sandbox_obj, const ContextOptions& options); ~ContextifyContext(); static void CleanupHook(void* arg); - v8::MaybeLocal CreateDataWrapper(Environment* env); - v8::MaybeLocal CreateV8Context(Environment* env, - v8::Local sandbox_obj, - const ContextOptions& options); + static v8::MaybeLocal CreateV8Context( + v8::Isolate* isolate, + v8::Local object_template, + v8::MicrotaskQueue* queue); + bool InitializeContext(v8::Local ctx, + Environment* env, + v8::Local sandbox_obj, + const ContextOptions& options); static void Init(Environment* env, v8::Local target); static void RegisterExternalReferences(ExternalReferenceRegistry* registry); @@ -83,11 +86,14 @@ class ContextifyContext { return microtask_queue_wrap_->microtask_queue(); } - template static ContextifyContext* Get(const v8::PropertyCallbackInfo& args); + static v8::Local CreateGlobalTemplate( + v8::Isolate* isolate); + private: + static bool IsStillInitializing(const ContextifyContext* ctx); static void MakeContext(const v8::FunctionCallbackInfo& args); static void IsContext(const v8::FunctionCallbackInfo& args); static void CompileFunction( @@ -150,7 +156,8 @@ class ContextifyScript : public BaseObject { static bool InstanceOf(Environment* env, const v8::Local& args); static void CreateCachedData(const v8::FunctionCallbackInfo& args); static void RunInContext(const v8::FunctionCallbackInfo& args); - static bool EvalMachine(Environment* env, + static bool EvalMachine(v8::Local context, + Environment* env, const int64_t timeout, const bool display_errors, const bool break_on_sigint, diff --git a/src/node_external_reference.h b/src/node_external_reference.h index b7d7a63a8a88a6..a77c1ab14ee0de 100644 --- a/src/node_external_reference.h +++ b/src/node_external_reference.h @@ -30,7 +30,12 @@ class ExternalReferenceRegistry { V(v8::GenericNamedPropertyDeleterCallback) \ V(v8::GenericNamedPropertyEnumeratorCallback) \ V(v8::GenericNamedPropertyQueryCallback) \ - V(v8::GenericNamedPropertySetterCallback) + V(v8::GenericNamedPropertySetterCallback) \ + V(v8::IndexedPropertySetterCallback) \ + V(v8::IndexedPropertyDefinerCallback) \ + V(v8::IndexedPropertyDeleterCallback) \ + V(v8::IndexedPropertyQueryCallback) \ + V(v8::IndexedPropertyDescriptorCallback) #define V(ExternalReferenceType) \ void Register(ExternalReferenceType addr) { RegisterT(addr); } From f60d687fcb22a0accce438dbaea79a8fa546e6fb Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 16 Aug 2022 23:44:01 +0800 Subject: [PATCH 2/4] vm: include vm context in the embedded snapshot Include a minimally initialized contextify context in the embedded snapshot. This paves the way for user-land vm context snapshots. --- src/env.h | 3 ++- src/node_contextify.cc | 33 ++++++++++++++++++++++++--------- src/node_contextify.h | 1 + src/node_snapshotable.cc | 19 ++++++++++++++++++- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/env.h b/src/env.h index ef5850ee3b3aef..6f148dbd041b83 100644 --- a/src/env.h +++ b/src/env.h @@ -1002,7 +1002,8 @@ struct SnapshotData { enum class DataOwnership { kOwned, kNotOwned }; static const uint32_t kMagic = 0x143da19; - static const SnapshotIndex kNodeBaseContextIndex = 0; + static const SnapshotIndex kNodeVMContextIndex = 0; + static const SnapshotIndex kNodeBaseContextIndex = kNodeVMContextIndex + 1; static const SnapshotIndex kNodeMainContextIndex = kNodeBaseContextIndex + 1; DataOwnership data_ownership = DataOwnership::kOwned; diff --git a/src/node_contextify.cc b/src/node_contextify.cc index e9930972865e44..9fd99f3e396fb0 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -28,6 +28,7 @@ #include "node_errors.h" #include "node_external_reference.h" #include "node_internals.h" +#include "node_snapshot_builder.h" #include "node_watchdog.h" #include "util-inl.h" @@ -118,6 +119,9 @@ ContextifyContext::ContextifyContext( object_template = CreateGlobalTemplate(env->isolate()); env->set_contextify_global_template(object_template); } + bool use_node_snapshot = per_process::cli_options->node_snapshot; + const SnapshotData* snapshot_data = + use_node_snapshot ? SnapshotBuilder::GetEmbeddedSnapshotData() : nullptr; MicrotaskQueue* queue = microtask_queue() @@ -125,7 +129,7 @@ ContextifyContext::ContextifyContext( : env->isolate()->GetCurrentContext()->GetMicrotaskQueue(); Local v8_context; - if (!(CreateV8Context(env->isolate(), object_template, queue) + if (!(CreateV8Context(env->isolate(), object_template, snapshot_data, queue) .ToLocal(&v8_context)) || !InitializeContext(v8_context, env, sandbox_obj, options)) { // Allocation failure, maximum call stack size reached, termination, etc. @@ -190,17 +194,28 @@ Local ContextifyContext::CreateGlobalTemplate( MaybeLocal ContextifyContext::CreateV8Context( Isolate* isolate, Local object_template, + const SnapshotData* snapshot_data, MicrotaskQueue* queue) { EscapableHandleScope scope(isolate); - Local ctx = Context::New(isolate, - nullptr, // extensions - object_template, - {}, // global object - {}, // deserialization callback - queue); - if (ctx.IsEmpty()) return MaybeLocal(); - + Local ctx; + if (snapshot_data == nullptr) { + ctx = Context::New(isolate, + nullptr, // extensions + object_template, + {}, // global object + {}, // deserialization callback + queue); + if (ctx.IsEmpty()) return MaybeLocal(); + } else if (!Context::FromSnapshot(isolate, + SnapshotData::kNodeVMContextIndex, + {}, // deserialization callback + nullptr, // extensions + {}, // global object + queue) + .ToLocal(&ctx)) { + return MaybeLocal(); + } return scope.Escape(ctx); } diff --git a/src/node_contextify.h b/src/node_contextify.h index 088b5c5a56192c..a424c56d7e8f26 100644 --- a/src/node_contextify.h +++ b/src/node_contextify.h @@ -52,6 +52,7 @@ class ContextifyContext { static v8::MaybeLocal CreateV8Context( v8::Isolate* isolate, v8::Local object_template, + const SnapshotData* snapshot_data, v8::MicrotaskQueue* queue); bool InitializeContext(v8::Local ctx, Environment* env, diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 4399b7a3cc132d..9aac5dd7f31d0c 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -7,6 +7,7 @@ #include "env-inl.h" #include "node_blob.h" #include "node_builtins.h" +#include "node_contextify.h" #include "node_errors.h" #include "node_external_reference.h" #include "node_file.h" @@ -32,6 +33,7 @@ using v8::HandleScope; using v8::Isolate; using v8::Local; using v8::Object; +using v8::ObjectTemplate; using v8::ScriptCompiler; using v8::ScriptOrigin; using v8::SnapshotCreator; @@ -1031,6 +1033,19 @@ int SnapshotBuilder::Generate(SnapshotData* out, // The default context with only things created by V8. Local default_context = Context::New(isolate); + // The context used by the vm module. + Local vm_context; + { + Local global_template = + main_instance->isolate_data()->contextify_global_template(); + CHECK(!global_template.IsEmpty()); + if (!contextify::ContextifyContext::CreateV8Context( + isolate, global_template, nullptr, nullptr) + .ToLocal(&vm_context)) { + return SNAPSHOT_ERROR; + } + } + // The Node.js-specific context with primodials, can be used by workers // TODO(joyeecheung): investigate if this can be used by vm contexts // without breaking compatibility. @@ -1112,7 +1127,9 @@ int SnapshotBuilder::Generate(SnapshotData* out, // blob is created. So initialize all the contexts before adding them. // TODO(joyeecheung): figure out how to remove this restriction. creator.SetDefaultContext(default_context); - size_t index = creator.AddContext(base_context); + size_t index = creator.AddContext(vm_context); + CHECK_EQ(index, SnapshotData::kNodeVMContextIndex); + index = creator.AddContext(base_context); CHECK_EQ(index, SnapshotData::kNodeBaseContextIndex); index = creator.AddContext(main_context, {SerializeNodeContextInternalFields, env}); From c507be8d75f27e7dbea8740d079691c3038cf00e Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 17 Aug 2022 15:48:46 +0800 Subject: [PATCH 3/4] vm: avoid unnecessary property getter interceptor calls Access to the global object from within a vm context is intercepted so it's slow, therefore we should try to avoid unnecessary access to it during the initialization of vm contexts. - Remove the Atomics.wake deletion as V8 now does not install it anymore. - Move the Intl.v8BreakIterator deletion into the snapshot. - Do not query the Object prototype if --disable-proto is not set. This should speed up the creation of vm contexts by about ~12%. --- src/api/environment.cc | 79 ++++++++++++++++++------------------------ src/node_contextify.cc | 16 +++++---- src/node_internals.h | 2 ++ 3 files changed, 44 insertions(+), 53 deletions(-) diff --git a/src/api/environment.cc b/src/api/environment.cc index bdf67521cbf80c..e35da6f3b39d61 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -555,50 +555,8 @@ Maybe InitializeContextRuntime(Local context) { Isolate* isolate = context->GetIsolate(); HandleScope handle_scope(isolate); - // Delete `Intl.v8BreakIterator` - // https://github.com/nodejs/node/issues/14909 - { - Local intl_string = - FIXED_ONE_BYTE_STRING(isolate, "Intl"); - Local break_iter_string = - FIXED_ONE_BYTE_STRING(isolate, "v8BreakIterator"); - - Local intl_v; - if (!context->Global() - ->Get(context, intl_string) - .ToLocal(&intl_v)) { - return Nothing(); - } - - if (intl_v->IsObject() && - intl_v.As() - ->Delete(context, break_iter_string) - .IsNothing()) { - return Nothing(); - } - } - - // Delete `Atomics.wake` - // https://github.com/nodejs/node/issues/21219 - { - Local atomics_string = - FIXED_ONE_BYTE_STRING(isolate, "Atomics"); - Local wake_string = - FIXED_ONE_BYTE_STRING(isolate, "wake"); - - Local atomics_v; - if (!context->Global() - ->Get(context, atomics_string) - .ToLocal(&atomics_v)) { - return Nothing(); - } - - if (atomics_v->IsObject() && - atomics_v.As() - ->Delete(context, wake_string) - .IsNothing()) { - return Nothing(); - } + if (per_process::cli_options->disable_proto == "") { + return Just(true); } // Remove __proto__ @@ -660,7 +618,32 @@ Maybe InitializeContextRuntime(Local context) { return Just(true); } -Maybe InitializeContextForSnapshot(Local context) { +Maybe InitializeBaseContextForSnapshot(Local context) { + Isolate* isolate = context->GetIsolate(); + HandleScope handle_scope(isolate); + + // Delete `Intl.v8BreakIterator` + // https://github.com/nodejs/node/issues/14909 + { + Context::Scope context_scope(context); + Local intl_string = FIXED_ONE_BYTE_STRING(isolate, "Intl"); + Local break_iter_string = + FIXED_ONE_BYTE_STRING(isolate, "v8BreakIterator"); + + Local intl_v; + if (!context->Global()->Get(context, intl_string).ToLocal(&intl_v)) { + return Nothing(); + } + + if (intl_v->IsObject() && + intl_v.As()->Delete(context, break_iter_string).IsNothing()) { + return Nothing(); + } + } + return Just(true); +} + +Maybe InitializeMainContextForSnapshot(Local context) { Isolate* isolate = context->GetIsolate(); HandleScope handle_scope(isolate); @@ -670,6 +653,9 @@ Maybe InitializeContextForSnapshot(Local context) { context->SetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration, True(isolate)); + if (InitializeBaseContextForSnapshot(context).IsNothing()) { + return Nothing(); + } return InitializePrimordials(context); } @@ -716,8 +702,9 @@ Maybe InitializePrimordials(Local context) { return Just(true); } +// This initializes the main context (i.e. vm contexts are not included). Maybe InitializeContext(Local context) { - if (InitializeContextForSnapshot(context).IsNothing()) { + if (InitializeMainContextForSnapshot(context).IsNothing()) { return Nothing(); } diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 9fd99f3e396fb0..72856f123b0cdb 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -206,14 +206,16 @@ MaybeLocal ContextifyContext::CreateV8Context( {}, // global object {}, // deserialization callback queue); - if (ctx.IsEmpty()) return MaybeLocal(); + if (ctx.IsEmpty() || InitializeBaseContextForSnapshot(ctx).IsNothing()) { + return MaybeLocal(); + } } else if (!Context::FromSnapshot(isolate, - SnapshotData::kNodeVMContextIndex, - {}, // deserialization callback - nullptr, // extensions - {}, // global object - queue) - .ToLocal(&ctx)) { + SnapshotData::kNodeVMContextIndex, + {}, // deserialization callback + nullptr, // extensions + {}, // global object + queue) + .ToLocal(&ctx)) { return MaybeLocal(); } return scope.Escape(ctx); diff --git a/src/node_internals.h b/src/node_internals.h index 1876452035caf0..de81fbee07c30f 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -92,6 +92,8 @@ void SignalExit(int signal, siginfo_t* info, void* ucontext); std::string GetProcessTitle(const char* default_title); std::string GetHumanReadableProcessName(); +v8::Maybe InitializeBaseContextForSnapshot( + v8::Local context); v8::Maybe InitializeContextRuntime(v8::Local context); v8::Maybe InitializePrimordials(v8::Local context); From 79578c52901b17ece7efaf8f1ada8e415e55b879 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 19 Aug 2022 16:13:37 +0800 Subject: [PATCH 4/4] fixup! vm: make ContextifyContext template context-independent --- src/env.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/env.cc b/src/env.cc index de09c4dec7d69e..9bc9346e653fe1 100644 --- a/src/env.cc +++ b/src/env.cc @@ -778,8 +778,6 @@ void Environment::InitializeMainContext(Local context, const EnvSerializeInfo* env_info) { context_.Reset(context->GetIsolate(), context); AssignToContext(context, ContextInfo("")); - context->SetAlignedPointerInEmbedderData( - ContextEmbedderIndex::kContextifyContext, nullptr); if (env_info != nullptr) { DeserializeProperties(env_info); } else {