◐ Shell
clean mode source ↗

n-api: improve runtime perf of n-api func call · nodejs/node@1dc9330

@@ -23,8 +23,6 @@ struct napi_env__ {

2323

loop(_loop) {}

2424

v8::Isolate* isolate;

2525

node::Persistent<v8::Value> last_exception;

26-

node::Persistent<v8::ObjectTemplate> function_data_template;

27-

node::Persistent<v8::ObjectTemplate> accessor_data_template;

2826

napi_extended_error_info last_error;

2927

int open_handle_scopes = 0;

3028

int open_callback_scopes = 0;

@@ -34,19 +32,6 @@ struct napi_env__ {

3432

#define NAPI_PRIVATE_KEY(context, suffix) \

3533

(node::Environment::GetCurrent((context))->napi_ ## suffix())

363437-

#define ENV_OBJECT_TEMPLATE(env, prefix, destination, field_count) \

38-

do { \

39-

if ((env)->prefix ## _template.IsEmpty()) { \

40-

(destination) = v8::ObjectTemplate::New(isolate); \

41-

(destination)->SetInternalFieldCount((field_count)); \

42-

(env)->prefix ## _template.Reset(isolate, (destination)); \

43-

} else { \

44-

(destination) = v8::Local<v8::ObjectTemplate>::New( \

45-

isolate, env->prefix ## _template); \

46-

} \

47-

} while (0)

48-49-5035

#define RETURN_STATUS_IF_FALSE(env, condition, status) \

5136

do { \

5237

if (!(condition)) { \

@@ -491,15 +476,45 @@ class TryCatch : public v8::TryCatch {

491476492477

//=== Function napi_callback wrapper =================================

493478494-

static const int kDataIndex = 0;

495-

static const int kEnvIndex = 1;

479+

// TODO(somebody): these constants can be removed with relevant changes

480+

// in CallbackWrapperBase<> and CallbackBundle.

481+

// Leave them for now just to keep the change set and cognitive load minimal.

482+

static const int kFunctionIndex = 0; // Used in CallbackBundle::cb[]

483+

static const int kGetterIndex = 0; // Used in CallbackBundle::cb[]

484+

static const int kSetterIndex = 1; // Used in CallbackBundle::cb[]

485+

static const int kCallbackCount = 2; // Used in CallbackBundle::cb[]

486+

// Max is "getter + setter" case

487+488+

// Use this data structure to associate callback data with each N-API function

489+

// exposed to JavaScript. The structure is stored in a v8::External which gets

490+

// passed into our callback wrapper. This reduces the performance impact of

491+

// calling through N-API.

492+

// Ref: benchmark/misc/function_call

493+

// Discussion (incl. perf. data): https://github.com/nodejs/node/pull/21072

494+

struct CallbackBundle {

495+

// Bind the lifecycle of `this` C++ object to a JavaScript object.

496+

// We never delete a CallbackBundle C++ object directly.

497+

void BindLifecycleTo(v8::Isolate* isolate, v8::Local<v8::Value> target) {

498+

handle.Reset(isolate, target);

499+

handle.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter);

500+

}

501+502+

napi_env env; // Necessary to invoke C++ NAPI callback

503+

void* cb_data; // The user provided callback data

504+

napi_callback cb[kCallbackCount]; // Max capacity is 2 (getter + setter)

505+

node::Persistent<v8::Value> handle; // Die with this JavaScript object

496506497-

static const int kFunctionIndex = 2;

498-

static const int kFunctionFieldCount = 3;

499-500-

static const int kGetterIndex = 2;

501-

static const int kSetterIndex = 3;

502-

static const int kAccessorFieldCount = 4;

507+

private:

508+

static void WeakCallback(v8::WeakCallbackInfo<CallbackBundle> const& info) {

509+

// Use the "WeakCallback mechanism" to delete the C++ `bundle` object.

510+

// This will be called when the v8::External containing `this` pointer

511+

// is being GC-ed.

512+

CallbackBundle* bundle = info.GetParameter();

513+

if (bundle != nullptr) {

514+

delete bundle;

515+

}

516+

}

517+

};

503518504519

// Base class extended by classes that wrap V8 function and property callback

505520

// info.

@@ -531,10 +546,10 @@ class CallbackWrapperBase : public CallbackWrapper {

531546

: CallbackWrapper(JsValueFromV8LocalValue(cbinfo.This()),

532547

args_length,

533548

nullptr),

534-

_cbinfo(cbinfo),

535-

_cbdata(v8::Local<v8::Object>::Cast(cbinfo.Data())) {

536-

_data = v8::Local<v8::External>::Cast(_cbdata->GetInternalField(kDataIndex))

537-

->Value();

549+

_cbinfo(cbinfo) {

550+

_bundle = reinterpret_cast<CallbackBundle*>(

551+

v8::Local<v8::External>::Cast(cbinfo.Data())->Value());

552+

_data = _bundle->cb_data;

538553

}

539554540555

napi_value GetNewTarget() override { return nullptr; }

@@ -543,13 +558,10 @@ class CallbackWrapperBase : public CallbackWrapper {

543558

void InvokeCallback() {

544559

napi_callback_info cbinfo_wrapper = reinterpret_cast<napi_callback_info>(

545560

static_cast<CallbackWrapper*>(this));

546-

napi_callback cb = reinterpret_cast<napi_callback>(

547-

v8::Local<v8::External>::Cast(

548-

_cbdata->GetInternalField(kInternalFieldIndex))->Value());

549561550-

napi_env env = static_cast<napi_env>(

551-

v8::Local<v8::External>::Cast(

552-

_cbdata->GetInternalField(kEnvIndex))->Value());

562+

// All other pointers we need are stored in `_bundle`

563+

napi_env env = _bundle->env;

564+

napi_callback cb = _bundle->cb[kInternalFieldIndex];

553565554566

napi_value result;

555567

NAPI_CALL_INTO_MODULE_THROW(env, result = cb(env, cbinfo_wrapper));

@@ -560,7 +572,7 @@ class CallbackWrapperBase : public CallbackWrapper {

560572

}

561573562574

const Info& _cbinfo;

563-

const v8::Local<v8::Object> _cbdata;

575+

CallbackBundle* _bundle;

564576

};

565577566578

class FunctionCallbackWrapper

@@ -682,62 +694,35 @@ class SetterCallbackWrapper

682694

// Creates an object to be made available to the static function callback

683695

// wrapper, used to retrieve the native callback function and data pointer.

684696

static

685-

v8::Local<v8::Object> CreateFunctionCallbackData(napi_env env,

686-

napi_callback cb,

687-

void* data) {

688-

v8::Isolate* isolate = env->isolate;

689-

v8::Local<v8::Context> context = isolate->GetCurrentContext();

697+

v8::Local<v8::Value> CreateFunctionCallbackData(napi_env env,

698+

napi_callback cb,

699+

void* data) {

700+

CallbackBundle* bundle = new CallbackBundle();

701+

bundle->cb[kFunctionIndex] = cb;

702+

bundle->cb_data = data;

703+

bundle->env = env;

704+

v8::Local<v8::Value> cbdata = v8::External::New(env->isolate, bundle);

705+

bundle->BindLifecycleTo(env->isolate, cbdata);

690706691-

v8::Local<v8::ObjectTemplate> otpl;

692-

ENV_OBJECT_TEMPLATE(env, function_data, otpl, v8impl::kFunctionFieldCount);

693-

v8::Local<v8::Object> cbdata = otpl->NewInstance(context).ToLocalChecked();

694-695-

cbdata->SetInternalField(

696-

v8impl::kEnvIndex,

697-

v8::External::New(isolate, static_cast<void*>(env)));

698-

cbdata->SetInternalField(

699-

v8impl::kFunctionIndex,

700-

v8::External::New(isolate, reinterpret_cast<void*>(cb)));

701-

cbdata->SetInternalField(

702-

v8impl::kDataIndex,

703-

v8::External::New(isolate, data));

704707

return cbdata;

705708

}

706709707710

// Creates an object to be made available to the static getter/setter

708711

// callback wrapper, used to retrieve the native getter/setter callback

709712

// function and data pointer.

710713

static

711-

v8::Local<v8::Object> CreateAccessorCallbackData(napi_env env,

712-

napi_callback getter,

713-

napi_callback setter,

714-

void* data) {

715-

v8::Isolate* isolate = env->isolate;

716-

v8::Local<v8::Context> context = isolate->GetCurrentContext();

717-718-

v8::Local<v8::ObjectTemplate> otpl;

719-

ENV_OBJECT_TEMPLATE(env, accessor_data, otpl, v8impl::kAccessorFieldCount);

720-

v8::Local<v8::Object> cbdata = otpl->NewInstance(context).ToLocalChecked();

721-722-

cbdata->SetInternalField(

723-

v8impl::kEnvIndex,

724-

v8::External::New(isolate, static_cast<void*>(env)));

725-726-

if (getter != nullptr) {

727-

cbdata->SetInternalField(

728-

v8impl::kGetterIndex,

729-

v8::External::New(isolate, reinterpret_cast<void*>(getter)));

730-

}

731-732-

if (setter != nullptr) {

733-

cbdata->SetInternalField(

734-

v8impl::kSetterIndex,

735-

v8::External::New(isolate, reinterpret_cast<void*>(setter)));

736-

}

714+

v8::Local<v8::Value> CreateAccessorCallbackData(napi_env env,

715+

napi_callback getter,

716+

napi_callback setter,

717+

void* data) {

718+

CallbackBundle* bundle = new CallbackBundle();

719+

bundle->cb[kGetterIndex] = getter;

720+

bundle->cb[kSetterIndex] = setter;

721+

bundle->cb_data = data;

722+

bundle->env = env;

723+

v8::Local<v8::Value> cbdata = v8::External::New(env->isolate, bundle);

724+

bundle->BindLifecycleTo(env->isolate, cbdata);

737725738-

cbdata->SetInternalField(

739-

v8impl::kDataIndex,

740-

v8::External::New(isolate, data));

741726

return cbdata;

742727

}

743728

@@ -1038,7 +1023,7 @@ napi_status napi_create_function(napi_env env,

10381023

v8::Isolate* isolate = env->isolate;

10391024

v8::Local<v8::Function> return_value;

10401025

v8::EscapableHandleScope scope(isolate);

1041-

v8::Local<v8::Object> cbdata =

1026+

v8::Local<v8::Value> cbdata =

10421027

v8impl::CreateFunctionCallbackData(env, cb, callback_data);

1043102810441029

RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure);

@@ -1078,7 +1063,7 @@ napi_status napi_define_class(napi_env env,

10781063

v8::Isolate* isolate = env->isolate;

1079106410801065

v8::EscapableHandleScope scope(isolate);

1081-

v8::Local<v8::Object> cbdata =

1066+

v8::Local<v8::Value> cbdata =

10821067

v8impl::CreateFunctionCallbackData(env, constructor, callback_data);

1083106810841069

RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure);

@@ -1114,7 +1099,7 @@ napi_status napi_define_class(napi_env env,

11141099

// This code is similar to that in napi_define_properties(); the

11151100

// difference is it applies to a template instead of an object.

11161101

if (p->getter != nullptr || p->setter != nullptr) {

1117-

v8::Local<v8::Object> cbdata = v8impl::CreateAccessorCallbackData(

1102+

v8::Local<v8::Value> cbdata = v8impl::CreateAccessorCallbackData(

11181103

env, p->getter, p->setter, p->data);

1119110411201105

tpl->PrototypeTemplate()->SetAccessor(

@@ -1125,7 +1110,7 @@ napi_status napi_define_class(napi_env env,

11251110

v8::AccessControl::DEFAULT,

11261111

attributes);

11271112

} else if (p->method != nullptr) {

1128-

v8::Local<v8::Object> cbdata =

1113+

v8::Local<v8::Value> cbdata =

11291114

v8impl::CreateFunctionCallbackData(env, p->method, p->data);

1130111511311116

RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure);

@@ -1487,7 +1472,7 @@ napi_status napi_define_properties(napi_env env,

14871472

v8impl::V8PropertyAttributesFromDescriptor(p);

1488147314891474

if (p->getter != nullptr || p->setter != nullptr) {

1490-

v8::Local<v8::Object> cbdata = v8impl::CreateAccessorCallbackData(

1475+

v8::Local<v8::Value> cbdata = v8impl::CreateAccessorCallbackData(

14911476

env,

14921477

p->getter,

14931478

p->setter,

@@ -1506,7 +1491,7 @@ napi_status napi_define_properties(napi_env env,

15061491

return napi_set_last_error(env, napi_invalid_arg);

15071492

}

15081493

} else if (p->method != nullptr) {

1509-

v8::Local<v8::Object> cbdata =

1494+

v8::Local<v8::Value> cbdata =

15101495

v8impl::CreateFunctionCallbackData(env, p->method, p->data);

1511149615121497

RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure);