-
-
Notifications
You must be signed in to change notification settings - Fork 33.9k
node-api: use v-table to reverse module dependencies #60916
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
base: main
Are you sure you want to change the base?
Conversation
|
Review requested:
|
|
From the PR description this seems super valuable and a great built-in alternative to the weak-node-api library we've been working on to add Node-API support to React Native. As such, I'd be happy for us to adopt this approach over the stuff we have now 👍 The limitation around the module being bound to a single runtime, might not be an issue as a multi-runtime host can inject functions that deal with that internally. I'm left wondering how (if at all) add-ons which are statically linked into the process are affected by this proposal? I guess not at all, as they can still register themselves and call into the global Node-API functions resolved at link time. |
|
nice! node-api symbol visibility has been a pain for us in deno so we'd love to adopt this approach as well. |
| NULL, \ | ||
| {0}, \ | ||
| }; \ | ||
| NAPI_C_CTOR(_register_##modname) { napi_module_register(&_module); } \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should still support addons compiled with legacy Node-API headers. Could this new v-table approach be opt-in?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, we should make it opt-in to start with and play with it a bit.
If it works well, then we can promote it to be default at some point.
I considered to use the NAPI_EXPERIMENTAL, but it seems it is not the right approach since we use the NAPI_EXPERIMENTAL for new Node-API functions, and the module loading is an orthogonal process. Thus, a special opt-in flag would be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added a new macro NODE_API_MODULE_USE_VTABLE. It is currently used only by two tests.
| #define NAPI_NO_RETURN | ||
| #endif | ||
|
|
||
| // Used by deprecated registration method napi_module_register. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For anyone wondering, this was moved into the node_api_types.h.
|
There might be a potential a potential coordination problem, where the addon tries to initialize itself before the vtable gets injected (set) by the host. I don't see that as an issue when the addon relies on "symbol based" module registration, since the host can ensure to call the initialize function after setting the vtable, but how about an addon trying to call |
these should probably be exclusive modes, so napi_module_register wouldn't be called, or could only be called from within the host call to the inject function. |
src/node_api.cc
Outdated
|
|
||
| #endif | ||
|
|
||
| node_api_vtable g_vtable = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be const?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! I will fix it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added const for the global variables related to the v-tables.
The I am going to add a conditional flag and restore the deleted test as @legendecas suggested. This test is using the |
Right, I would expect it too, but we should verify and test this scenario. |
|
The issues that I am facing on Mac and Linux are due to the use of real "C" compilers where the |
|
Nice!👍 In Lynx/PrimJS, we are currently using a similar vtable approach to address the needs of multi-runtime injection. However, our previous API was not fully aligned with the Node-API standard, which is a problem I have been working to fix recently. |
cf9b056 to
f3d584b
Compare
It is great to hear it! Any suggestions to improve are welcome. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #60916 +/- ##
==========================================
+ Coverage 88.52% 88.53% +0.01%
==========================================
Files 703 703
Lines 208396 208406 +10
Branches 40185 40184 -1
==========================================
+ Hits 184483 184513 +30
+ Misses 15918 15893 -25
- Partials 7995 8000 +5
🚀 New features to boost your workflow:
|
Disclaimer
Please consider this PR as a proof of concept and a discussion starter. We discussed this design a bit in the latest Node-API meeting with @legendecas and @KevinEady, and this PR provides the specific implementation details to visualize the new ideas.
The issue
The recent investigation of the issue nodejs/abi-stable-node#471 had shown that the current way how Node-API modules depend on API exposed by Node.js has a number of limitations and issues that are not simple to overcome especially if the modules are distributed as pre-built libraries.
To be specific we have the following picture:
.nodemodules are compiled with the delay loading dependency on thenode.exeprocess. Each modules is compiled with thewin_delay_load_hook.ccsource file where we define a global__pfnDliNotifyHook2that allows to bind to Node-API functions from current process if its name is different fromnode.exe, or from thelibnode.dll. If a module was compiled where such hook is not defined or cannot be defined, then such module cannot bind to Node-API functions for non-node.exeruntimes..nodeuses weak function binding that allows to bind to any function with the same name present in the process. This does not work on Android where the modules are required to have a strong binding.Word.exeuseslibnode.dlland React Native for Windows with Hermes JS VM, then it is not possible to specify the target runtime for a module.The proposed solution
The proposal is to reverse the dependency. Instead of
.nodeto depend on Node-API, we should make it to be the responsibility of the runtime to load the.nodemodule and to inject its API into the module. This way it does not matter what is the name of the process or the embedded runtime DLL name. The same pre-built module can be loaded from different JS runtimes such ashermes.dllorreact-native.dll. (Though, the module can be used only by one runtime at this point.)This PR shows how such injection can be implemented:
js_native_api_types.handnode_api_types.hdefine thenode_api_js_native_vtableandnode_api_module_vtablestructs with entries for all Node-API functions. They are sorted so that all new function pointers are added in the end of the struct. The main requirement is that we must never change the position of the entries except for the experimental functions.js_native_api.handnode_api.hare two sets of functions that can be used in different scearios independently.js_native_api_v8.ccandnode_api.ccinitialize the struct instances with all Node-API functions pointers.node_api.hchanges theNAPI_MODULE_INITmacro so that each.nodemodule defines globalconst node_api_module_vtable* g_node_api_module_vtableandconst node_api_js_native_vtable* g_node_api_js_native_vtablevariables and exports the newnode_api_module_set_vtable_v1function that is called from thenode_binding.ccto inject the Node-API v-tables.js_native_api.handnode_api.halso contain the implementation of all Node-API functions asstatic inlinefunctions that use the global variables. These functions become part of the.nodemodule after compilation. They do not exist when we compile Node.js code. The new macroNODE_API_MODULE_USE_VTABLEcontrols where the header files define Node-API function prototypes or the newstatic inlinefunctions.After this change all test modules compile and run without changes.
The
NODE_API_MODULE_USE_VTABLEis only used for two tests:node-api\1_hello_worldandjs-native-api\7_factory_wrap.When we look at the imports of these modules we do not see any imports from the
node.exe.It means that we can stop using the
win_delay_load_hook.ccand be able to load the pre-built.nodemodule from any runtime that supports Node-API.The idea to use a v-table for the API is not new. E.g. Java JNI API is also based on a v-table.