src: use STL containers instead of v8 values for static module data

Instead of putting the source code and the cache in v8::Objects,
put them in per-process std::maps. This has the following benefits:

- It's slightly lighter in weight compared to storing things on the
  v8 heap. Also it may be slightly faster since the preivous v8::Object
  is already in dictionary mode - though the difference is very small
  given the number of native modules is limited.
- The source and code cache generation templates are now much simpler
  since they just initialize static arrays and manipulate STL
  constructs.
- The static native module data can be accessed independently of any
  Environment or Isolate, and it's easy to look them up from the
  C++'s side.
- It's now impossible to mutate the source code used to compile
  native modules from the JS land since it's completely separate
  from the v8 heap. We can still get the constant strings from
  process.binding('natives') but that's all.

A few drive-by fixes:

- Remove DecorateErrorStack in LookupAndCompile - We don't need to
  capture the exception to decorate when we encounter
  errors during native module compilation, as those errors should be
  syntax errors and v8 is able to decorate them well. We use
  CompileFunctionInContext so there is no need to worry about
  wrappers either.
- The code cache could be rejected when node is started with v8 flags.
  Instead of aborting in that case, simply keep a record in the
  native_module_without_cache set.
- Refactor js2c.py a bit, reduce code duplication and inline Render()
  to make the one-byte/two-byte special treatment easier to read.

PR-URL: https://github.com/nodejs/node/pull/24384
Fixes: https://github.com/Remove
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Joyee Cheung 2018-11-14 22:38:12 +08:00
parent 092ab7a1d3
commit 7778c035a0
No known key found for this signature in database
GPG Key ID: 92B78A53C8303B8D
16 changed files with 460 additions and 422 deletions

View File

@ -9,11 +9,12 @@ const {
NativeModule
} = require('internal/bootstrap/loaders');
const {
source,
getSource,
compileCodeCache
} = internalBinding('native_module');
const { hasTracing } = process.binding('config');
const source = getSource();
const depsModule = Object.keys(source).filter(
(key) => NativeModule.isDepsModule(key) || key.startsWith('internal/deps')
);

View File

@ -410,7 +410,6 @@
'src/node_api.h',
'src/node_api_types.h',
'src/node_buffer.h',
'src/node_code_cache.h',
'src/node_constants.h',
'src/node_contextify.h',
'src/node_errors.h',
@ -418,7 +417,6 @@
'src/node_http2.h',
'src/node_http2_state.h',
'src/node_internals.h',
'src/node_javascript.h',
'src/node_messaging.h',
'src/node_mutex.h',
'src/node_native_module.h',
@ -430,6 +428,7 @@
'src/node_persistent.h',
'src/node_platform.h',
'src/node_root_certs.h',
'src/node_union_bytes.h',
'src/node_version.h',
'src/node_watchdog.h',
'src/node_revert.h',

View File

@ -1,13 +1,14 @@
#include "node_internals.h"
#include "async_wrap.h"
#include "v8-profiler.h"
#include "node_buffer.h"
#include "node_platform.h"
#include "node_file.h"
#include "node_context_data.h"
#include "node_file.h"
#include "node_internals.h"
#include "node_native_module.h"
#include "node_platform.h"
#include "node_worker.h"
#include "tracing/agent.h"
#include "tracing/traced_value.h"
#include "v8-profiler.h"
#include <stdio.h>
#include <algorithm>

View File

@ -29,13 +29,13 @@
#include "inspector_agent.h"
#endif
#include "handle_wrap.h"
#include "node.h"
#include "node_http2_state.h"
#include "node_options.h"
#include "req_wrap.h"
#include "util.h"
#include "uv.h"
#include "v8.h"
#include "node.h"
#include "node_options.h"
#include "node_http2_state.h"
#include <list>
#include <stdint.h>
@ -347,12 +347,6 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \
V(message_port, v8::Object) \
V(message_port_constructor_template, v8::FunctionTemplate) \
V(native_modules_code_cache, v8::Object) \
V(native_modules_code_cache_hash, v8::Object) \
V(native_modules_source, v8::Object) \
V(native_modules_source_hash, v8::Object) \
V(native_modules_with_cache, v8::Set) \
V(native_modules_without_cache, v8::Set) \
V(performance_entry_callback, v8::Function) \
V(performance_entry_template, v8::Function) \
V(pipe_constructor_template, v8::FunctionTemplate) \
@ -684,6 +678,9 @@ class Environment {
// List of id's that have been destroyed and need the destroy() cb called.
inline std::vector<double>* destroy_async_id_list();
std::set<std::string> native_modules_with_cache;
std::set<std::string> native_modules_without_cache;
std::unordered_multimap<int, loader::ModuleWrap*> hash_to_module_map;
std::unordered_map<uint32_t, loader::ModuleWrap*> id_to_module_map;
std::unordered_map<uint32_t, contextify::ContextifyScript*>

View File

@ -24,7 +24,6 @@
#include "node_context_data.h"
#include "node_errors.h"
#include "node_internals.h"
#include "node_javascript.h"
#include "node_native_module.h"
#include "node_perf.h"
#include "node_platform.h"
@ -130,7 +129,7 @@ typedef int mode_t;
namespace node {
using native_module::NativeModule;
using native_module::NativeModuleLoader;
using options_parser::kAllowedInEnvironment;
using options_parser::kDisallowedInEnvironment;
using v8::Array;
@ -212,7 +211,7 @@ double prog_start_time;
Mutex per_process_opts_mutex;
std::shared_ptr<PerProcessOptions> per_process_opts {
new PerProcessOptions() };
NativeModuleLoader per_process_loader;
static Mutex node_isolate_mutex;
static Isolate* node_isolate;
@ -1243,8 +1242,7 @@ static void GetInternalBinding(const FunctionCallbackInfo<Value>& args) {
Null(env->isolate())).FromJust());
DefineConstants(env->isolate(), exports);
} else if (!strcmp(*module_v, "natives")) {
exports = Object::New(env->isolate());
NativeModule::GetNatives(env, exports);
exports = per_process_loader.GetSourceObject(env->context());
} else {
return ThrowIfNoSuchModule(env, *module_v);
}
@ -1780,18 +1778,24 @@ void LoadEnvironment(Environment* env) {
// The bootstrapper scripts are lib/internal/bootstrap/loaders.js and
// lib/internal/bootstrap/node.js, each included as a static C string
// defined in node_javascript.h, generated in node_javascript.cc by
// node_js2c.
// generated in node_javascript.cc by node_js2c.
// TODO(joyeecheung): use NativeModule::Compile
// TODO(joyeecheung): use NativeModuleLoader::Compile
// We duplicate the string literals here since once we refactor the bootstrap
// compilation out to NativeModuleLoader none of this is going to matter
Isolate* isolate = env->isolate();
Local<String> loaders_name =
FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/loaders.js");
FIXED_ONE_BYTE_STRING(isolate, "internal/bootstrap/loaders.js");
Local<String> loaders_source =
per_process_loader.GetSource(isolate, "internal/bootstrap/loaders");
MaybeLocal<Function> loaders_bootstrapper =
GetBootstrapper(env, LoadersBootstrapperSource(env), loaders_name);
GetBootstrapper(env, loaders_source, loaders_name);
Local<String> node_name =
FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/node.js");
FIXED_ONE_BYTE_STRING(isolate, "internal/bootstrap/node.js");
Local<String> node_source =
per_process_loader.GetSource(isolate, "internal/bootstrap/node");
MaybeLocal<Function> node_bootstrapper =
GetBootstrapper(env, NodeBootstrapperSource(env), node_name);
GetBootstrapper(env, node_source, node_name);
if (loaders_bootstrapper.IsEmpty() || node_bootstrapper.IsEmpty()) {
// Execution was interrupted.
@ -1843,8 +1847,6 @@ void LoadEnvironment(Environment* env) {
env->options()->debug_options->break_node_first_line)
};
NativeModule::LoadBindings(env);
// Bootstrap internal loaders
Local<Value> bootstrapped_loaders;
if (!ExecuteBootstrapper(env, loaders_bootstrapper.ToLocalChecked(),
@ -2485,7 +2487,6 @@ void FreePlatform(MultiIsolatePlatform* platform) {
delete platform;
}
Local<Context> NewContext(Isolate* isolate,
Local<ObjectTemplate> object_template) {
auto context = Context::New(isolate, nullptr, object_template);
@ -2499,8 +2500,9 @@ Local<Context> NewContext(Isolate* isolate,
// Run lib/internal/per_context.js
Context::Scope context_scope(context);
// TODO(joyeecheung): use NativeModule::Compile
Local<String> per_context = NodePerContextSource(isolate);
// TODO(joyeecheung): use NativeModuleLoader::Compile
Local<String> per_context =
per_process_loader.GetSource(isolate, "internal/per_context");
ScriptCompiler::Source per_context_src(per_context, nullptr);
Local<Script> s = ScriptCompiler::Compile(
context,

View File

@ -1,19 +0,0 @@
#ifndef SRC_NODE_CODE_CACHE_H_
#define SRC_NODE_CODE_CACHE_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "node_internals.h"
namespace node {
extern const bool native_module_has_code_cache;
void DefineCodeCache(Environment* env, v8::Local<v8::Object> target);
void DefineCodeCacheHash(Environment* env, v8::Local<v8::Object> target);
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_NODE_CODE_CACHE_H_

View File

@ -1,23 +1,19 @@
#include "node_code_cache.h"
#include "node_native_module.h"
// This is supposed to be generated by tools/generate_code_cache.js
// The stub here is used when configure is run without `--code-cache-path`
namespace node {
namespace native_module {
const bool native_module_has_code_cache = false;
// The generated source code would insert <std::string, UnionString> pairs
// into native_module_loader.code_cache_.
void NativeModuleLoader::LoadCodeCache() {}
void DefineCodeCache(Environment* env, v8::Local<v8::Object> target) {
// When we do not produce code cache for builtin modules,
// `internalBinding('code_cache')` returns an empty object
// (here as `target`) so this is a noop.
}
void DefineCodeCacheHash(Environment* env, v8::Local<v8::Object> target) {
// When we do not produce code cache for builtin modules,
// `internalBinding('code_cache_hash')` returns an empty object
// (here as `target`) so this is a noop.
}
// The generated source code would instert <std::string, std::string> pairs
// into native_module_loader.code_cache_hash_.
void NativeModuleLoader::LoadCodeCacheHash() {}
} // namespace native_module
} // namespace node

View File

@ -171,6 +171,10 @@ struct sockaddr;
namespace node {
namespace native_module {
class NativeModuleLoader;
}
extern Mutex process_mutex;
extern Mutex environ_mutex;
@ -179,6 +183,7 @@ extern bool v8_initialized;
extern Mutex per_process_opts_mutex;
extern std::shared_ptr<PerProcessOptions> per_process_opts;
extern native_module::NativeModuleLoader per_process_loader;
// Forward declaration
class Environment;

View File

@ -1,41 +0,0 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef SRC_NODE_JAVASCRIPT_H_
#define SRC_NODE_JAVASCRIPT_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "node_internals.h"
namespace node {
void DefineJavaScript(Environment* env, v8::Local<v8::Object> target);
void DefineJavaScriptHash(Environment* env, v8::Local<v8::Object> target);
v8::Local<v8::String> NodePerContextSource(v8::Isolate* isolate);
v8::Local<v8::String> LoadersBootstrapperSource(Environment* env);
v8::Local<v8::String> NodeBootstrapperSource(Environment* env);
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_NODE_JAVASCRIPT_H_

View File

@ -1,13 +1,13 @@
#include "node_native_module.h"
#include "node_code_cache.h"
#include "node_errors.h"
#include "node_javascript.h"
#include "node_internals.h"
namespace node {
namespace native_module {
using v8::Array;
using v8::ArrayBuffer;
using v8::ArrayBufferCreationMode;
using v8::Context;
using v8::EscapableHandleScope;
using v8::Function;
@ -25,94 +25,107 @@ using v8::ScriptCompiler;
using v8::ScriptOrigin;
using v8::Set;
using v8::String;
using v8::TryCatch;
using v8::Uint8Array;
using v8::Value;
void NativeModule::GetNatives(Environment* env, Local<Object> exports) {
DefineJavaScript(env, exports);
// TODO(joyeecheung): make these more general and put them into util.h
Local<Object> MapToObject(Local<Context> context,
const NativeModuleRecordMap& in) {
Isolate* isolate = context->GetIsolate();
Local<Object> out = Object::New(isolate);
for (auto const& x : in) {
Local<String> key = OneByteString(isolate, x.first.c_str(), x.first.size());
out->Set(context, key, x.second.ToStringChecked(isolate)).FromJust();
}
return out;
}
void NativeModule::LoadBindings(Environment* env) {
// TODO(joyeecheung): put the static values into a
// std::map<std::string, const uint8_t*> instead of a v8::Object,
// because here they are only looked up from the C++ side
// (except in process.binding('natives') which we don't use)
// so there is little value to put them in a v8::Object upfront.
// Moreover, a std::map lookup should be faster than a lookup on
// an V8 Object in dictionary mode.
Local<Set> ToJsSet(Local<Context> context,
const std::set<std::string>& in) {
Isolate* isolate = context->GetIsolate();
Local<Set> out = Set::New(isolate);
for (auto const& x : in) {
out->Add(context, OneByteString(isolate, x.c_str(), x.size()))
.ToLocalChecked();
}
return out;
}
void NativeModuleLoader::GetCacheUsage(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Isolate* isolate = env->isolate();
Local<Context> context = env->context();
Local<Value> null = Null(isolate);
Local<Object> native_modules_source = Object::New(isolate);
CHECK(native_modules_source->SetPrototype(context, null).FromJust());
DefineJavaScript(env, native_modules_source);
native_modules_source->SetIntegrityLevel(context, IntegrityLevel::kFrozen)
Local<Object> result = Object::New(isolate);
result
->Set(env->context(),
OneByteString(isolate, "compiledWithCache"),
ToJsSet(context, env->native_modules_with_cache))
.FromJust();
env->set_native_modules_source(native_modules_source);
Local<Object> native_modules_source_hash = Object::New(isolate);
CHECK(native_modules_source_hash->SetPrototype(context, null).FromJust());
DefineJavaScriptHash(env, native_modules_source_hash);
native_modules_source_hash
->SetIntegrityLevel(context, IntegrityLevel::kFrozen)
result
->Set(env->context(),
OneByteString(isolate, "compiledWithoutCache"),
ToJsSet(context, env->native_modules_without_cache))
.FromJust();
env->set_native_modules_source_hash(native_modules_source_hash);
Local<Object> native_modules_code_cache = Object::New(isolate);
CHECK(native_modules_code_cache->SetPrototype(context, null).FromJust());
DefineCodeCache(env, native_modules_code_cache);
native_modules_code_cache->SetIntegrityLevel(context, IntegrityLevel::kFrozen)
.FromJust();
env->set_native_modules_code_cache(native_modules_code_cache);
Local<Object> native_modules_code_cache_hash = Object::New(isolate);
CHECK(native_modules_code_cache_hash->SetPrototype(context, null).FromJust());
DefineCodeCacheHash(env, native_modules_code_cache_hash);
native_modules_code_cache_hash
->SetIntegrityLevel(context, IntegrityLevel::kFrozen)
.FromJust();
env->set_native_modules_code_cache_hash(native_modules_code_cache_hash);
env->set_native_modules_with_cache(Set::New(isolate));
env->set_native_modules_without_cache(Set::New(isolate));
args.GetReturnValue().Set(result);
}
void NativeModule::CompileCodeCache(const FunctionCallbackInfo<Value>& args) {
void NativeModuleLoader::GetSourceObject(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
args.GetReturnValue().Set(per_process_loader.GetSourceObject(env->context()));
}
Local<Object> NativeModuleLoader::GetSourceObject(
Local<Context> context) const {
return MapToObject(context, source_);
}
Local<String> NativeModuleLoader::GetSource(Isolate* isolate,
const char* id) const {
const auto it = source_.find(id);
CHECK_NE(it, source_.end());
return it->second.ToStringChecked(isolate);
}
NativeModuleLoader::NativeModuleLoader() {
LoadJavaScriptSource();
LoadJavaScriptHash();
LoadCodeCache();
LoadCodeCacheHash();
}
void NativeModuleLoader::CompileCodeCache(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsString());
Local<String> id = args[0].As<String>();
node::Utf8Value id(env->isolate(), args[0].As<String>());
Local<Value> result = CompileAsModule(env, id, true);
// TODO(joyeecheung): allow compiling cache for bootstrapper by
// switching on id
Local<Value> result = CompileAsModule(env, *id, true);
if (!result.IsEmpty()) {
args.GetReturnValue().Set(result);
}
}
void NativeModule::CompileFunction(const FunctionCallbackInfo<Value>& args) {
void NativeModuleLoader::CompileFunction(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsString());
Local<String> id = args[0].As<String>();
Local<Value> result = CompileAsModule(env, id, false);
node::Utf8Value id(env->isolate(), args[0].As<String>());
Local<Value> result = CompileAsModule(env, *id, false);
if (!result.IsEmpty()) {
args.GetReturnValue().Set(result);
}
}
Local<Value> NativeModule::CompileAsModule(Environment* env,
Local<String> id,
bool produce_code_cache) {
Local<String> parameters[] = {env->exports_string(),
env->require_string(),
env->module_string(),
env->process_string(),
env->internal_binding_string()};
return Compile(
env, id, parameters, arraysize(parameters), produce_code_cache);
Local<Value> NativeModuleLoader::CompileAsModule(Environment* env,
const char* id,
bool produce_code_cache) {
return per_process_loader.LookupAndCompile(
env->context(), id, produce_code_cache, env);
}
// Currently V8 only checks that the length of the source code is the
@ -139,30 +152,25 @@ Local<Value> NativeModule::CompileAsModule(Environment* env,
// early in the bootstrap process so it should be easy to detect and fix.
// Returns nullptr if there is no code cache corresponding to the id
ScriptCompiler::CachedData* GetCachedData(Environment* env, Local<String> id) {
HandleScope scope(env->isolate());
Local<Context> context = env->context();
Local<Value> result =
env->native_modules_code_cache()->Get(context, id).ToLocalChecked();
ScriptCompiler::CachedData* NativeModuleLoader::GetCachedData(
const char* id) const {
const auto it = per_process_loader.code_cache_.find(id);
// This could be false if the module cannot be cached somehow.
// See lib/internal/bootstrap/cache.js on the modules that cannot be cached
if (result->IsUndefined()) {
if (it == per_process_loader.code_cache_.end()) {
return nullptr;
}
CHECK(result->IsUint8Array());
Local<Uint8Array> code_cache = result.As<Uint8Array>();
const uint8_t* code_cache_value = it->second.one_bytes_data();
size_t code_cache_length = it->second.length();
result =
env->native_modules_code_cache_hash()->Get(context, id).ToLocalChecked();
CHECK(result->IsString());
Local<String> code_cache_hash = result.As<String>();
const auto it2 = code_cache_hash_.find(id);
CHECK_NE(it2, code_cache_hash_.end());
const std::string& code_cache_hash_value = it2->second;
result =
env->native_modules_source_hash()->Get(context, id).ToLocalChecked();
CHECK(result->IsString());
Local<String> source_hash = result.As<String>();
const auto it3 = source_hash_.find(id);
CHECK_NE(it3, source_hash_.end());
const std::string& source_hash_value = it3->second;
// It may fail when any of the inputs of the `node_js2c` target in
// node.gyp is modified but the tools/generate_code_cache.js
@ -170,33 +178,26 @@ ScriptCompiler::CachedData* GetCachedData(Environment* env, Local<String> id) {
// FIXME(joyeecheung): Figure out how to resolve the dependency issue.
// When the code cache was introduced we were at a point where refactoring
// node.gyp may not be worth the effort.
CHECK(code_cache_hash->StrictEquals(source_hash));
CHECK_EQ(code_cache_hash_value, source_hash_value);
ArrayBuffer::Contents contents = code_cache->Buffer()->GetContents();
uint8_t* data = static_cast<uint8_t*>(contents.Data());
return new ScriptCompiler::CachedData(data + code_cache->ByteOffset(),
code_cache->ByteLength());
return new ScriptCompiler::CachedData(code_cache_value, code_cache_length);
}
// Returns Local<Function> of the compiled module if produce_code_cache
// is false (we are only compiling the function).
// Otherwise return a Local<Object> containing the cache.
Local<Value> NativeModule::Compile(Environment* env,
Local<String> id,
Local<String> parameters[],
size_t parameters_count,
bool produce_code_cache) {
EscapableHandleScope scope(env->isolate());
Local<Context> context = env->context();
Isolate* isolate = env->isolate();
Local<Value> NativeModuleLoader::LookupAndCompile(Local<Context> context,
const char* id,
bool produce_code_cache,
Environment* optional_env) {
Isolate* isolate = context->GetIsolate();
EscapableHandleScope scope(isolate);
Local<Value> result =
env->native_modules_source()->Get(context, id).ToLocalChecked();
CHECK(result->IsString());
Local<String> source = result.As<String>();
Local<String> source = GetSource(isolate, id);
std::string filename_s = id + std::string(".js");
Local<String> filename =
String::Concat(isolate, id, FIXED_ONE_BYTE_STRING(isolate, ".js"));
OneByteString(isolate, filename_s.c_str(), filename_s.size());
Local<Integer> line_offset = Integer::New(isolate, 0);
Local<Integer> column_offset = Integer::New(isolate, 0);
ScriptOrigin origin(filename, line_offset, column_offset);
@ -208,8 +209,8 @@ Local<Value> NativeModule::Compile(Environment* env,
// built with them.
// 2. If we are generating code cache for tools/general_code_cache.js, we
// are not going to use any cache ourselves.
if (native_module_has_code_cache && !produce_code_cache) {
cached_data = GetCachedData(env, id);
if (has_code_cache_ && !produce_code_cache) {
cached_data = GetCachedData(id);
if (cached_data != nullptr) {
use_cache = true;
}
@ -226,35 +227,55 @@ Local<Value> NativeModule::Compile(Environment* env,
options = ScriptCompiler::kNoCompileOptions;
}
MaybeLocal<Function> maybe_fun =
ScriptCompiler::CompileFunctionInContext(context,
&script_source,
parameters_count,
parameters,
0,
nullptr,
options);
MaybeLocal<Function> maybe_fun;
// Currently we assume if Environment is ready, then we must be compiling
// native modules instead of bootstrappers.
if (optional_env != nullptr) {
Local<String> parameters[] = {optional_env->exports_string(),
optional_env->require_string(),
optional_env->module_string(),
optional_env->process_string(),
optional_env->internal_binding_string()};
maybe_fun = ScriptCompiler::CompileFunctionInContext(context,
&script_source,
arraysize(parameters),
parameters,
0,
nullptr,
options);
} else {
// Until we migrate bootstrappers compilations here this is unreachable
// TODO(joyeecheung): it should be possible to generate the argument names
// from some special comments for the bootstrapper case.
// Note that for bootstrappers we may not be able to get the argument
// names as env->some_string() because we might be compiling before
// those strings are initialized.
UNREACHABLE();
}
TryCatch try_catch(isolate);
Local<Function> fun;
// This could fail when there are early errors in the native modules,
// e.g. the syntax errors
if (maybe_fun.IsEmpty() || !maybe_fun.ToLocal(&fun)) {
DecorateErrorStack(env, try_catch);
try_catch.ReThrow();
// In the case of early errors, v8 is already capable of
// decorating the stack for us - note that we use CompileFunctionInContext
// so there is no need to worry about wrappers.
return scope.Escape(Local<Value>());
}
if (use_cache) {
// If the cache is rejected, something must be wrong with the build
// and we should just crash.
CHECK(!script_source.GetCachedData()->rejected);
if (env->native_modules_with_cache()->Add(context, id).IsEmpty()) {
return scope.Escape(Local<Value>());
if (optional_env != nullptr) {
// This could happen when Node is run with any v8 flag, but
// the cache is not generated with one
if (script_source.GetCachedData()->rejected) {
optional_env->native_modules_without_cache.insert(id);
} else {
optional_env->native_modules_with_cache.insert(id);
}
}
} else {
if (env->native_modules_without_cache()->Add(context, id).IsEmpty()) {
return scope.Escape(Local<Value>());
if (optional_env != nullptr) {
optional_env->native_modules_without_cache.insert(id);
}
}
@ -262,59 +283,38 @@ 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));
// 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
size_t cached_data_length = cached_data->length;
// Since we have no special allocator to create an ArrayBuffer
// 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 should be fine.
Local<Object> buf =
Buffer::Copy(env, data, cached_data->length).ToLocalChecked();
return scope.Escape(buf);
MallocedBuffer<uint8_t> copied(cached_data->length);
memcpy(copied.data, cached_data->data, cached_data_length);
Local<ArrayBuffer> buf =
ArrayBuffer::New(isolate,
copied.release(),
cached_data_length,
ArrayBufferCreationMode::kInternalized);
return scope.Escape(Uint8Array::New(buf, 0, cached_data_length));
} else {
return scope.Escape(fun);
}
}
void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context) {
void NativeModuleLoader::Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context) {
Environment* env = Environment::GetCurrent(context);
target
->Set(context,
FIXED_ONE_BYTE_STRING(env->isolate(), "source"),
env->native_modules_source())
.FromJust();
target
->Set(context,
FIXED_ONE_BYTE_STRING(env->isolate(), "sourceHash"),
env->native_modules_source_hash())
.FromJust();
target
->Set(context,
FIXED_ONE_BYTE_STRING(env->isolate(), "codeCache"),
env->native_modules_code_cache())
.FromJust();
target
->Set(context,
FIXED_ONE_BYTE_STRING(env->isolate(), "codeCacheHash"),
env->native_modules_code_cache_hash())
.FromJust();
target
->Set(context,
FIXED_ONE_BYTE_STRING(env->isolate(), "compiledWithCache"),
env->native_modules_with_cache())
.FromJust();
target
->Set(context,
FIXED_ONE_BYTE_STRING(env->isolate(), "compiledWithoutCache"),
env->native_modules_without_cache())
.FromJust();
env->SetMethod(target, "compileFunction", NativeModule::CompileFunction);
env->SetMethod(target, "compileCodeCache", NativeModule::CompileCodeCache);
env->SetMethod(
target, "getSource", NativeModuleLoader::GetSourceObject);
env->SetMethod(
target, "getCacheUsage", NativeModuleLoader::GetCacheUsage);
env->SetMethod(
target, "compileFunction", NativeModuleLoader::CompileFunction);
env->SetMethod(
target, "compileCodeCache", NativeModuleLoader::CompileCodeCache);
// internalBinding('native_module') should be frozen
target->SetIntegrityLevel(context, IntegrityLevel::kFrozen).FromJust();
}
@ -322,5 +322,5 @@ void Initialize(Local<Object> target,
} // namespace native_module
} // namespace node
NODE_MODULE_CONTEXT_AWARE_INTERNAL(native_module,
node::native_module::Initialize)
NODE_MODULE_CONTEXT_AWARE_INTERNAL(
native_module, node::native_module::NativeModuleLoader::Initialize)

View File

@ -3,34 +3,68 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "node_internals.h"
#include <map>
#include <set>
#include <string>
#include "env.h"
#include "node_union_bytes.h"
#include "v8.h"
namespace node {
namespace native_module {
// The native (C++) side of the native module compilation.
using NativeModuleRecordMap = std::map<std::string, UnionBytes>;
using NativeModuleHashMap = std::map<std::string, std::string>;
class NativeModule {
// The native (C++) side of the native module compilation.
// This class should not depend on Environment
class NativeModuleLoader {
public:
// For legacy process.binding('natives') which is mutable
static void GetNatives(Environment* env, v8::Local<v8::Object> exports);
// Loads the static JavaScript source code and the cache into Environment
static void LoadBindings(Environment* env);
NativeModuleLoader();
static void Initialize(v8::Local<v8::Object> target,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context);
v8::Local<v8::Object> GetSourceObject(v8::Local<v8::Context> context) const;
v8::Local<v8::String> GetSource(v8::Isolate* isolate, const char* id) const;
private:
static void GetCacheUsage(const v8::FunctionCallbackInfo<v8::Value>& args);
// For legacy process.binding('natives') which is mutable, and for
// internalBinding('native_module').source for internal use
static void GetSourceObject(const v8::FunctionCallbackInfo<v8::Value>& args);
// Compile code cache for a specific native module
static void CompileCodeCache(const v8::FunctionCallbackInfo<v8::Value>& args);
// Compile a specific native module as a function
static void CompileFunction(const v8::FunctionCallbackInfo<v8::Value>& args);
private:
// Generated by tools/js2c.py as node_javascript.cc
void LoadJavaScriptSource(); // Loads data into source_
void LoadJavaScriptHash(); // Loads data into source_hash_
// Generated by tools/generate_code_cache.js as node_code_cache.cc when
// the build is configured with --code-cache-path=.... They are noops
// in node_code_cache_stub.cc
void LoadCodeCache(); // Loads data into code_cache_
void LoadCodeCacheHash(); // Loads data into code_cache_hash_
v8::ScriptCompiler::CachedData* GetCachedData(const char* id) const;
static v8::Local<v8::Value> CompileAsModule(Environment* env,
v8::Local<v8::String> id,
const char* id,
bool produce_code_cache);
// TODO(joyeecheung): make this public and reuse it to compile bootstrappers
static v8::Local<v8::Value> Compile(Environment* env,
v8::Local<v8::String> id,
v8::Local<v8::String> parameters[],
size_t parameters_count,
bool produce_code_cache);
// TODO(joyeecheung): make this public and reuse it to compile bootstrappers.
// For bootstrappers optional_env may be a nullptr.
// This method magically knows what parameter it should pass to
// the function to be compiled.
v8::Local<v8::Value> LookupAndCompile(v8::Local<v8::Context> context,
const char* id,
bool produce_code_cache,
Environment* optional_env);
bool has_code_cache_ = false;
NativeModuleRecordMap source_;
NativeModuleRecordMap code_cache_;
NativeModuleHashMap source_hash_;
NativeModuleHashMap code_cache_hash_;
};
} // namespace native_module

93
src/node_union_bytes.h Normal file
View File

@ -0,0 +1,93 @@
#ifndef SRC_NODE_UNION_BYTES_H_
#define SRC_NODE_UNION_BYTES_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
// A union of const uint8_t* or const uint16_t* data that can be
// turned into external v8::String when given an isolate.
#include "env.h"
#include "v8.h"
namespace node {
class NonOwningExternalOneByteResource
: public v8::String::ExternalOneByteStringResource {
public:
explicit NonOwningExternalOneByteResource(const uint8_t* data, size_t length)
: data_(data), length_(length) {}
~NonOwningExternalOneByteResource() override = default;
const char* data() const override {
return reinterpret_cast<const char*>(data_);
}
size_t length() const override { return length_; }
private:
const uint8_t* data_;
size_t length_;
DISALLOW_COPY_AND_ASSIGN(NonOwningExternalOneByteResource);
};
class NonOwningExternalTwoByteResource
: public v8::String::ExternalStringResource {
public:
explicit NonOwningExternalTwoByteResource(const uint16_t* data, size_t length)
: data_(data), length_(length) {}
~NonOwningExternalTwoByteResource() override = default;
const uint16_t* data() const override { return data_; }
size_t length() const override { return length_; }
private:
const uint16_t* data_;
size_t length_;
DISALLOW_COPY_AND_ASSIGN(NonOwningExternalTwoByteResource);
};
// Similar to a v8::String, but it's independent from Isolates
// and can be materialized in Isolates as external Strings
// via ToStringChecked. The data pointers are owned by the caller.
class UnionBytes {
public:
UnionBytes(const uint16_t* data, size_t length)
: is_one_byte_(false), two_bytes_(data), length_(length) {}
UnionBytes(const uint8_t* data, size_t length)
: is_one_byte_(true), one_bytes_(data), length_(length) {}
bool is_one_byte() const { return is_one_byte_; }
const uint16_t* two_bytes_data() const {
CHECK(!is_one_byte_);
return two_bytes_;
}
const uint8_t* one_bytes_data() const {
CHECK(is_one_byte_);
return one_bytes_;
}
v8::Local<v8::String> ToStringChecked(v8::Isolate* isolate) const {
if (is_one_byte_) {
NonOwningExternalOneByteResource* source =
new NonOwningExternalOneByteResource(one_bytes_, length_);
return v8::String::NewExternalOneByte(isolate, source).ToLocalChecked();
} else {
NonOwningExternalTwoByteResource* source =
new NonOwningExternalTwoByteResource(two_bytes_, length_);
return v8::String::NewExternalTwoByte(isolate, source).ToLocalChecked();
}
}
size_t length() { return length_; }
private:
bool is_one_byte_;
union {
const uint8_t* one_bytes_;
const uint16_t* two_bytes_;
};
size_t length_;
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_NODE_UNION_BYTES_H_

View File

@ -30,10 +30,10 @@ if (child.status !== 0) {
}
// Verifies that:
// - node::DefineCodeCache()
// - node::DefineCodeCacheHash()
// - node::LoadCodeCache()
// - node::LoadCodeCacheHash()
// are defined in the generated code.
// See src/node_code_cache_stub.cc for explanations.
// See src/node_native_module.h for explanations.
const rl = readline.createInterface({
input: fs.createReadStream(dest),
@ -44,10 +44,10 @@ let hasCacheDef = false;
let hasHashDef = false;
rl.on('line', common.mustCallAtLeast((line) => {
if (line.includes('DefineCodeCache(')) {
if (line.includes('LoadCodeCache(')) {
hasCacheDef = true;
}
if (line.includes('DefineCodeCacheHash(')) {
if (line.includes('LoadCodeCacheHash(')) {
hasHashDef = true;
}
}, 2));

View File

@ -19,8 +19,7 @@ const {
internalBinding
} = require('internal/test/binding');
const {
compiledWithoutCache,
compiledWithCache
getCacheUsage
} = internalBinding('native_module');
for (const key of cachableBuiltins) {
@ -30,6 +29,12 @@ for (const key of cachableBuiltins) {
require(key);
}
// The computation has to be delayed until we have done loading modules
const {
compiledWithoutCache,
compiledWithCache
} = getCacheUsage();
const loadedModules = process.moduleLoadList
.filter((m) => m.startsWith('NativeModule'))
.map((m) => m.replace('NativeModule ', ''));
@ -39,7 +44,11 @@ const loadedModules = process.moduleLoadList
if (process.config.variables.node_code_cache_path === undefined) {
console.log('The binary is not configured with code cache');
assert.deepStrictEqual(compiledWithCache, new Set());
assert.deepStrictEqual(compiledWithoutCache, new Set(loadedModules));
for (const key of loadedModules) {
assert(compiledWithoutCache.has(key),
`"${key}" should've been compiled without code cache`);
}
} else {
console.log('The binary is configured with code cache');
assert.strictEqual(

View File

@ -13,6 +13,12 @@ const {
cachableBuiltins
} = require('internal/bootstrap/cache');
const {
types: {
isUint8Array
}
} = require('util');
function hash(str) {
if (process.versions.openssl) {
return require('crypto').createHash('sha256').update(str).digest('hex');
@ -52,29 +58,25 @@ function formatSize(num) {
* initializers of the code cache.
*
* @param {string} key ID of the builtin module
* @param {Buffer} cache Code cache of the builtin module
* @param {Uint8Array} cache Code cache of the builtin module
* @return { definition: string, initializer: string }
*/
function getInitalizer(key, cache) {
const defName = key.replace(/\//g, '_').replace(/-/g, '_');
const definition = `static uint8_t ${defName}_raw[] = {\n` +
const defName = `${key.replace(/\//g, '_').replace(/-/g, '_')}_raw`;
const definition = `static const uint8_t ${defName}[] = {\n` +
`${cache.join(',')}\n};`;
const source = getSource(key);
const sourceHash = hash(source);
const initializer = `
v8::Local<v8::ArrayBuffer> ${defName}_ab =
v8::ArrayBuffer::New(isolate, ${defName}_raw, ${cache.length});
v8::Local<v8::Uint8Array> ${defName}_array =
v8::Uint8Array::New(${defName}_ab, 0, ${cache.length});
target->Set(context,
FIXED_ONE_BYTE_STRING(isolate, "${key}"),
${defName}_array).FromJust();
`;
const hashIntializer = `
target->Set(context,
FIXED_ONE_BYTE_STRING(isolate, "${key}"),
OneByteString(isolate, "${sourceHash}")).FromJust();
`;
const initializer =
'code_cache_.emplace(\n' +
` "${key}",\n` +
` UnionBytes(${defName}, arraysize(${defName}))\n` +
');';
const hashIntializer =
'code_cache_hash_.emplace(\n' +
` "${key}",\n` +
` "${sourceHash}"\n` +
');';
return {
definition, initializer, hashIntializer, sourceHash
};
@ -85,55 +87,55 @@ const cacheInitializers = [];
const cacheHashInitializers = [];
let totalCacheSize = 0;
function lexical(a, b) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
for (const key of cachableBuiltins) {
for (const key of cachableBuiltins.sort(lexical)) {
const cachedData = getCodeCache(key);
if (!cachedData.length) {
if (!isUint8Array(cachedData)) {
console.error(`Failed to generate code cache for '${key}'`);
process.exit(1);
}
const length = cachedData.length;
totalCacheSize += length;
const size = cachedData.byteLength;
totalCacheSize += size;
const {
definition, initializer, hashIntializer, sourceHash
} = getInitalizer(key, cachedData);
cacheDefinitions.push(definition);
cacheInitializers.push(initializer);
cacheHashInitializers.push(hashIntializer);
console.log(`Generated cache for '${key}', size = ${formatSize(length)}` +
console.log(`Generated cache for '${key}', size = ${formatSize(size)}` +
`, hash = ${sourceHash}, total = ${formatSize(totalCacheSize)}`);
}
const result = `#include "node.h"
#include "node_code_cache.h"
#include "v8.h"
#include "env.h"
#include "env-inl.h"
const result = `#include "node_native_module.h"
#include "node_internals.h"
// This file is generated by tools/generate_code_cache.js
// and is used when configure is run with \`--code-cache-path\`
namespace node {
namespace native_module {
${cacheDefinitions.join('\n\n')}
const bool native_module_has_code_cache = true;
// The target here will be returned as \`internalBinding('code_cache')\`
void DefineCodeCache(Environment* env, v8::Local<v8::Object> target) {
v8::Isolate* isolate = env->isolate();
v8::Local<v8::Context> context = env->context();
${cacheInitializers.join('\n')}
void NativeModuleLoader::LoadCodeCache() {
has_code_cache_ = true;
${cacheInitializers.join('\n ')}
}
// The target here will be returned as \`internalBinding('code_cache_hash')\`
void DefineCodeCacheHash(Environment* env, v8::Local<v8::Object> target) {
v8::Isolate* isolate = env->isolate();
v8::Local<v8::Context> context = env->context();
${cacheHashInitializers.join('\n')}
void NativeModuleLoader::LoadCodeCacheHash() {
${cacheHashInitializers.join('\n ')}
}
} // namespace native_module
} // namespace node
`;

View File

@ -43,11 +43,6 @@ def ToCArray(elements, step=10):
slices = map(lambda s: ','.join(str(x) for x in s), slices)
return ',\n'.join(slices)
def ToCString(contents):
return ToCArray(map(ord, contents), step=20)
def ReadFile(filename):
file = open(filename, "rt")
try:
@ -176,79 +171,49 @@ def ReadMacros(lines):
TEMPLATE = """
#include "node.h"
#include "node_javascript.h"
#include "v8.h"
#include "env.h"
#include "env-inl.h"
#include "node_native_module.h"
#include "node_internals.h"
namespace node {{
namespace {{
namespace native_module {{
{definitions}
}} // anonymous namespace
v8::Local<v8::String> NodePerContextSource(v8::Isolate* isolate) {{
return internal_per_context_value.ToStringChecked(isolate);
}}
v8::Local<v8::String> LoadersBootstrapperSource(Environment* env) {{
return internal_bootstrap_loaders_value.ToStringChecked(env->isolate());
}}
v8::Local<v8::String> NodeBootstrapperSource(Environment* env) {{
return internal_bootstrap_node_value.ToStringChecked(env->isolate());
}}
void DefineJavaScript(Environment* env, v8::Local<v8::Object> target) {{
void NativeModuleLoader::LoadJavaScriptSource() {{
{initializers}
}}
void DefineJavaScriptHash(Environment* env, v8::Local<v8::Object> target) {{
void NativeModuleLoader::LoadJavaScriptHash() {{
{hash_initializers}
}}
}} // namespace native_module
}} // namespace node
"""
ONE_BYTE_STRING = """
static const uint8_t raw_{var}[] = {{ {data} }};
static struct : public v8::String::ExternalOneByteStringResource {{
const char* data() const override {{
return reinterpret_cast<const char*>(raw_{var});
}}
size_t length() const override {{ return arraysize(raw_{var}); }}
void Dispose() override {{ /* Default calls `delete this`. */ }}
v8::Local<v8::String> ToStringChecked(v8::Isolate* isolate) {{
return v8::String::NewExternalOneByte(isolate, this).ToLocalChecked();
}}
}} {var};
static const uint8_t {var}[] = {{ {data} }};
"""
TWO_BYTE_STRING = """
static const uint16_t raw_{var}[] = {{ {data} }};
static struct : public v8::String::ExternalStringResource {{
const uint16_t* data() const override {{ return raw_{var}; }}
size_t length() const override {{ return arraysize(raw_{var}); }}
void Dispose() override {{ /* Default calls `delete this`. */ }}
v8::Local<v8::String> ToStringChecked(v8::Isolate* isolate) {{
return v8::String::NewExternalTwoByte(isolate, this).ToLocalChecked();
}}
}} {var};
static const uint16_t {var}[] = {{ {data} }};
"""
INITIALIZER = """\
CHECK(target->Set(env->context(),
{key}.ToStringChecked(env->isolate()),
{value}.ToStringChecked(env->isolate())).FromJust());
INITIALIZER = """
source_.emplace(
"{module}",
UnionBytes({var}, arraysize({var}))
);
"""
HASH_INITIALIZER = """\
CHECK(target->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "{key}"),
FIXED_ONE_BYTE_STRING(env->isolate(), "{value}")).FromJust());
source_hash_.emplace(
"{module}",
"{hash_value}"
);
"""
DEPRECATED_DEPS = """\
@ -259,20 +224,6 @@ process.emitWarning(
module.exports = require('internal/deps/{module}');
"""
def Render(var, data):
# Treat non-ASCII as UTF-8 and convert it to UTF-16.
if any(ord(c) > 127 for c in data):
template = TWO_BYTE_STRING
data = map(ord, data.decode('utf-8').encode('utf-16be'))
data = [data[i] * 256 + data[i+1] for i in xrange(0, len(data), 2)]
data = ToCArray(data)
else:
template = ONE_BYTE_STRING
data = ToCString(data)
return template.format(var=var, data=data)
def JS2C(source, target):
modules = []
consts = {}
@ -291,7 +242,29 @@ def JS2C(source, target):
# Build source code lines
definitions = []
initializers = []
hash_initializers = [];
hash_initializers = []
def AddModule(module, source):
var = '%s_raw' % (module.replace('-', '_').replace('/', '_'))
source_hash = hashlib.sha256(source).hexdigest()
# Treat non-ASCII as UTF-8 and convert it to UTF-16.
if any(ord(c) > 127 for c in source):
source = map(ord, source.decode('utf-8').encode('utf-16be'))
source = [source[i] * 256 + source[i+1] for i in xrange(0, len(source), 2)]
source = ToCArray(source)
definition = TWO_BYTE_STRING.format(var=var, data=source)
else:
source = ToCArray(map(ord, source), step=20)
definition = ONE_BYTE_STRING.format(var=var, data=source)
initializer = INITIALIZER.format(module=module,
var=var)
hash_initializer = HASH_INITIALIZER.format(module=module,
hash_value=source_hash)
definitions.append(definition)
initializers.append(initializer)
hash_initializers.append(hash_initializer)
for name in modules:
lines = ReadFile(str(name))
@ -317,28 +290,14 @@ def JS2C(source, target):
if name.endswith(".gypi"):
lines = re.sub(r'#.*?\n', '', lines)
lines = re.sub(r'\'', '"', lines)
name = name.split('.', 1)[0]
var = name.replace('-', '_').replace('/', '_')
key = '%s_key' % var
value = '%s_value' % var
hash_value = hashlib.sha256(lines).hexdigest()
definitions.append(Render(key, name))
definitions.append(Render(value, lines))
initializers.append(INITIALIZER.format(key=key, value=value))
hash_initializers.append(HASH_INITIALIZER.format(key=name, value=hash_value))
AddModule(name.split('.', 1)[0], lines)
# Add deprecated aliases for deps without 'deps/'
if deprecated_deps is not None:
name = '/'.join(deprecated_deps)
name = name.split('.', 1)[0]
var = name.replace('-', '_').replace('/', '_')
key = '%s_key' % var
value = '%s_value' % var
definitions.append(Render(key, name))
definitions.append(Render(value, DEPRECATED_DEPS.format(module=name)))
initializers.append(INITIALIZER.format(key=key, value=value))
hash_initializers.append(HASH_INITIALIZER.format(key=name, value=hash_value))
module = '/'.join(deprecated_deps).split('.', 1)[0]
source = DEPRECATED_DEPS.format(module=module)
AddModule(module, source)
# Emit result
output = open(str(target[0]), "w")