Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

process: use v8 fast api calls for hrtime #33600

Merged
merged 2 commits into from Jun 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion common.gypi
Expand Up @@ -36,7 +36,7 @@

# Reset this number to 0 on major V8 upgrades.
# Increment by one for each non-official patch applied to deps/v8.
'v8_embedder_string': '-node.17',
'v8_embedder_string': '-node.18',

##### V8 defaults for Node.js #####

Expand Down
40 changes: 22 additions & 18 deletions deps/v8/include/v8-fast-api-calls.h
Expand Up @@ -183,30 +183,32 @@ class CTypeInfo {
kUnwrappedApiObject,
};

enum ArgFlags : char {
None = 0,
IsArrayBit = 1 << 0, // This argument is first in an array of values.
enum class ArgFlags : uint8_t {
kNone = 0,
kIsArrayBit = 1 << 0, // This argument is first in an array of values.
};

static CTypeInfo FromWrapperType(const void* wrapper_type_info,
ArgFlags flags = ArgFlags::None) {
ArgFlags flags = ArgFlags::kNone) {
uintptr_t wrapper_type_info_ptr =
reinterpret_cast<uintptr_t>(wrapper_type_info);
// Check that the lower kIsWrapperTypeBit bits are 0's.
CHECK_EQ(
wrapper_type_info_ptr & ~(static_cast<uintptr_t>(~0)
<< static_cast<uintptr_t>(kIsWrapperTypeBit)),
0);
0u);
// TODO(mslekova): Refactor the manual bit manipulations to use
// PointerWithPayload instead.
return CTypeInfo(wrapper_type_info_ptr | flags | kIsWrapperTypeBit);
return CTypeInfo(wrapper_type_info_ptr | static_cast<int>(flags) |
kIsWrapperTypeBit);
}

static constexpr CTypeInfo FromCType(Type ctype,
ArgFlags flags = ArgFlags::None) {
ArgFlags flags = ArgFlags::kNone) {
// ctype cannot be Type::kUnwrappedApiObject.
return CTypeInfo(
((static_cast<uintptr_t>(ctype) << kTypeOffset) & kTypeMask) | flags);
((static_cast<uintptr_t>(ctype) << kTypeOffset) & kTypeMask) |
static_cast<int>(flags));
}

const void* GetWrapperInfo() const;
Expand All @@ -218,7 +220,9 @@ class CTypeInfo {
return static_cast<Type>((payload_ & kTypeMask) >> kTypeOffset);
}

constexpr bool IsArray() const { return payload_ & ArgFlags::IsArrayBit; }
constexpr bool IsArray() const {
return payload_ & static_cast<int>(ArgFlags::kIsArrayBit);
}

private:
explicit constexpr CTypeInfo(uintptr_t payload) : payload_(payload) {}
Expand Down Expand Up @@ -283,9 +287,6 @@ SUPPORTED_C_TYPES(SPECIALIZE_GET_C_TYPE_FOR)
template <typename T, typename = void>
struct EnableIfHasWrapperTypeInfo {};

template <>
struct EnableIfHasWrapperTypeInfo<void> {};

template <typename T>
struct EnableIfHasWrapperTypeInfo<T, decltype(WrapperTraits<T>::GetTypeInfo(),
void())> {
Expand All @@ -297,7 +298,7 @@ template <typename T, typename = void>
struct GetCTypePointerImpl {
static constexpr CTypeInfo Get() {
return CTypeInfo::FromCType(GetCType<T>::Get().GetType(),
CTypeInfo::IsArrayBit);
CTypeInfo::ArgFlags::kIsArrayBit);
}
};

Expand All @@ -321,7 +322,7 @@ struct GetCTypePointerPointerImpl<
T, typename EnableIfHasWrapperTypeInfo<T>::type> {
static constexpr CTypeInfo Get() {
return CTypeInfo::FromWrapperType(WrapperTraits<T>::GetTypeInfo(),
CTypeInfo::IsArrayBit);
CTypeInfo::ArgFlags::kIsArrayBit);
}
};

Expand All @@ -335,11 +336,12 @@ template <typename R, typename... Args>
class CFunctionInfoImpl : public CFunctionInfo {
public:
CFunctionInfoImpl()
: return_info_(i::GetCType<R>::Get()),
: return_info_(internal::GetCType<R>::Get()),
arg_count_(sizeof...(Args)),
arg_info_{i::GetCType<Args>::Get()...} {
static_assert(i::GetCType<R>::Get().GetType() == CTypeInfo::Type::kVoid,
"Only void return types are currently supported.");
arg_info_{internal::GetCType<Args>::Get()...} {
static_assert(
internal::GetCType<R>::Get().GetType() == CTypeInfo::Type::kVoid,
"Only void return types are currently supported.");
}

const CTypeInfo& ReturnInfo() const override { return return_info_; }
Expand All @@ -359,6 +361,8 @@ class CFunctionInfoImpl : public CFunctionInfo {

class V8_EXPORT CFunction {
public:
constexpr CFunction() : address_(nullptr), type_info_(nullptr) {}

const CTypeInfo& ReturnInfo() const { return type_info_->ReturnInfo(); }

const CTypeInfo& ArgumentInfo(unsigned int index) const {
Expand Down
12 changes: 11 additions & 1 deletion deps/v8/include/v8.h
Expand Up @@ -8184,7 +8184,9 @@ class V8_EXPORT Isolate {
array_buffer_allocator_shared(),
external_references(nullptr),
allow_atomics_wait(true),
only_terminate_in_safe_scope(false) {}
only_terminate_in_safe_scope(false),
embedder_wrapper_type_index(-1),
embedder_wrapper_object_index(-1) {}

/**
* Allows the host application to provide the address of a function that is
Expand Down Expand Up @@ -8248,6 +8250,14 @@ class V8_EXPORT Isolate {
* Termination is postponed when there is no active SafeForTerminationScope.
*/
bool only_terminate_in_safe_scope;

/**
* The following parameters describe the offsets for addressing type info
* for wrapped API objects and are used by the fast C API
* (for details see v8-fast-api-calls.h).
*/
int embedder_wrapper_type_index;
int embedder_wrapper_object_index;
};


Expand Down
9 changes: 7 additions & 2 deletions deps/v8/src/api/api.cc
Expand Up @@ -1582,8 +1582,9 @@ void FunctionTemplate::SetCallHandler(FunctionCallback callback,
data = v8::Undefined(reinterpret_cast<v8::Isolate*>(isolate));
}
obj->set_data(*Utils::OpenHandle(*data));
if (c_function != nullptr) {
DCHECK_NOT_NULL(c_function->GetAddress());
// Blink passes CFunction's constructed with the default constructor
// for non-fast calls, so we should check the address too.
if (c_function != nullptr && c_function->GetAddress()) {
i::FunctionTemplateInfo::SetCFunction(
isolate, info,
i::handle(*FromCData(isolate, c_function->GetAddress()), isolate));
Expand Down Expand Up @@ -8333,6 +8334,10 @@ void Isolate::Initialize(Isolate* isolate,
}
i_isolate->set_only_terminate_in_safe_scope(
params.only_terminate_in_safe_scope);
i_isolate->set_embedder_wrapper_type_index(
params.embedder_wrapper_type_index);
i_isolate->set_embedder_wrapper_object_index(
params.embedder_wrapper_object_index);

if (!i::V8::GetCurrentPlatform()
->GetForegroundTaskRunner(isolate)
Expand Down
2 changes: 2 additions & 0 deletions deps/v8/test/cctest/test-api.cc
Expand Up @@ -27064,6 +27064,8 @@ void SetupTest(v8::Local<v8::Value> initial_value, LocalContext* env,
v8::Isolate* isolate = CcTest::isolate();

v8::CFunction c_func = v8::CFunction::Make(ApiNumberChecker<T>::CheckArgFast);
CHECK_EQ(c_func.ArgumentInfo(0).GetType(),
v8::CTypeInfo::Type::kUnwrappedApiObject);

Local<v8::FunctionTemplate> checker_templ = v8::FunctionTemplate::New(
isolate, ApiNumberChecker<T>::CheckArgSlow, v8::Local<v8::Value>(),
Expand Down
9 changes: 4 additions & 5 deletions lib/internal/process/per_thread.js
Expand Up @@ -41,7 +41,6 @@ function assert(x, msg) {
function wrapProcessMethods(binding) {
const {
hrtime: _hrtime,
hrtimeBigInt: _hrtimeBigInt,
cpuUsage: _cpuUsage,
memoryUsage: _memoryUsage,
resourceUsage: _resourceUsage
Expand Down Expand Up @@ -113,10 +112,10 @@ function wrapProcessMethods(binding) {
// 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(3);
const hrValues = new Uint32Array(_hrtime.buffer);

function hrtime(time) {
_hrtime(hrValues);
_hrtime.hrtime();

if (time !== undefined) {
if (!ArrayIsArray(time)) {
Expand All @@ -140,9 +139,9 @@ function wrapProcessMethods(binding) {

// 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(1);
const hrBigintValues = new BigUint64Array(_hrtime.buffer, 0, 1);
function hrtimeBigInt() {
_hrtimeBigInt(hrBigintValues);
_hrtime.hrtimeBigInt();
return hrBigintValues[0];
}

Expand Down
2 changes: 2 additions & 0 deletions src/api/environment.cc
Expand Up @@ -209,6 +209,8 @@ void SetIsolateCreateParamsForNode(Isolate::CreateParams* params) {
// heap based on the actual physical memory.
params->constraints.ConfigureDefaults(total_memory, 0);
}
params->embedder_wrapper_object_index = BaseObject::InternalFields::kSlot;
params->embedder_wrapper_type_index = std::numeric_limits<int>::max();
}

void SetIsolateErrorHandlers(v8::Isolate* isolate, const IsolateSettings& s) {
Expand Down
131 changes: 98 additions & 33 deletions src/node_process_methods.cc
Expand Up @@ -7,6 +7,7 @@
#include "node_process.h"
#include "util-inl.h"
#include "uv.h"
#include "v8-fast-api-calls.h"
#include "v8.h"

#include <vector>
Expand All @@ -33,7 +34,7 @@ namespace node {

using v8::Array;
using v8::ArrayBuffer;
using v8::BigUint64Array;
using v8::BackingStore;
using v8::Context;
using v8::Float64Array;
using v8::FunctionCallbackInfo;
Expand All @@ -46,7 +47,6 @@ using v8::Number;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Uint32Array;
using v8::Value;

namespace per_process {
Expand Down Expand Up @@ -131,35 +131,6 @@ static void Cwd(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(cwd);
}


// Hrtime exposes libuv's uv_hrtime() high-resolution timer.

// 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 Hrtime(const FunctionCallbackInfo<Value>& args) {
uint64_t t = uv_hrtime();

Local<ArrayBuffer> ab = args[0].As<Uint32Array>()->Buffer();
uint32_t* fields = static_cast<uint32_t*>(ab->GetBackingStore()->Data());

fields[0] = (t / NANOS_PER_SEC) >> 32;
fields[1] = (t / NANOS_PER_SEC) & 0xffffffff;
fields[2] = t % NANOS_PER_SEC;
}

static void HrtimeBigInt(const FunctionCallbackInfo<Value>& args) {
Local<ArrayBuffer> ab = args[0].As<BigUint64Array>()->Buffer();
uint64_t* fields = static_cast<uint64_t*>(ab->GetBackingStore()->Data());
fields[0] = uv_hrtime();
}

static void Kill(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Local<Context> context = env->context();
Expand Down Expand Up @@ -452,6 +423,85 @@ static void ReallyExit(const FunctionCallbackInfo<Value>& args) {
env->Exit(code);
}

class FastHrtime : public BaseObject {
public:
static Local<Object> New(Environment* env) {
Local<v8::ObjectTemplate> otmpl = v8::ObjectTemplate::New(env->isolate());
otmpl->SetInternalFieldCount(FastHrtime::kInternalFieldCount);

auto create_func = [env](auto fast_func, auto slow_func) {
auto cfunc = v8::CFunction::Make(fast_func);
return v8::FunctionTemplate::New(env->isolate(),
slow_func,
Local<Value>(),
Local<v8::Signature>(),
0,
v8::ConstructorBehavior::kThrow,
v8::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<Object> obj = otmpl->NewInstance(env->context()).ToLocalChecked();

Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), 12);
new FastHrtime(env, obj, ab->GetBackingStore());
obj->Set(
env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "buffer"), ab)
.ToChecked();

return obj;
}

private:
FastHrtime(Environment* env,
Local<Object> object,
std::shared_ptr<v8::BackingStore> backing_store)
: BaseObject(env, object), backing_store_(backing_store) {}

void MemoryInfo(MemoryTracker* tracker) const override {}

SET_MEMORY_INFO_NAME(FastHrtime)
SET_SELF_SIZE(FastHrtime)

// 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 FastNumber(FastHrtime* receiver) {
uint64_t t = uv_hrtime();
uint32_t* fields = static_cast<uint32_t*>(receiver->backing_store_->Data());
fields[0] = (t / NANOS_PER_SEC) >> 32;
fields[1] = (t / NANOS_PER_SEC) & 0xffffffff;
fields[2] = t % NANOS_PER_SEC;
jasnell marked this conversation as resolved.
Show resolved Hide resolved
}

static void SlowNumber(const FunctionCallbackInfo<Value>& args) {
FastNumber(FromJSObject<FastHrtime>(args.Holder()));
}

static void FastBigInt(FastHrtime* receiver) {
uint64_t t = uv_hrtime();
uint64_t* fields = static_cast<uint64_t*>(receiver->backing_store_->Data());
fields[0] = t;
}

static void SlowBigInt(const FunctionCallbackInfo<Value>& args) {
FastBigInt(FromJSObject<FastHrtime>(args.Holder()));
}

std::shared_ptr<BackingStore> backing_store_;
};

static void InitializeProcessMethods(Local<Object> target,
Local<Value> unused,
Local<Context> context,
Expand All @@ -475,8 +525,6 @@ static void InitializeProcessMethods(Local<Object> target,
env->SetMethod(target, "_rawDebug", RawDebug);
env->SetMethod(target, "memoryUsage", MemoryUsage);
env->SetMethod(target, "cpuUsage", CPUUsage);
env->SetMethod(target, "hrtime", Hrtime);
env->SetMethod(target, "hrtimeBigInt", HrtimeBigInt);
env->SetMethod(target, "resourceUsage", ResourceUsage);

env->SetMethod(target, "_getActiveRequests", GetActiveRequests);
Expand All @@ -488,9 +536,26 @@ static void InitializeProcessMethods(Local<Object> target,
env->SetMethod(target, "reallyExit", ReallyExit);
env->SetMethodNoSideEffect(target, "uptime", Uptime);
env->SetMethod(target, "patchProcessObject", PatchProcessObject);

target
->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "hrtime"),
FastHrtime::New(env))
.ToChecked();
}

} // namespace node

namespace v8 {
template <>
class WrapperTraits<node::FastHrtime> {
public:
static const void* GetTypeInfo() {
devsnek marked this conversation as resolved.
Show resolved Hide resolved
static const int tag = 0;
return reinterpret_cast<const void*>(&tag);
}
};
} // namespace v8

NODE_MODULE_CONTEXT_AWARE_INTERNAL(process_methods,
node::InitializeProcessMethods)