-
Notifications
You must be signed in to change notification settings - Fork 30k
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
src: compile native modules and their code cache in C++ #24221
Conversation
CI: https://ci.nodejs.org/job/node-test-pull-request/18401/ This supersedes #23837 cc @ryzokuken |
Also cc @bcoe to confirm
|
Last one had infra issues, resumed build: https://ci.nodejs.org/job/node-test-pull-request/18403/ |
Testing CI is green. Not sure why but there is a even bigger improvement on Linux when running workers with the extra requires. Benchmark CI results. Benchmark results
Significant impact
|
@joyeecheung #23941 got coverage being enabled early enough that missing coverage for internal modules is no longer an issue 👍 |
@bcoe Thanks, judging from https://coverage.nodejs.org/coverage-7cf56797dddc7a00/root/internal/bootstrap/loaders.js.html looks like it already works with the current approach, so I'll remove that part as a goal. |
This patch refactors out a part of NativeModule.prototype.compile (in JS land) into a C++ NativeModule class, this enables a couple of possibilities: 1. By moving the code to the C++ land, we have more opportunity to specialize the compilation process of the native modules (e.g. compilation options, code cache) that is orthogonal to how user land modules are compiled 2. We can reuse the code to compile bootstrappers and context fixers and enable them to be compiled with the code cache later, since they are not loaded by NativeModule in the JS land their caching must be done in C++. 3. Since there is no need to pass the static data to JS for compilation anymore, this enables us to use (std::map<std::string, const char*>) in the generated node_code_cache.cc and node_javascript.cc later, and scope every actual access to the source of native modules to a std::map lookup instead of a lookup on a v8::Object in dictionary mode. This patch also refactor the code cache generator and tests a bit and trace the `withCodeCache` and `withoutCodeCache` in a Set instead of an Array, and makes sure that all the cachable builtins are tested.
2d84ae0
to
56fb2ab
Compare
Benchmark results from the CI
@nodejs/process Can I get some review please? |
src/env.h
Outdated
V(native_module_code_cache_hash, v8::Object) \ | ||
V(native_module_column_offset, v8::Integer) \ | ||
V(native_module_line_offset, v8::Integer) \ | ||
V(native_module_parameters, v8::Array) \ |
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.
This doesn't seem to be used anywhere.
src/env.h
Outdated
V(native_module_parameters, v8::Array) \ | ||
V(native_module_source, v8::Object) \ | ||
V(native_module_source_hash, v8::Object) \ | ||
V(native_module_with_cache, v8::Set) \ |
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.
Nit:
V(native_module_with_cache, v8::Set) \ | |
V(native_modules_with_cache, v8::Set) \ |
src/node_native_module.cc
Outdated
env->set_native_module_code_cache_hash(native_module_code_cache_hash); | ||
|
||
env->set_native_module_column_offset(Integer::New(isolate, 0)); | ||
env->set_native_module_line_offset(Integer::New(isolate, 0)); |
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.
Any reason why we have these two variables rather than just hardcoding 0? Integer::New
takes next to no time anyway.
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 wasn't sure about the offset of bootstrappers - looks like they are also 0-offsetted, so hard coding should be fine.
} else { | ||
// The binary is configured with code cache. | ||
console.log('The binary is configured with code cache'); |
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.
console.log('The binary is configured with code cache'); | |
// The binary is configured with code cache. |
😉
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 prefer to see this when I run the tests by hand, just so that I don't miss anything because this test is very fail-safe. Do you have a particular reason why we can't console log here?
} | ||
|
||
CHECK(result->IsUint8Array()); | ||
Local<Uint8Array> code_cache = result.As<Uint8Array>(); |
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.
Replace this and the later ArrayBuffer::Contents
unwrapping with SPREAD_BUFFER_ARG
?
Lines 385 to 394 in 0603c0a
#define SPREAD_BUFFER_ARG(val, name) \ | |
CHECK((val)->IsArrayBufferView()); \ | |
v8::Local<v8::ArrayBufferView> name = (val).As<v8::ArrayBufferView>(); \ | |
v8::ArrayBuffer::Contents name##_c = name->Buffer()->GetContents(); \ | |
const size_t name##_offset = name->ByteOffset(); \ | |
const size_t name##_length = name->ByteLength(); \ | |
char* const name##_data = \ | |
static_cast<char*>(name##_c.Data()) + name##_offset; \ | |
if (name##_length > 0) \ | |
CHECK_NE(name##_data, nullptr); |
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.
The API takes uint8_t*
, there is no stopping you doing a reinterpret_cast
I guess, but is there any real benefit in using a macro to eliminate two lines and then reinterpret_cast
?
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.
Personally I prefer two lines without a reinterpret_cast
(definatly over a macro).
src/node_native_module.cc
Outdated
Isolate* isolate = env->isolate(); | ||
|
||
Local<String> source; | ||
Local<Uint8Array> code_cache; |
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.
Seems unused.
src/node_native_module.cc
Outdated
Local<Value> result; | ||
result = env->native_module_source()->Get(context, id).ToLocalChecked(); | ||
CHECK(result->IsString()); | ||
source = result.As<String>(); |
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.
Can we just declare Local<String> source
here?
src/node_native_module.cc
Outdated
Local<Uint8Array> code_cache; | ||
|
||
Local<Value> result; | ||
result = env->native_module_source()->Get(context, id).ToLocalChecked(); |
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.
Coalesce these two lines?
src/node_native_module.cc
Outdated
|
||
env->SetMethod(target, "compileFunction", NativeModule::CompileFunction); | ||
env->SetMethod(target, "compileCodeCache", NativeModule::CompileCodeCache); | ||
// internalBinding('native_module') should be forzen |
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.
nit:
// internalBinding('native_module') should be forzen | |
// internalBinding('native_module') should be frozen |
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.
🎉 🎉 🎉
Fixed some of the nits: https://ci.nodejs.org/job/node-test-pull-request/18596/ |
I plan to land this later today, in order to pursue 2 and 3 listed in the OP. |
V(trace_category_state_function, v8::Function) \ | ||
V(tty_constructor_template, v8::FunctionTemplate) \ | ||
V(udp_constructor_function, v8::Function) \ | ||
V(url_constructor_function, v8::Function) \ |
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.
Can we backport this part immediately after the PR lands, or split it into its own PR?
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 can try clang-format this in 10.x, not sure if it's necessary for 8.x
src/node_code_cache.h
Outdated
@@ -7,6 +7,8 @@ | |||
|
|||
namespace node { | |||
|
|||
extern bool native_module_has_code_cache; |
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.
Can this be const
, to clarify that it’s not global mutable state?
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.
Even constexpr
.
Actually it probably should move to .gyp... I need to keep this in mind
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.
@refack This is the one bit that we can generate without a chicken-and-egg problem...what what good does it do if we can't generate the actual static data along with 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.
Although, come to think of it, now that we refactored the native module compilation/code cache generation in C++, we may be able to solve that chicken-and-egg problem more easily...when I manage to migrate the map to a std::map, I believe we only have a dependency of a working v8 isolate to convert source code into code cache, that's easier to plug in than node..
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.
@refack This is the one bit that we can generate without a chicken-and-egg problem...what what good does it do if we can't generate the actual static data along with it?
I mean, GYP knows if the code cache is a stub or real (if node_code_cache_stub.cc
is included or generated/node_code_cache.cc
) - So it can inject #define HAS_REAL_CODE_CACHE 0 / 1
and we don't need this as code at all.
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.
P.S. we need to push for GCC7 and get if constexpr
that way we can write code that doesn't compile decided by const code values.
ScriptCompiler::CreateCodeCacheForFunction(fun)); | ||
CHECK_NE(cached_data, nullptr); | ||
char* data = | ||
reinterpret_cast<char*>(const_cast<uint8_t*>(cached_data->data)); |
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 don’t think you need to cast the const
away?
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 do...otherwise the compiler barks at me..
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.
Hm … my compiler (gcc 7.3.0) accepts this:
diff --git a/src/node_native_module.cc b/src/node_native_module.cc
index 50f9e3dc3a53..d6b938309d5c 100644
--- a/src/node_native_module.cc
+++ b/src/node_native_module.cc
@@ -262,8 +262,7 @@ Local<Value> NativeModule::Compile(Environment* env,
std::unique_ptr<ScriptCompiler::CachedData> cached_data(
ScriptCompiler::CreateCodeCacheForFunction(fun));
CHECK_NE(cached_data, nullptr);
- char* data =
- reinterpret_cast<char*>(const_cast<uint8_t*>(cached_data->data));
+ const char* data = reinterpret_cast<const char*>(cached_data->data);
// Since we have no API to create a buffer from a new'ed pointer,
// we will need to copy it - but this code path is only run by the
// tooling that generates the code cache to be bundled in the binary
So it sounds like a compiler bug? Maybe we could leave a comment so that people aren’t tempted to refactor the const_cast
away?
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.
Just FTR, mutating the value of cast-away-const is undefined behaviour, and in recent MSVS version, it will probably crash (definatly crash in a Debug build).
So we might want to consider memcpy this, or CL ustream to mark it mutable
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.
There is no mutation of the underlying data – the const_cast
is valid, if it is necessary.
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.
If const_cast
actually causes crashes when its valid, this is probably one of the last place we need to worry about :) (it's only invoked by code cache generator, which hopefully no-one but us uses)
Will it be simpler to upfront encode all strings as |
We still need to teach V8 whether its one byte or two byte when we need to convert them to external strings |
CI is green: somehow https://ci.nodejs.org/job/node-test-binary-windows/21540/ is having trouble notifying upstream.. |
Landed in bd765d6 |
This patch refactors out a part of NativeModule.prototype.compile (in JS land) into a C++ NativeModule class, this enables a couple of possibilities: 1. By moving the code to the C++ land, we have more opportunity to specialize the compilation process of the native modules (e.g. compilation options, code cache) that is orthogonal to how user land modules are compiled 2. We can reuse the code to compile bootstrappers and context fixers and enable them to be compiled with the code cache later, since they are not loaded by NativeModule in the JS land their caching must be done in C++. 3. Since there is no need to pass the static data to JS for compilation anymore, this enables us to use (std::map<std::string, const char*>) in the generated node_code_cache.cc and node_javascript.cc later, and scope every actual access to the source of native modules to a std::map lookup instead of a lookup on a v8::Object in dictionary mode. This patch also refactor the code cache generator and tests a bit and trace the `withCodeCache` and `withoutCodeCache` in a Set instead of an Array, and makes sure that all the cachable builtins are tested. PR-URL: #24221 Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
This patch refactors out a part of NativeModule.prototype.compile (in JS land) into a C++ NativeModule class, this enables a couple of possibilities: 1. By moving the code to the C++ land, we have more opportunity to specialize the compilation process of the native modules (e.g. compilation options, code cache) that is orthogonal to how user land modules are compiled 2. We can reuse the code to compile bootstrappers and context fixers and enable them to be compiled with the code cache later, since they are not loaded by NativeModule in the JS land their caching must be done in C++. 3. Since there is no need to pass the static data to JS for compilation anymore, this enables us to use (std::map<std::string, const char*>) in the generated node_code_cache.cc and node_javascript.cc later, and scope every actual access to the source of native modules to a std::map lookup instead of a lookup on a v8::Object in dictionary mode. This patch also refactor the code cache generator and tests a bit and trace the `withCodeCache` and `withoutCodeCache` in a Set instead of an Array, and makes sure that all the cachable builtins are tested. PR-URL: #24221 Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
Notes: to backport this change, these changes need to be backported first
And there are several changes not started by me but should to be backported if we want to do this properly without causing another bunch of conflicts |
And we cannot use |
@ryzokuken We can, but the underlying data is independent of any V8 isolate in nature, |
This PR contains two patch. The first one, a benchmark patch, is only here so that we can run the benchmark CI with more options. I have opened another PR for that patch: #24220
The second patch moves the native module compilation to C++.
Local benchmark results:
src: compile native modules and their code cache in C++
This patch refactors out a part of NativeModule.prototype.compile
(in JS land) into a C++ NativeModule class, this enables a
couple of possibilities:
to specialize the compilation process of the native modules
(e.g. compilation options, code cache) that is orthogonal to
how user land modules are compiled
fixers and enable them to be compiled with the code cache later,
since they are not loaded by NativeModule in the JS land their
caching must be done in C++.
compilation anymore, this enables us to use
(std::map<std::string, const char*>) in the generated
node_code_cache.cc and node_javascript.cc later, and scope
every actual access to the source of native modules to a
std::map lookup instead of a lookup on a v8::Object in
dictionary mode.
This patch also refactor the code cache generator and tests
a bit and trace the
withCodeCache
andwithoutCodeCache
in a Set instead of an Array, and makes sure that all the cachable
builtins are tested.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes