Skip to content

Commit

Permalink
process: support hrtime in the snapshot
Browse files Browse the repository at this point in the history
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: #40649
Refs: #35711
Refs: #37476
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
joyeecheung committed Nov 17, 2021
1 parent 4265f27 commit e6d8ae0
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 179 deletions.
3 changes: 3 additions & 0 deletions lib/internal/bootstrap/node.js
Expand Up @@ -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;
Expand Down
11 changes: 1 addition & 10 deletions lib/internal/bootstrap/pre_execution.js
Expand Up @@ -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,
Expand Down
67 changes: 34 additions & 33 deletions lib/internal/process/per_thread.js
Expand Up @@ -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.
Expand Down Expand Up @@ -396,8 +395,10 @@ function toggleTraceCategoryState(asyncHooksEnabled) {

module.exports = {
toggleTraceCategoryState,
getFastAPIs,
assert,
buildAllowedFlags,
wrapProcessMethods
wrapProcessMethods,
hrtime,
hrtimeBigInt,
refreshHrtimeBuffer,
};
45 changes: 36 additions & 9 deletions src/env-inl.h
Expand Up @@ -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 <cstddef>
#include <cstdint>
Expand Down Expand Up @@ -1036,13 +1037,20 @@ inline void Environment::ThrowUVException(int errorno,
UVException(isolate(), errorno, syscall, message, path, dest));
}

inline v8::Local<v8::FunctionTemplate>
Environment::NewFunctionTemplate(v8::FunctionCallback callback,
v8::Local<v8::Signature> signature,
v8::ConstructorBehavior behavior,
v8::SideEffectType side_effect_type) {
return v8::FunctionTemplate::New(isolate(), callback, v8::Local<v8::Value>(),
signature, 0, behavior, side_effect_type);
inline v8::Local<v8::FunctionTemplate> Environment::NewFunctionTemplate(
v8::FunctionCallback callback,
v8::Local<v8::Signature> signature,
v8::ConstructorBehavior behavior,
v8::SideEffectType side_effect_type,
const v8::CFunction* c_function) {
return v8::FunctionTemplate::New(isolate(),
callback,
v8::Local<v8::Value>(),
signature,
0,
behavior,
side_effect_type,
c_function);
}

inline void Environment::SetMethod(v8::Local<v8::Object> that,
Expand All @@ -1063,6 +1071,25 @@ inline void Environment::SetMethod(v8::Local<v8::Object> that,
function->SetName(name_string); // NODE_SET_METHOD() compatibility.
}

inline void Environment::SetFastMethod(v8::Local<v8::Object> that,
const char* name,
v8::FunctionCallback slow_callback,
const v8::CFunction* c_function) {
v8::Local<v8::Context> context = isolate()->GetCurrentContext();
v8::Local<v8::Function> function =
NewFunctionTemplate(slow_callback,
v8::Local<v8::Signature>(),
v8::ConstructorBehavior::kThrow,
v8::SideEffectType::kHasNoSideEffect,
c_function)
->GetFunction(context)
.ToLocalChecked();
const v8::NewStringType type = v8::NewStringType::kInternalized;
v8::Local<v8::String> name_string =
v8::String::NewFromUtf8(isolate(), name, type).ToLocalChecked();
that->Set(context, name_string, function).Check();
}

inline void Environment::SetMethodNoSideEffect(v8::Local<v8::Object> that,
const char* name,
v8::FunctionCallback callback) {
Expand Down
20 changes: 12 additions & 8 deletions src/env.h
Expand Up @@ -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"
Expand Down Expand Up @@ -1239,20 +1240,23 @@ class Environment : public MemoryRetainer {
const char* path = nullptr,
const char* dest = nullptr);

inline v8::Local<v8::FunctionTemplate>
NewFunctionTemplate(v8::FunctionCallback callback,
v8::Local<v8::Signature> signature =
v8::Local<v8::Signature>(),
v8::ConstructorBehavior behavior =
v8::ConstructorBehavior::kAllow,
v8::SideEffectType side_effect =
v8::SideEffectType::kHasSideEffect);
inline v8::Local<v8::FunctionTemplate> NewFunctionTemplate(
v8::FunctionCallback callback,
v8::Local<v8::Signature> signature = v8::Local<v8::Signature>(),
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<v8::Object> that,
const char* name,
v8::FunctionCallback callback);

inline void SetFastMethod(v8::Local<v8::Object> that,
const char* name,
v8::FunctionCallback slow_callback,
const v8::CFunction* c_function);

inline void SetProtoMethod(v8::Local<v8::FunctionTemplate> that,
const char* name,
v8::FunctionCallback callback);
Expand Down
5 changes: 5 additions & 0 deletions src/node_external_reference.h
Expand Up @@ -5,17 +5,22 @@

#include <cinttypes>
#include <vector>
#include "v8-fast-api-calls.h"
#include "v8.h"

namespace node {

using CFunctionCallback = void (*)(v8::Local<v8::Value> receiver);

// This class manages the external references from the V8 heap
// to the C++ addresses in Node.js.
class ExternalReferenceRegistry {
public:
ExternalReferenceRegistry();

#define ALLOWED_EXTERNAL_REFERENCE_TYPES(V) \
V(CFunctionCallback) \
V(const v8::CFunctionInfo*) \
V(v8::FunctionCallback) \
V(v8::AccessorGetterCallback) \
V(v8::AccessorSetterCallback) \
Expand Down
54 changes: 54 additions & 0 deletions src/node_process.h
Expand Up @@ -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<v8::Object> CreateEnvVarProxy(v8::Local<v8::Context> context,
v8::Isolate* isolate);

Expand Down Expand Up @@ -38,6 +44,54 @@ v8::Maybe<bool> ProcessEmitDeprecationWarning(Environment* env,
v8::MaybeLocal<v8::Object> CreateProcessObject(Environment* env);
void PatchProcessObject(const v8::FunctionCallbackInfo<v8::Value>& 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<v8::Object> object);

void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(BindingData)
SET_SELF_SIZE(BindingData)

static BindingData* FromV8Value(v8::Local<v8::Value> receiver);
static void NumberImpl(BindingData* receiver);

static void FastNumber(v8::Local<v8::Value> receiver) {
NumberImpl(FromV8Value(receiver));
}

static void SlowNumber(const v8::FunctionCallbackInfo<v8::Value>& args);

static void BigIntImpl(BindingData* receiver);

static void FastBigInt(v8::Local<v8::Value> receiver) {
BigIntImpl(FromV8Value(receiver));
}

static void SlowBigInt(const v8::FunctionCallbackInfo<v8::Value>& args);

private:
static constexpr size_t kBufferSize =
std::max(sizeof(uint64_t), sizeof(uint32_t) * 3);
v8::Global<v8::ArrayBuffer> array_buffer_;
std::shared_ptr<v8::BackingStore> 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_

0 comments on commit e6d8ae0

Please sign in to comment.