@@ -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())
3634
37- #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 {
491476
492477// === Function napi_callback wrapper =================================
493478
494- 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
496506
497- 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+ };
503518
504519//  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  }
539554
540555  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 ());
549561
550-     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 ] ;
553565
554566    napi_value result;
555567    NAPI_CALL_INTO_MODULE_THROW (env, result = cb (env, cbinfo_wrapper));
@@ -560,7 +572,7 @@ class CallbackWrapperBase : public CallbackWrapper {
560572  }
561573
562574  const  Info& _cbinfo;
563-   const  v8::Local<v8::Object> _cbdata ;
575+   CallbackBundle* _bundle ;
564576};
565577
566578class  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.
684696static 
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);
690706
691-   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}
706709
707710//  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.
710713static 
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);
737725
738-   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);
10431028
10441029  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 ;
10791064
10801065  v8::EscapableHandleScope scope (isolate);
1081-   v8::Local<v8::Object > cbdata =
1066+   v8::Local<v8::Value > cbdata =
10821067      v8impl::CreateFunctionCallbackData (env, constructor, callback_data);
10831068
10841069  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 );
11191104
11201105      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 );
11301115
11311116      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);
14881473
14891474    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 );
15111496
15121497      RETURN_STATUS_IF_FALSE (env, !cbdata.IsEmpty (), napi_generic_failure);
0 commit comments