From e6d8ae05b2cba68594d6ffd7cce721f19b1722bd Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Sat, 10 Jul 2021 01:54:09 +0800 Subject: [PATCH] process: support hrtime in the snapshot Put the hrtime backing store in the process methods binding data so that it can be integrated into the snapshot builder. For now we simply discard the contents of the hrtime buffer during serialization and create new buffers upon deserialization because the contents are only useful in a synchronous call. This also moves the helper function for creating V8 FastAPI methods into `Environment::SetFastMethod()` for code reuse. The v8::CFunction need to be created before the Environment is created so that we have the CTypeInfo address available for external reference registration. PR-URL: https://github.com/nodejs/node/pull/40649 Refs: https://github.com/nodejs/node/issues/35711 Refs: https://github.com/nodejs/node/issues/37476 Reviewed-By: James M Snell --- lib/internal/bootstrap/node.js | 3 + lib/internal/bootstrap/pre_execution.js | 11 +- lib/internal/process/per_thread.js | 67 +++---- src/env-inl.h | 45 ++++- src/env.h | 20 ++- src/node_external_reference.h | 5 + src/node_process.h | 54 ++++++ src/node_process_methods.cc | 223 +++++++++++------------- src/node_snapshotable.cc | 1 + src/node_snapshotable.h | 3 +- 10 files changed, 253 insertions(+), 179 deletions(-) diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index ae4eac502803e7..b43fb2a5eb4b3a 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -162,6 +162,9 @@ const rawMethods = internalBinding('process_methods'); process.kill = wrapped.kill; process.exit = wrapped.exit; + process.hrtime = perThreadSetup.hrtime; + process.hrtime.bigint = perThreadSetup.hrtimeBigInt; + process.openStdin = function() { process.stdin.resume(); return process.stdin; diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js index f2a10641906e31..71b0afd4e67c99 100644 --- a/lib/internal/bootstrap/pre_execution.js +++ b/lib/internal/bootstrap/pre_execution.js @@ -82,16 +82,7 @@ function patchProcessObject(expandArgv1) { const binding = internalBinding('process_methods'); binding.patchProcessObject(process); - // TODO(joyeecheung): snapshot fast APIs (which need to work with - // array buffers, whose connection with C++ needs to be rebuilt after - // deserialization). - const { - hrtime, - hrtimeBigInt - } = require('internal/process/per_thread').getFastAPIs(binding); - - process.hrtime = hrtime; - process.hrtime.bigint = hrtimeBigInt; + require('internal/process/per_thread').refreshHrtimeBuffer(); ObjectDefineProperty(process, 'argv0', { enumerable: true, diff --git a/lib/internal/process/per_thread.js b/lib/internal/process/per_thread.js index f1d11911a4444a..a63217d9b3955a 100644 --- a/lib/internal/process/per_thread.js +++ b/lib/internal/process/per_thread.js @@ -54,49 +54,48 @@ function assert(x, msg) { if (!x) throw new ERR_ASSERTION(msg || 'assertion error'); } -function getFastAPIs(binding) { - const { - hrtime: _hrtime - } = binding.getFastAPIs(); +const binding = internalBinding('process_methods'); + +let hrValues; +let hrBigintValues; +function refreshHrtimeBuffer() { // The 3 entries filled in by the original process.hrtime contains // the upper/lower 32 bits of the second part of the value, // and the remaining nanoseconds of the value. - const hrValues = new Uint32Array(_hrtime.buffer); + hrValues = new Uint32Array(binding.hrtimeBuffer); + // Use a BigUint64Array in the closure because this is actually a bit + // faster than simply returning a BigInt from C++ in V8 7.1. + hrBigintValues = new BigUint64Array(binding.hrtimeBuffer, 0, 1); +} - function hrtime(time) { - _hrtime.hrtime(); +// Create the buffers. +refreshHrtimeBuffer(); - if (time !== undefined) { - validateArray(time, 'time'); - if (time.length !== 2) { - throw new ERR_OUT_OF_RANGE('time', 2, time.length); - } +function hrtime(time) { + binding.hrtime(); - const sec = (hrValues[0] * 0x100000000 + hrValues[1]) - time[0]; - const nsec = hrValues[2] - time[1]; - const needsBorrow = nsec < 0; - return [needsBorrow ? sec - 1 : sec, needsBorrow ? nsec + 1e9 : nsec]; + if (time !== undefined) { + validateArray(time, 'time'); + if (time.length !== 2) { + throw new ERR_OUT_OF_RANGE('time', 2, time.length); } - return [ - hrValues[0] * 0x100000000 + hrValues[1], - hrValues[2], - ]; + const sec = (hrValues[0] * 0x100000000 + hrValues[1]) - time[0]; + const nsec = hrValues[2] - time[1]; + const needsBorrow = nsec < 0; + return [needsBorrow ? sec - 1 : sec, needsBorrow ? nsec + 1e9 : nsec]; } - // Use a BigUint64Array in the closure because this is actually a bit - // faster than simply returning a BigInt from C++ in V8 7.1. - const hrBigintValues = new BigUint64Array(_hrtime.buffer, 0, 1); - function hrtimeBigInt() { - _hrtime.hrtimeBigInt(); - return hrBigintValues[0]; - } + return [ + hrValues[0] * 0x100000000 + hrValues[1], + hrValues[2], + ]; +} - return { - hrtime, - hrtimeBigInt, - }; +function hrtimeBigInt() { + binding.hrtimeBigInt(); + return hrBigintValues[0]; } // The execution of this function itself should not cause any side effects. @@ -396,8 +395,10 @@ function toggleTraceCategoryState(asyncHooksEnabled) { module.exports = { toggleTraceCategoryState, - getFastAPIs, assert, buildAllowedFlags, - wrapProcessMethods + wrapProcessMethods, + hrtime, + hrtimeBigInt, + refreshHrtimeBuffer, }; diff --git a/src/env-inl.h b/src/env-inl.h index 1e85bc07a4cc29..04d4edc90b50d1 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -29,11 +29,12 @@ #include "callback_queue-inl.h" #include "env.h" #include "node.h" +#include "node_context_data.h" +#include "node_perf_common.h" #include "util-inl.h" #include "uv.h" +#include "v8-fast-api-calls.h" #include "v8.h" -#include "node_perf_common.h" -#include "node_context_data.h" #include #include @@ -1036,13 +1037,20 @@ inline void Environment::ThrowUVException(int errorno, UVException(isolate(), errorno, syscall, message, path, dest)); } -inline v8::Local - Environment::NewFunctionTemplate(v8::FunctionCallback callback, - v8::Local signature, - v8::ConstructorBehavior behavior, - v8::SideEffectType side_effect_type) { - return v8::FunctionTemplate::New(isolate(), callback, v8::Local(), - signature, 0, behavior, side_effect_type); +inline v8::Local Environment::NewFunctionTemplate( + v8::FunctionCallback callback, + v8::Local signature, + v8::ConstructorBehavior behavior, + v8::SideEffectType side_effect_type, + const v8::CFunction* c_function) { + return v8::FunctionTemplate::New(isolate(), + callback, + v8::Local(), + signature, + 0, + behavior, + side_effect_type, + c_function); } inline void Environment::SetMethod(v8::Local that, @@ -1063,6 +1071,25 @@ inline void Environment::SetMethod(v8::Local that, function->SetName(name_string); // NODE_SET_METHOD() compatibility. } +inline void Environment::SetFastMethod(v8::Local that, + const char* name, + v8::FunctionCallback slow_callback, + const v8::CFunction* c_function) { + v8::Local context = isolate()->GetCurrentContext(); + v8::Local function = + NewFunctionTemplate(slow_callback, + v8::Local(), + v8::ConstructorBehavior::kThrow, + v8::SideEffectType::kHasNoSideEffect, + c_function) + ->GetFunction(context) + .ToLocalChecked(); + const v8::NewStringType type = v8::NewStringType::kInternalized; + v8::Local name_string = + v8::String::NewFromUtf8(isolate(), name, type).ToLocalChecked(); + that->Set(context, name_string, function).Check(); +} + inline void Environment::SetMethodNoSideEffect(v8::Local that, const char* name, v8::FunctionCallback callback) { diff --git a/src/env.h b/src/env.h index 7078a8fb4f5f74..c0712d4881a084 100644 --- a/src/env.h +++ b/src/env.h @@ -34,6 +34,7 @@ #include "handle_wrap.h" #include "node.h" #include "node_binding.h" +#include "node_external_reference.h" #include "node_main_instance.h" #include "node_options.h" #include "node_perf_common.h" @@ -1239,20 +1240,23 @@ class Environment : public MemoryRetainer { const char* path = nullptr, const char* dest = nullptr); - inline v8::Local - NewFunctionTemplate(v8::FunctionCallback callback, - v8::Local signature = - v8::Local(), - v8::ConstructorBehavior behavior = - v8::ConstructorBehavior::kAllow, - v8::SideEffectType side_effect = - v8::SideEffectType::kHasSideEffect); + inline v8::Local NewFunctionTemplate( + v8::FunctionCallback callback, + v8::Local signature = v8::Local(), + v8::ConstructorBehavior behavior = v8::ConstructorBehavior::kAllow, + v8::SideEffectType side_effect = v8::SideEffectType::kHasSideEffect, + const v8::CFunction* c_function = nullptr); // Convenience methods for NewFunctionTemplate(). inline void SetMethod(v8::Local that, const char* name, v8::FunctionCallback callback); + inline void SetFastMethod(v8::Local that, + const char* name, + v8::FunctionCallback slow_callback, + const v8::CFunction* c_function); + inline void SetProtoMethod(v8::Local that, const char* name, v8::FunctionCallback callback); diff --git a/src/node_external_reference.h b/src/node_external_reference.h index 445ca19f3fbcf2..347945a7496d5f 100644 --- a/src/node_external_reference.h +++ b/src/node_external_reference.h @@ -5,10 +5,13 @@ #include #include +#include "v8-fast-api-calls.h" #include "v8.h" namespace node { +using CFunctionCallback = void (*)(v8::Local receiver); + // This class manages the external references from the V8 heap // to the C++ addresses in Node.js. class ExternalReferenceRegistry { @@ -16,6 +19,8 @@ class ExternalReferenceRegistry { ExternalReferenceRegistry(); #define ALLOWED_EXTERNAL_REFERENCE_TYPES(V) \ + V(CFunctionCallback) \ + V(const v8::CFunctionInfo*) \ V(v8::FunctionCallback) \ V(v8::AccessorGetterCallback) \ V(v8::AccessorSetterCallback) \ diff --git a/src/node_process.h b/src/node_process.h index 14c8f659168975..68956cb0ac3c60 100644 --- a/src/node_process.h +++ b/src/node_process.h @@ -3,10 +3,16 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS +#include "node_snapshotable.h" +#include "v8-fast-api-calls.h" #include "v8.h" namespace node { +class Environment; +class MemoryTracker; +class ExternalReferenceRegistry; + v8::MaybeLocal CreateEnvVarProxy(v8::Local context, v8::Isolate* isolate); @@ -38,6 +44,54 @@ v8::Maybe ProcessEmitDeprecationWarning(Environment* env, v8::MaybeLocal CreateProcessObject(Environment* env); void PatchProcessObject(const v8::FunctionCallbackInfo& args); +namespace process { +class BindingData : public SnapshotableObject { + public: + void AddMethods(); + static void RegisterExternalReferences(ExternalReferenceRegistry* registry); + + SERIALIZABLE_OBJECT_METHODS() + static constexpr FastStringKey type_name{"node::process::BindingData"}; + static constexpr EmbedderObjectType type_int = + EmbedderObjectType::k_process_binding_data; + + BindingData(Environment* env, v8::Local object); + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(BindingData) + SET_SELF_SIZE(BindingData) + + static BindingData* FromV8Value(v8::Local receiver); + static void NumberImpl(BindingData* receiver); + + static void FastNumber(v8::Local receiver) { + NumberImpl(FromV8Value(receiver)); + } + + static void SlowNumber(const v8::FunctionCallbackInfo& args); + + static void BigIntImpl(BindingData* receiver); + + static void FastBigInt(v8::Local receiver) { + BigIntImpl(FromV8Value(receiver)); + } + + static void SlowBigInt(const v8::FunctionCallbackInfo& args); + + private: + static constexpr size_t kBufferSize = + std::max(sizeof(uint64_t), sizeof(uint32_t) * 3); + v8::Global array_buffer_; + std::shared_ptr backing_store_; + + // These need to be static so that we have their addresses available to + // register as external references in the snapshot at environment creation + // time. + static v8::CFunction fast_number_; + static v8::CFunction fast_bigint_; +}; + +} // namespace process } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #endif // SRC_NODE_PROCESS_H_ diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index e1ff446ac106d3..e4b16805586cb9 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -36,14 +36,10 @@ namespace node { using v8::Array; using v8::ArrayBuffer; -using v8::BackingStore; using v8::CFunction; -using v8::ConstructorBehavior; using v8::Context; using v8::Float64Array; using v8::FunctionCallbackInfo; -using v8::FunctionTemplate; -using v8::Global; using v8::HeapStatistics; using v8::Integer; using v8::Isolate; @@ -51,9 +47,6 @@ using v8::Local; using v8::NewStringType; using v8::Number; using v8::Object; -using v8::ObjectTemplate; -using v8::SideEffectType; -using v8::Signature; using v8::String; using v8::Uint32; using v8::Value; @@ -423,124 +416,119 @@ static void ReallyExit(const FunctionCallbackInfo& args) { env->Exit(code); } -class FastHrtime : public BaseObject { - public: - static Local New(Environment* env) { - Local ctor = FunctionTemplate::New(env->isolate()); - ctor->Inherit(BaseObject::GetConstructorTemplate(env)); - Local otmpl = ctor->InstanceTemplate(); - otmpl->SetInternalFieldCount(FastHrtime::kInternalFieldCount); - - auto create_func = [env](auto fast_func, auto slow_func) { - auto cfunc = CFunction::Make(fast_func); - return FunctionTemplate::New(env->isolate(), - slow_func, - Local(), - Local(), - 0, - ConstructorBehavior::kThrow, - SideEffectType::kHasNoSideEffect, - &cfunc); - }; - - otmpl->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "hrtime"), - create_func(FastNumber, SlowNumber)); - otmpl->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "hrtimeBigInt"), - create_func(FastBigInt, SlowBigInt)); - - Local obj = otmpl->NewInstance(env->context()).ToLocalChecked(); - - Local ab = - ArrayBuffer::New(env->isolate(), - std::max(sizeof(uint64_t), sizeof(uint32_t) * 3)); - new FastHrtime(env, obj, ab); - obj->Set( - env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "buffer"), ab) - .ToChecked(); - - return obj; - } +namespace process { - private: - FastHrtime(Environment* env, - Local object, - Local ab) - : BaseObject(env, object), - array_buffer_(env->isolate(), ab), - backing_store_(ab->GetBackingStore()) { - MakeWeak(); - } +constexpr FastStringKey BindingData::type_name; - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackField("array_buffer", array_buffer_); - } - SET_MEMORY_INFO_NAME(FastHrtime) - SET_SELF_SIZE(FastHrtime) +BindingData::BindingData(Environment* env, v8::Local object) + : SnapshotableObject(env, object, type_int) { + Local ab = ArrayBuffer::New(env->isolate(), kBufferSize); + array_buffer_.Reset(env->isolate(), ab); + object + ->Set(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "hrtimeBuffer"), + ab) + .ToChecked(); + backing_store_ = ab->GetBackingStore(); +} - static FastHrtime* FromV8Value(Local value) { - Local v8_object = value.As(); - return static_cast( - v8_object->GetAlignedPointerFromInternalField(BaseObject::kSlot)); - } +v8::CFunction BindingData::fast_number_(v8::CFunction::Make(FastNumber)); +v8::CFunction BindingData::fast_bigint_(v8::CFunction::Make(FastBigInt)); - // This is the legacy version of hrtime before BigInt was introduced in - // JavaScript. - // The value returned by uv_hrtime() is a 64-bit int representing nanoseconds, - // so this function instead fills in an Uint32Array with 3 entries, - // to avoid any integer overflow possibility. - // The first two entries contain the second part of the value - // broken into the upper/lower 32 bits to be converted back in JS, - // because there is no Uint64Array in JS. - // The third entry contains the remaining nanosecond part of the value. - static void NumberImpl(FastHrtime* receiver) { - uint64_t t = uv_hrtime(); - uint32_t* fields = static_cast(receiver->backing_store_->Data()); - fields[0] = (t / NANOS_PER_SEC) >> 32; - fields[1] = (t / NANOS_PER_SEC) & 0xffffffff; - fields[2] = t % NANOS_PER_SEC; - } +void BindingData::AddMethods() { + env()->SetFastMethod(object(), "hrtime", SlowNumber, &fast_number_); + env()->SetFastMethod(object(), "hrtimeBigInt", SlowBigInt, &fast_bigint_); +} - static void FastNumber(Local receiver) { - NumberImpl(FromV8Value(receiver)); - } +void BindingData::RegisterExternalReferences( + ExternalReferenceRegistry* registry) { + registry->Register(SlowNumber); + registry->Register(SlowBigInt); + registry->Register(FastNumber); + registry->Register(FastBigInt); + registry->Register(fast_number_.GetTypeInfo()); + registry->Register(fast_bigint_.GetTypeInfo()); +} - static void SlowNumber(const FunctionCallbackInfo& args) { - NumberImpl(FromJSObject(args.Holder())); - } +BindingData* BindingData::FromV8Value(Local value) { + Local v8_object = value.As(); + return static_cast( + v8_object->GetAlignedPointerFromInternalField(BaseObject::kSlot)); +} - static void BigIntImpl(FastHrtime* receiver) { - uint64_t t = uv_hrtime(); - uint64_t* fields = static_cast(receiver->backing_store_->Data()); - fields[0] = t; - } +void BindingData::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("array_buffer", array_buffer_); +} - static void FastBigInt(Local receiver) { - BigIntImpl(FromV8Value(receiver)); - } +// This is the legacy version of hrtime before BigInt was introduced in +// JavaScript. +// The value returned by uv_hrtime() is a 64-bit int representing nanoseconds, +// so this function instead fills in an Uint32Array with 3 entries, +// to avoid any integer overflow possibility. +// The first two entries contain the second part of the value +// broken into the upper/lower 32 bits to be converted back in JS, +// because there is no Uint64Array in JS. +// The third entry contains the remaining nanosecond part of the value. +void BindingData::NumberImpl(BindingData* receiver) { + // Make sure we don't accidentally access buffers wiped for snapshot. + CHECK(!receiver->array_buffer_.IsEmpty()); + uint64_t t = uv_hrtime(); + uint32_t* fields = static_cast(receiver->backing_store_->Data()); + fields[0] = (t / NANOS_PER_SEC) >> 32; + fields[1] = (t / NANOS_PER_SEC) & 0xffffffff; + fields[2] = t % NANOS_PER_SEC; +} - static void SlowBigInt(const FunctionCallbackInfo& args) { - BigIntImpl(FromJSObject(args.Holder())); - } +void BindingData::BigIntImpl(BindingData* receiver) { + // Make sure we don't accidentally access buffers wiped for snapshot. + CHECK(!receiver->array_buffer_.IsEmpty()); + uint64_t t = uv_hrtime(); + uint64_t* fields = static_cast(receiver->backing_store_->Data()); + fields[0] = t; +} + +void BindingData::SlowBigInt(const FunctionCallbackInfo& args) { + BigIntImpl(FromJSObject(args.Holder())); +} - Global array_buffer_; - std::shared_ptr backing_store_; -}; +void BindingData::SlowNumber(const v8::FunctionCallbackInfo& args) { + NumberImpl(FromJSObject(args.Holder())); +} -static void GetFastAPIs(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Local ret = Object::New(env->isolate()); - ret->Set(env->context(), - FIXED_ONE_BYTE_STRING(env->isolate(), "hrtime"), - FastHrtime::New(env)) - .ToChecked(); - args.GetReturnValue().Set(ret); +void BindingData::PrepareForSerialization(Local context, + v8::SnapshotCreator* creator) { + // It's not worth keeping. + // Release it, we will recreate it when the instance is dehydrated. + array_buffer_.Reset(); } -static void InitializeProcessMethods(Local target, - Local unused, - Local context, - void* priv) { +InternalFieldInfo* BindingData::Serialize(int index) { + DCHECK_EQ(index, BaseObject::kSlot); + InternalFieldInfo* info = InternalFieldInfo::New(type()); + return info; +} + +void BindingData::Deserialize(Local context, + Local holder, + int index, + InternalFieldInfo* info) { + DCHECK_EQ(index, BaseObject::kSlot); + v8::HandleScope scope(context->GetIsolate()); Environment* env = Environment::GetCurrent(context); + // Recreate the buffer in the constructor. + BindingData* binding = env->AddBindingData(context, holder); + CHECK_NOT_NULL(binding); +} + +static void Initialize(Local target, + Local unused, + Local context, + void* priv) { + Environment* env = Environment::GetCurrent(context); + BindingData* const binding_data = + env->AddBindingData(context, target); + if (binding_data == nullptr) return; + binding_data->AddMethods(); // define various internal methods if (env->owns_process_state()) { @@ -567,11 +555,11 @@ static void InitializeProcessMethods(Local target, env->SetMethod(target, "reallyExit", ReallyExit); env->SetMethodNoSideEffect(target, "uptime", Uptime); env->SetMethod(target, "patchProcessObject", PatchProcessObject); - env->SetMethod(target, "getFastAPIs", GetFastAPIs); } -void RegisterProcessMethodsExternalReferences( - ExternalReferenceRegistry* registry) { +void RegisterExternalReferences(ExternalReferenceRegistry* registry) { + BindingData::RegisterExternalReferences(registry); + registry->Register(DebugProcess); registry->Register(DebugEnd); registry->Register(Abort); @@ -594,12 +582,11 @@ void RegisterProcessMethodsExternalReferences( registry->Register(ReallyExit); registry->Register(Uptime); registry->Register(PatchProcessObject); - registry->Register(GetFastAPIs); } +} // namespace process } // namespace node -NODE_MODULE_CONTEXT_AWARE_INTERNAL(process_methods, - node::InitializeProcessMethods) +NODE_MODULE_CONTEXT_AWARE_INTERNAL(process_methods, node::process::Initialize) NODE_MODULE_EXTERNAL_REFERENCE(process_methods, - node::RegisterProcessMethodsExternalReferences) + node::process::RegisterExternalReferences) diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 6e284bb66a0de5..6f45ce537907cd 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -11,6 +11,7 @@ #include "node_file.h" #include "node_internals.h" #include "node_main_instance.h" +#include "node_process.h" #include "node_v8.h" #include "node_v8_platform-inl.h" diff --git a/src/node_snapshotable.h b/src/node_snapshotable.h index ceb84fc9bf0382..1ccd9a93226241 100644 --- a/src/node_snapshotable.h +++ b/src/node_snapshotable.h @@ -16,7 +16,8 @@ struct SnapshotData; #define SERIALIZABLE_OBJECT_TYPES(V) \ V(fs_binding_data, fs::BindingData) \ V(v8_binding_data, v8_utils::BindingData) \ - V(blob_binding_data, BlobBindingData) + V(blob_binding_data, BlobBindingData) \ + V(process_binding_data, process::BindingData) enum class EmbedderObjectType : uint8_t { k_default = 0,