diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 58f7396990dddb..21b6da230c7e44 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,