src: use NativeModuleLoader to compile per_context.js
This patch introduces a NativeModuleLoader::CompileAndCall that can run a JS script under `lib/` as a function called with a null receiver and arguments specified from the C++ layer. Since all our bootstrappers are wrapped in functions in the source to avoid leaking variables into the global scope anyway, this allows us to remove that extra indentation in the JS source code. As a start we move the compilation and execution of per_context.js to NativeModuleLoader::CompileAndCall(). This patch also changes the return value of NativeModuleLoader::LookupAndCompile() to a MaybeLocal since the caller has to take care of the result being empty anyway. This patch reverts the previous design of having the NativeModuleLoader::Compile() method magically know about the parameters of the function - until we have tooling in-place to guess the parameter names in the source with some annotation, it's more readable to allow the caller to specify the parameters along with the arguments values. PR-URL: https://github.com/nodejs/node/pull/24660 Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Gus Caplan <me@gus.host>
This commit is contained in:
parent
25ad8decc6
commit
804138093a
@ -1,44 +1,44 @@
|
|||||||
|
// arguments: global
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// node::NewContext calls this script
|
// node::NewContext calls this script
|
||||||
|
|
||||||
(function(global) {
|
// https://github.com/nodejs/node/issues/14909
|
||||||
// https://github.com/nodejs/node/issues/14909
|
if (global.Intl) delete global.Intl.v8BreakIterator;
|
||||||
if (global.Intl) delete global.Intl.v8BreakIterator;
|
|
||||||
|
|
||||||
// https://github.com/nodejs/node/issues/21219
|
// https://github.com/nodejs/node/issues/21219
|
||||||
// Adds Atomics.notify and warns on first usage of Atomics.wake
|
// Adds Atomics.notify and warns on first usage of Atomics.wake
|
||||||
// https://github.com/v8/v8/commit/c79206b363 adds Atomics.notify so
|
// https://github.com/v8/v8/commit/c79206b363 adds Atomics.notify so
|
||||||
// now we alias Atomics.wake to notify so that we can remove it
|
// now we alias Atomics.wake to notify so that we can remove it
|
||||||
// semver major without worrying about V8.
|
// semver major without worrying about V8.
|
||||||
|
|
||||||
const AtomicsNotify = global.Atomics.notify;
|
const AtomicsNotify = global.Atomics.notify;
|
||||||
const ReflectApply = global.Reflect.apply;
|
const ReflectApply = global.Reflect.apply;
|
||||||
|
|
||||||
const warning = 'Atomics.wake will be removed in a future version, ' +
|
const warning = 'Atomics.wake will be removed in a future version, ' +
|
||||||
'use Atomics.notify instead.';
|
'use Atomics.notify instead.';
|
||||||
|
|
||||||
let wakeWarned = false;
|
let wakeWarned = false;
|
||||||
function wake(typedArray, index, count) {
|
function wake(typedArray, index, count) {
|
||||||
if (!wakeWarned) {
|
if (!wakeWarned) {
|
||||||
wakeWarned = true;
|
wakeWarned = true;
|
||||||
|
|
||||||
if (global.process !== undefined) {
|
if (global.process !== undefined) {
|
||||||
global.process.emitWarning(warning, 'Atomics');
|
global.process.emitWarning(warning, 'Atomics');
|
||||||
} else {
|
} else {
|
||||||
global.console.error(`Atomics: ${warning}`);
|
global.console.error(`Atomics: ${warning}`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ReflectApply(AtomicsNotify, this, arguments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Object.defineProperties(global.Atomics, {
|
return ReflectApply(AtomicsNotify, this, arguments);
|
||||||
wake: {
|
}
|
||||||
value: wake,
|
|
||||||
writable: true,
|
global.Object.defineProperties(global.Atomics, {
|
||||||
enumerable: false,
|
wake: {
|
||||||
configurable: true,
|
value: wake,
|
||||||
},
|
writable: true,
|
||||||
});
|
enumerable: false,
|
||||||
}(this));
|
configurable: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
19
src/node.cc
19
src/node.cc
@ -162,7 +162,6 @@ using v8::ObjectTemplate;
|
|||||||
using v8::PropertyAttribute;
|
using v8::PropertyAttribute;
|
||||||
using v8::ReadOnly;
|
using v8::ReadOnly;
|
||||||
using v8::Script;
|
using v8::Script;
|
||||||
using v8::ScriptCompiler;
|
|
||||||
using v8::ScriptOrigin;
|
using v8::ScriptOrigin;
|
||||||
using v8::SealHandleScope;
|
using v8::SealHandleScope;
|
||||||
using v8::SideEffectType;
|
using v8::SideEffectType;
|
||||||
@ -2500,14 +2499,16 @@ Local<Context> NewContext(Isolate* isolate,
|
|||||||
// Run lib/internal/per_context.js
|
// Run lib/internal/per_context.js
|
||||||
Context::Scope context_scope(context);
|
Context::Scope context_scope(context);
|
||||||
|
|
||||||
// TODO(joyeecheung): use NativeModuleLoader::Compile
|
std::vector<Local<String>> parameters = {
|
||||||
Local<String> per_context =
|
FIXED_ONE_BYTE_STRING(isolate, "global")};
|
||||||
per_process_loader.GetSource(isolate, "internal/per_context");
|
std::vector<Local<Value>> arguments = {context->Global()};
|
||||||
ScriptCompiler::Source per_context_src(per_context, nullptr);
|
MaybeLocal<Value> result = per_process_loader.CompileAndCall(
|
||||||
Local<Script> s = ScriptCompiler::Compile(
|
context, "internal/per_context", ¶meters, &arguments, nullptr);
|
||||||
context,
|
if (result.IsEmpty()) {
|
||||||
&per_context_src).ToLocalChecked();
|
// Execution failed during context creation.
|
||||||
s->Run(context).ToLocalChecked();
|
// TODO(joyeecheung): deprecate this signature and return a MaybeLocal.
|
||||||
|
return Local<Context>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
@ -103,29 +103,54 @@ void NativeModuleLoader::CompileCodeCache(
|
|||||||
|
|
||||||
// TODO(joyeecheung): allow compiling cache for bootstrapper by
|
// TODO(joyeecheung): allow compiling cache for bootstrapper by
|
||||||
// switching on id
|
// switching on id
|
||||||
Local<Value> result = CompileAsModule(env, *id, true);
|
MaybeLocal<Value> result =
|
||||||
|
CompileAsModule(env, *id, CompilationResultType::kCodeCache);
|
||||||
if (!result.IsEmpty()) {
|
if (!result.IsEmpty()) {
|
||||||
args.GetReturnValue().Set(result);
|
args.GetReturnValue().Set(result.ToLocalChecked());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NativeModuleLoader::CompileFunction(
|
void NativeModuleLoader::CompileFunction(
|
||||||
const FunctionCallbackInfo<Value>& args) {
|
const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
|
||||||
CHECK(args[0]->IsString());
|
CHECK(args[0]->IsString());
|
||||||
node::Utf8Value id(env->isolate(), args[0].As<String>());
|
node::Utf8Value id(env->isolate(), args[0].As<String>());
|
||||||
Local<Value> result = CompileAsModule(env, *id, false);
|
|
||||||
|
MaybeLocal<Value> result =
|
||||||
|
CompileAsModule(env, *id, CompilationResultType::kFunction);
|
||||||
if (!result.IsEmpty()) {
|
if (!result.IsEmpty()) {
|
||||||
args.GetReturnValue().Set(result);
|
args.GetReturnValue().Set(result.ToLocalChecked());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Local<Value> NativeModuleLoader::CompileAsModule(Environment* env,
|
// TODO(joyeecheung): it should be possible to generate the argument names
|
||||||
const char* id,
|
// from some special comments for the bootstrapper case.
|
||||||
bool produce_code_cache) {
|
MaybeLocal<Value> NativeModuleLoader::CompileAndCall(
|
||||||
|
Local<Context> context,
|
||||||
|
const char* id,
|
||||||
|
std::vector<Local<String>>* parameters,
|
||||||
|
std::vector<Local<Value>>* arguments,
|
||||||
|
Environment* optional_env) {
|
||||||
|
Isolate* isolate = context->GetIsolate();
|
||||||
|
MaybeLocal<Value> compiled = per_process_loader.LookupAndCompile(
|
||||||
|
context, id, parameters, CompilationResultType::kFunction, nullptr);
|
||||||
|
if (compiled.IsEmpty()) {
|
||||||
|
return compiled;
|
||||||
|
}
|
||||||
|
Local<Function> fn = compiled.ToLocalChecked().As<Function>();
|
||||||
|
return fn->Call(
|
||||||
|
context, v8::Null(isolate), arguments->size(), arguments->data());
|
||||||
|
}
|
||||||
|
|
||||||
|
MaybeLocal<Value> NativeModuleLoader::CompileAsModule(
|
||||||
|
Environment* env, const char* id, CompilationResultType result) {
|
||||||
|
std::vector<Local<String>> parameters = {env->exports_string(),
|
||||||
|
env->require_string(),
|
||||||
|
env->module_string(),
|
||||||
|
env->process_string(),
|
||||||
|
env->internal_binding_string()};
|
||||||
return per_process_loader.LookupAndCompile(
|
return per_process_loader.LookupAndCompile(
|
||||||
env->context(), id, produce_code_cache, env);
|
env->context(), id, ¶meters, result, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Currently V8 only checks that the length of the source code is the
|
// Currently V8 only checks that the length of the source code is the
|
||||||
@ -183,15 +208,18 @@ ScriptCompiler::CachedData* NativeModuleLoader::GetCachedData(
|
|||||||
return new ScriptCompiler::CachedData(code_cache_value, code_cache_length);
|
return new ScriptCompiler::CachedData(code_cache_value, code_cache_length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns Local<Function> of the compiled module if produce_code_cache
|
// Returns Local<Function> of the compiled module if return_code_cache
|
||||||
// is false (we are only compiling the function).
|
// is false (we are only compiling the function).
|
||||||
// Otherwise return a Local<Object> containing the cache.
|
// Otherwise return a Local<Object> containing the cache.
|
||||||
Local<Value> NativeModuleLoader::LookupAndCompile(Local<Context> context,
|
MaybeLocal<Value> NativeModuleLoader::LookupAndCompile(
|
||||||
const char* id,
|
Local<Context> context,
|
||||||
bool produce_code_cache,
|
const char* id,
|
||||||
Environment* optional_env) {
|
std::vector<Local<String>>* parameters,
|
||||||
|
CompilationResultType result_type,
|
||||||
|
Environment* optional_env) {
|
||||||
Isolate* isolate = context->GetIsolate();
|
Isolate* isolate = context->GetIsolate();
|
||||||
EscapableHandleScope scope(isolate);
|
EscapableHandleScope scope(isolate);
|
||||||
|
Local<Value> ret; // Used to convert to MaybeLocal before return
|
||||||
|
|
||||||
Local<String> source = GetSource(isolate, id);
|
Local<String> source = GetSource(isolate, id);
|
||||||
|
|
||||||
@ -209,7 +237,7 @@ Local<Value> NativeModuleLoader::LookupAndCompile(Local<Context> context,
|
|||||||
// built with them.
|
// built with them.
|
||||||
// 2. If we are generating code cache for tools/general_code_cache.js, we
|
// 2. If we are generating code cache for tools/general_code_cache.js, we
|
||||||
// are not going to use any cache ourselves.
|
// are not going to use any cache ourselves.
|
||||||
if (has_code_cache_ && !produce_code_cache) {
|
if (has_code_cache_ && result_type == CompilationResultType::kFunction) {
|
||||||
cached_data = GetCachedData(id);
|
cached_data = GetCachedData(id);
|
||||||
if (cached_data != nullptr) {
|
if (cached_data != nullptr) {
|
||||||
use_cache = true;
|
use_cache = true;
|
||||||
@ -219,7 +247,7 @@ Local<Value> NativeModuleLoader::LookupAndCompile(Local<Context> context,
|
|||||||
ScriptCompiler::Source script_source(source, origin, cached_data);
|
ScriptCompiler::Source script_source(source, origin, cached_data);
|
||||||
|
|
||||||
ScriptCompiler::CompileOptions options;
|
ScriptCompiler::CompileOptions options;
|
||||||
if (produce_code_cache) {
|
if (result_type == CompilationResultType::kCodeCache) {
|
||||||
options = ScriptCompiler::kEagerCompile;
|
options = ScriptCompiler::kEagerCompile;
|
||||||
} else if (use_cache) {
|
} else if (use_cache) {
|
||||||
options = ScriptCompiler::kConsumeCodeCache;
|
options = ScriptCompiler::kConsumeCodeCache;
|
||||||
@ -227,42 +255,25 @@ Local<Value> NativeModuleLoader::LookupAndCompile(Local<Context> context,
|
|||||||
options = ScriptCompiler::kNoCompileOptions;
|
options = ScriptCompiler::kNoCompileOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
MaybeLocal<Function> maybe_fun;
|
MaybeLocal<Function> maybe_fun =
|
||||||
// Currently we assume if Environment is ready, then we must be compiling
|
ScriptCompiler::CompileFunctionInContext(context,
|
||||||
// native modules instead of bootstrappers.
|
&script_source,
|
||||||
if (optional_env != nullptr) {
|
parameters->size(),
|
||||||
Local<String> parameters[] = {optional_env->exports_string(),
|
parameters->data(),
|
||||||
optional_env->require_string(),
|
0,
|
||||||
optional_env->module_string(),
|
nullptr,
|
||||||
optional_env->process_string(),
|
options);
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
Local<Function> fun;
|
|
||||||
// This could fail when there are early errors in the native modules,
|
// This could fail when there are early errors in the native modules,
|
||||||
// e.g. the syntax errors
|
// e.g. the syntax errors
|
||||||
if (maybe_fun.IsEmpty() || !maybe_fun.ToLocal(&fun)) {
|
if (maybe_fun.IsEmpty()) {
|
||||||
// In the case of early errors, v8 is already capable of
|
// In the case of early errors, v8 is already capable of
|
||||||
// decorating the stack for us - note that we use CompileFunctionInContext
|
// decorating the stack for us - note that we use CompileFunctionInContext
|
||||||
// so there is no need to worry about wrappers.
|
// so there is no need to worry about wrappers.
|
||||||
return scope.Escape(Local<Value>());
|
return MaybeLocal<Value>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Local<Function> fun = maybe_fun.ToLocalChecked();
|
||||||
if (use_cache) {
|
if (use_cache) {
|
||||||
if (optional_env != nullptr) {
|
if (optional_env != nullptr) {
|
||||||
// This could happen when Node is run with any v8 flag, but
|
// This could happen when Node is run with any v8 flag, but
|
||||||
@ -279,7 +290,7 @@ Local<Value> NativeModuleLoader::LookupAndCompile(Local<Context> context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (produce_code_cache) {
|
if (result_type == CompilationResultType::kCodeCache) {
|
||||||
std::unique_ptr<ScriptCompiler::CachedData> cached_data(
|
std::unique_ptr<ScriptCompiler::CachedData> cached_data(
|
||||||
ScriptCompiler::CreateCodeCacheForFunction(fun));
|
ScriptCompiler::CreateCodeCacheForFunction(fun));
|
||||||
CHECK_NE(cached_data, nullptr);
|
CHECK_NE(cached_data, nullptr);
|
||||||
@ -296,10 +307,12 @@ Local<Value> NativeModuleLoader::LookupAndCompile(Local<Context> context,
|
|||||||
copied.release(),
|
copied.release(),
|
||||||
cached_data_length,
|
cached_data_length,
|
||||||
ArrayBufferCreationMode::kInternalized);
|
ArrayBufferCreationMode::kInternalized);
|
||||||
return scope.Escape(Uint8Array::New(buf, 0, cached_data_length));
|
ret = Uint8Array::New(buf, 0, cached_data_length);
|
||||||
} else {
|
} else {
|
||||||
return scope.Escape(fun);
|
ret = fun;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return scope.Escape(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NativeModuleLoader::Initialize(Local<Object> target,
|
void NativeModuleLoader::Initialize(Local<Object> target,
|
||||||
|
@ -16,10 +16,24 @@ namespace native_module {
|
|||||||
using NativeModuleRecordMap = std::map<std::string, UnionBytes>;
|
using NativeModuleRecordMap = std::map<std::string, UnionBytes>;
|
||||||
using NativeModuleHashMap = std::map<std::string, std::string>;
|
using NativeModuleHashMap = std::map<std::string, std::string>;
|
||||||
|
|
||||||
// The native (C++) side of the native module compilation.
|
// The native (C++) side of the NativeModule in JS land, which
|
||||||
// This class should not depend on Environment
|
// handles compilation and caching of builtin modules (NativeModule)
|
||||||
|
// and bootstrappers, whose source are bundled into the binary
|
||||||
|
// as static data.
|
||||||
|
// This class should not depend on a particular isolate, context, or
|
||||||
|
// environment. Rather it should take them as arguments when necessary.
|
||||||
|
// The instances of this class are per-process.
|
||||||
class NativeModuleLoader {
|
class NativeModuleLoader {
|
||||||
public:
|
public:
|
||||||
|
// kCodeCache indicates that the compilation result should be returned
|
||||||
|
// as a Uint8Array, whereas kFunction indicates that the result should
|
||||||
|
// be returned as a Function.
|
||||||
|
// TODO(joyeecheung): it's possible to always produce code cache
|
||||||
|
// on the main thread and consume them in worker threads, or just
|
||||||
|
// share the cache among all the threads, although
|
||||||
|
// we need to decide whether to do that even when workers are not used.
|
||||||
|
enum class CompilationResultType { kCodeCache, kFunction };
|
||||||
|
|
||||||
NativeModuleLoader();
|
NativeModuleLoader();
|
||||||
static void Initialize(v8::Local<v8::Object> target,
|
static void Initialize(v8::Local<v8::Object> target,
|
||||||
v8::Local<v8::Value> unused,
|
v8::Local<v8::Value> unused,
|
||||||
@ -27,6 +41,18 @@ class NativeModuleLoader {
|
|||||||
v8::Local<v8::Object> GetSourceObject(v8::Local<v8::Context> context) const;
|
v8::Local<v8::Object> GetSourceObject(v8::Local<v8::Context> context) const;
|
||||||
v8::Local<v8::String> GetSource(v8::Isolate* isolate, const char* id) const;
|
v8::Local<v8::String> GetSource(v8::Isolate* isolate, const char* id) const;
|
||||||
|
|
||||||
|
// Run a script with JS source bundled inside the binary as if it's wrapped
|
||||||
|
// in a function called with a null receiver and arguments specified in C++.
|
||||||
|
// The returned value is empty if an exception is encountered.
|
||||||
|
// JS code run with this method can assume that their top-level
|
||||||
|
// declarations won't affect the global scope.
|
||||||
|
v8::MaybeLocal<v8::Value> CompileAndCall(
|
||||||
|
v8::Local<v8::Context> context,
|
||||||
|
const char* id,
|
||||||
|
std::vector<v8::Local<v8::String>>* parameters,
|
||||||
|
std::vector<v8::Local<v8::Value>>* arguments,
|
||||||
|
Environment* optional_env);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void GetCacheUsage(const v8::FunctionCallbackInfo<v8::Value>& args);
|
static void GetCacheUsage(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||||
// For legacy process.binding('natives') which is mutable, and for
|
// For legacy process.binding('natives') which is mutable, and for
|
||||||
@ -48,17 +74,21 @@ class NativeModuleLoader {
|
|||||||
void LoadCodeCacheHash(); // Loads data into code_cache_hash_
|
void LoadCodeCacheHash(); // Loads data into code_cache_hash_
|
||||||
|
|
||||||
v8::ScriptCompiler::CachedData* GetCachedData(const char* id) const;
|
v8::ScriptCompiler::CachedData* GetCachedData(const char* id) const;
|
||||||
static v8::Local<v8::Value> CompileAsModule(Environment* env,
|
|
||||||
const char* id,
|
// Compile a script as a NativeModule that can be loaded via
|
||||||
bool produce_code_cache);
|
// NativeModule.p.require in JS land.
|
||||||
// TODO(joyeecheung): make this public and reuse it to compile bootstrappers.
|
static v8::MaybeLocal<v8::Value> CompileAsModule(
|
||||||
|
Environment* env, const char* id, CompilationResultType result_type);
|
||||||
|
|
||||||
// For bootstrappers optional_env may be a nullptr.
|
// For bootstrappers optional_env may be a nullptr.
|
||||||
// This method magically knows what parameter it should pass to
|
// If an exception is encountered (e.g. source code contains
|
||||||
// the function to be compiled.
|
// syntax error), the returned value is empty.
|
||||||
v8::Local<v8::Value> LookupAndCompile(v8::Local<v8::Context> context,
|
v8::MaybeLocal<v8::Value> LookupAndCompile(
|
||||||
const char* id,
|
v8::Local<v8::Context> context,
|
||||||
bool produce_code_cache,
|
const char* id,
|
||||||
Environment* optional_env);
|
std::vector<v8::Local<v8::String>>* parameters,
|
||||||
|
CompilationResultType result_type,
|
||||||
|
Environment* optional_env);
|
||||||
|
|
||||||
bool has_code_cache_ = false;
|
bool has_code_cache_ = false;
|
||||||
NativeModuleRecordMap source_;
|
NativeModuleRecordMap source_;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user