diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/env.cc | 9 | ||||
-rw-r--r-- | src/env.h | 15 | ||||
-rw-r--r-- | src/node.cc | 36 | ||||
-rw-r--r-- | src/node_code_cache.h | 19 | ||||
-rw-r--r-- | src/node_code_cache_stub.cc | 22 | ||||
-rw-r--r-- | src/node_internals.h | 5 | ||||
-rw-r--r-- | src/node_javascript.h | 41 | ||||
-rw-r--r-- | src/node_native_module.cc | 324 | ||||
-rw-r--r-- | src/node_native_module.h | 64 | ||||
-rw-r--r-- | src/node_union_bytes.h | 93 |
10 files changed, 348 insertions, 280 deletions
diff --git a/src/env.cc b/src/env.cc index a39c51c26d..5731bad993 100644 --- a/src/env.cc +++ b/src/env.cc @@ -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> @@ -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*> diff --git a/src/node.cc b/src/node.cc index faef976e97..20aafe6a05 100644 --- a/src/node.cc +++ b/src/node.cc @@ -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, diff --git a/src/node_code_cache.h b/src/node_code_cache.h deleted file mode 100644 index 8054279d55..0000000000 --- a/src/node_code_cache.h +++ /dev/null @@ -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_ diff --git a/src/node_code_cache_stub.cc b/src/node_code_cache_stub.cc index 2fb86c5bf7..95997bb120 100644 --- a/src/node_code_cache_stub.cc +++ b/src/node_code_cache_stub.cc @@ -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 diff --git a/src/node_internals.h b/src/node_internals.h index b6cd628086..68c0b1f753 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -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; diff --git a/src/node_javascript.h b/src/node_javascript.h deleted file mode 100644 index 80ef40b4ec..0000000000 --- a/src/node_javascript.h +++ /dev/null @@ -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_ diff --git a/src/node_native_module.cc b/src/node_native_module.cc index ea7fe9189a..11136a244c 100644 --- a/src/node_native_module.cc +++ b/src/node_native_module.cc @@ -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; +} + +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 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. +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); + args.GetReturnValue().Set(result); +} - 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); +void NativeModuleLoader::GetSourceObject( + const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + args.GetReturnValue().Set(per_process_loader.GetSourceObject(env->context())); +} - 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); +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); +} - env->set_native_modules_with_cache(Set::New(isolate)); - env->set_native_modules_without_cache(Set::New(isolate)); +NativeModuleLoader::NativeModuleLoader() { + LoadJavaScriptSource(); + LoadJavaScriptHash(); + LoadCodeCache(); + LoadCodeCacheHash(); } -void NativeModule::CompileCodeCache(const FunctionCallbackInfo<Value>& args) { +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) diff --git a/src/node_native_module.h b/src/node_native_module.h index c4ffbfb0cd..ad9343f199 100644 --- a/src/node_native_module.h +++ b/src/node_native_module.h @@ -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 diff --git a/src/node_union_bytes.h b/src/node_union_bytes.h new file mode 100644 index 0000000000..128bb11ed0 --- /dev/null +++ b/src/node_union_bytes.h @@ -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_ |