Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: use dedicated routine to compile function for builtin CJS loader #52016

Merged
merged 1 commit into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 6 additions & 17 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,13 @@ const {
getLazy,
} = require('internal/util');
const {
internalCompileFunction,
makeContextifyScript,
runScriptInThisContext,
} = require('internal/vm');
const { containsModuleSyntax } = internalBinding('contextify');
const {
containsModuleSyntax,
compileFunctionForCJSLoader,
} = internalBinding('contextify');

const assert = require('internal/assert');
const fs = require('fs');
Expand Down Expand Up @@ -1274,23 +1276,10 @@ function wrapSafe(filename, content, cjsModuleInstance, codeCache) {
return runScriptInThisContext(script, true, false);
}

const params = [ 'exports', 'require', 'module', '__filename', '__dirname' ];
try {
const result = internalCompileFunction(
content, // code,
filename, // filename
0, // lineOffset
0, // columnOffset,
codeCache, // cachedData
false, // produceCachedData
undefined, // parsingContext
undefined, // contextExtensions
params, // params
hostDefinedOptionId, // hostDefinedOptionId
importModuleDynamically, // importModuleDynamically
);
const result = compileFunctionForCJSLoader(content, filename);

// The code cache is used for SEAs only.
// cachedDataRejected is only set for cache coming from SEA.
if (codeCache &&
result.cachedDataRejected !== false &&
internalBinding('sea').isSea()) {
Expand Down
33 changes: 7 additions & 26 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ function lazyTypes() {
return _TYPES = require('internal/util/types');
}

const { containsModuleSyntax } = internalBinding('contextify');
const {
containsModuleSyntax,
compileFunctionForCJSLoader,
} = internalBinding('contextify');

const { BuiltinModule } = require('internal/bootstrap/realm');
const assert = require('internal/assert');
const { readFileSync } = require('fs');
Expand Down Expand Up @@ -57,10 +61,7 @@ const moduleWrap = internalBinding('module_wrap');
const { ModuleWrap } = moduleWrap;
const asyncESM = require('internal/process/esm_loader');
const { emitWarningSync } = require('internal/process/warning');
const { internalCompileFunction } = require('internal/vm');
const {
vm_dynamic_import_default_internal,
} = internalBinding('symbols');

// Lazy-loading to avoid circular dependencies.
let getSourceSync;
/**
Expand Down Expand Up @@ -210,28 +211,8 @@ function enrichCJSError(err, content, filename) {
*/
function loadCJSModule(module, source, url, filename) {
let compileResult;
const hostDefinedOptionId = vm_dynamic_import_default_internal;
const importModuleDynamically = vm_dynamic_import_default_internal;
try {
compileResult = internalCompileFunction(
source, // code,
filename, // filename
0, // lineOffset
0, // columnOffset,
undefined, // cachedData
false, // produceCachedData
undefined, // parsingContext
undefined, // contextExtensions
[ // params
'exports',
'require',
'module',
'__filename',
'__dirname',
],
hostDefinedOptionId, // hostDefinedOptionsId
importModuleDynamically, // importModuleDynamically
);
compileResult = compileFunctionForCJSLoader(source, filename);
} catch (err) {
enrichCJSError(err, source, filename);
throw err;
Expand Down
6 changes: 2 additions & 4 deletions lib/internal/util/embedding.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
const { BuiltinModule: { normalizeRequirableId } } = require('internal/bootstrap/realm');
const { Module, wrapSafe } = require('internal/modules/cjs/loader');
const { codes: { ERR_UNKNOWN_BUILTIN_MODULE } } = require('internal/errors');
const { getCodeCache, getCodePath, isSea } = internalBinding('sea');
const { getCodePath, isSea } = internalBinding('sea');

// This is roughly the same as:
//
Expand All @@ -18,9 +18,7 @@ function embedderRunCjs(contents) {
const filename = process.execPath;
const compiledWrapper = wrapSafe(
isSea() ? getCodePath() : filename,
contents,
undefined,
getCodeCache());
contents);

const customModule = new Module(filename, null);
customModule.filename = filename;
Expand Down
2 changes: 2 additions & 0 deletions src/env_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
// Strings are per-isolate primitives but Environment proxies them
// for the sake of convenience. Strings should be ASCII-only.
#define PER_ISOLATE_STRING_PROPERTIES(V) \
V(__filename_string, "__filename") \
V(__dirname_string, "__dirname") \
V(ack_string, "ack") \
V(address_string, "address") \
V(aliases_string, "aliases") \
Expand Down
131 changes: 116 additions & 15 deletions src/node_contextify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "node_errors.h"
#include "node_external_reference.h"
#include "node_internals.h"
#include "node_sea.h"
#include "node_snapshot_builder.h"
#include "node_watchdog.h"
#include "util-inl.h"
Expand Down Expand Up @@ -1150,6 +1151,15 @@ ContextifyScript::ContextifyScript(Environment* env, Local<Object> object)

ContextifyScript::~ContextifyScript() {}

static Local<PrimitiveArray> GetHostDefinedOptions(Isolate* isolate,
Local<Symbol> id_symbol) {
Local<PrimitiveArray> host_defined_options =
PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength);
host_defined_options->Set(
isolate, loader::HostDefinedOptions::kID, id_symbol);
return host_defined_options;
}

void ContextifyContext::CompileFunction(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Expand Down Expand Up @@ -1280,15 +1290,6 @@ void ContextifyContext::CompileFunction(
args.GetReturnValue().Set(result);
}

Local<PrimitiveArray> ContextifyContext::GetHostDefinedOptions(
Isolate* isolate, Local<Symbol> id_symbol) {
Local<PrimitiveArray> host_defined_options =
PrimitiveArray::New(isolate, loader::HostDefinedOptions::kLength);
host_defined_options->Set(
isolate, loader::HostDefinedOptions::kID, id_symbol);
return host_defined_options;
}

ScriptCompiler::Source ContextifyContext::GetCommonJSSourceInstance(
Isolate* isolate,
Local<String> code,
Expand Down Expand Up @@ -1322,6 +1323,16 @@ ScriptCompiler::CompileOptions ContextifyContext::GetCompileOptions(
return options;
}

static std::vector<Local<String>> GetCJSParameters(IsolateData* data) {
return {
data->exports_string(),
data->require_string(),
data->module_string(),
data->__filename_string(),
data->__dirname_string(),
};
}

Local<Object> ContextifyContext::CompileFunctionAndCacheResult(
Environment* env,
Local<Context> parsing_context,
Expand Down Expand Up @@ -1450,12 +1461,7 @@ void ContextifyContext::ContainsModuleSyntax(
isolate, code, filename, 0, 0, host_defined_options, nullptr);
ScriptCompiler::CompileOptions options = GetCompileOptions(source);

std::vector<Local<String>> params = {
String::NewFromUtf8(isolate, "exports").ToLocalChecked(),
String::NewFromUtf8(isolate, "require").ToLocalChecked(),
String::NewFromUtf8(isolate, "module").ToLocalChecked(),
String::NewFromUtf8(isolate, "__filename").ToLocalChecked(),
String::NewFromUtf8(isolate, "__dirname").ToLocalChecked()};
std::vector<Local<String>> params = GetCJSParameters(env->isolate_data());

TryCatchScope try_catch(env);
ShouldNotAbortOnUncaughtScope no_abort_scope(env);
Expand Down Expand Up @@ -1485,6 +1491,96 @@ void ContextifyContext::ContainsModuleSyntax(
args.GetReturnValue().Set(found_error_message_caused_by_module_syntax);
}

static void CompileFunctionForCJSLoader(
const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsString());
CHECK(args[1]->IsString());
Local<String> code = args[0].As<String>();
Local<String> filename = args[1].As<String>();
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Environment* env = Environment::GetCurrent(context);

Local<Symbol> symbol = env->vm_dynamic_import_default_internal();
Local<PrimitiveArray> hdo = GetHostDefinedOptions(isolate, symbol);
ScriptOrigin origin(isolate,
filename,
0, // line offset
0, // column offset
true, // is cross origin
-1, // script id
Local<Value>(), // source map URL
false, // is opaque
false, // is WASM
false, // is ES Module
hdo);
ScriptCompiler::CachedData* cached_data = nullptr;

#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
bool used_cache_from_sea = false;
if (sea::IsSingleExecutable()) {
sea::SeaResource sea = sea::FindSingleExecutableResource();
if (sea.use_code_cache()) {
std::string_view data = sea.code_cache.value();
cached_data = new ScriptCompiler::CachedData(
reinterpret_cast<const uint8_t*>(data.data()),
static_cast<int>(data.size()),
v8::ScriptCompiler::CachedData::BufferNotOwned);
used_cache_from_sea = true;
}
}
#endif
ScriptCompiler::Source source(code, origin, cached_data);

TryCatchScope try_catch(env);

std::vector<Local<String>> params = GetCJSParameters(env->isolate_data());

MaybeLocal<Function> maybe_fn = ScriptCompiler::CompileFunction(
context,
&source,
params.size(),
params.data(),
0, /* context extensions size */
nullptr, /* context extensions data */
// TODO(joyeecheung): allow optional eager compilation.
Copy link
Member Author

@joyeecheung joyeecheung Mar 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly, in my prototype for #47472, enabling eager compilation doesn't speed up the loading of the various CLI tools I found in the code base - actually, it's slower than the default (lazy compilation). But since #51672 showed that eager compilation of essential internals could at least speed up our core startup, I think this really depends on the usage pattern of the code being compiled, so I left a TODO for future investigation (also I think technically users can try eager compilation themselves using --no-lazy)

cached_data == nullptr ? ScriptCompiler::kNoCompileOptions
: ScriptCompiler::kConsumeCodeCache,
v8::ScriptCompiler::NoCacheReason::kNoCacheNoReason);

Local<Function> fn;
if (!maybe_fn.ToLocal(&fn)) {
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
errors::DecorateErrorStack(env, try_catch);
if (!try_catch.HasTerminated()) {
try_catch.ReThrow();
}
return;
}
}

bool cache_rejected = false;
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
if (used_cache_from_sea) {
cache_rejected = source.GetCachedData()->rejected;
}
#endif

std::vector<Local<Name>> names = {
env->cached_data_rejected_string(),
env->source_map_url_string(),
env->function_string(),
};
std::vector<Local<Value>> values = {
Boolean::New(isolate, cache_rejected),
fn->GetScriptOrigin().SourceMapUrl(),
fn,
};
Local<Object> result = Object::New(
isolate, v8::Null(isolate), names.data(), values.data(), names.size());
args.GetReturnValue().Set(result);
}

static void StartSigintWatchdog(const FunctionCallbackInfo<Value>& args) {
int ret = SigintWatchdogHelper::GetInstance()->Start();
args.GetReturnValue().Set(ret == 0);
Expand Down Expand Up @@ -1537,6 +1633,10 @@ void CreatePerIsolateProperties(IsolateData* isolate_data,
isolate, target, "watchdogHasPendingSigint", WatchdogHasPendingSigint);

SetMethod(isolate, target, "measureMemory", MeasureMemory);
SetMethod(isolate,
target,
"compileFunctionForCJSLoader",
CompileFunctionForCJSLoader);
}

static void CreatePerContextProperties(Local<Object> target,
Expand Down Expand Up @@ -1576,6 +1676,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
ContextifyContext::RegisterExternalReferences(registry);
ContextifyScript::RegisterExternalReferences(registry);

registry->Register(CompileFunctionForCJSLoader);
registry->Register(StartSigintWatchdog);
registry->Register(StopSigintWatchdog);
registry->Register(WatchdogHasPendingSigint);
Expand Down
2 changes: 0 additions & 2 deletions src/node_contextify.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ class ContextifyContext : public BaseObject {
bool produce_cached_data,
v8::Local<v8::Symbol> id_symbol,
const errors::TryCatchScope& try_catch);
static v8::Local<v8::PrimitiveArray> GetHostDefinedOptions(
v8::Isolate* isolate, v8::Local<v8::Symbol> id_symbol);
static v8::ScriptCompiler::Source GetCommonJSSourceInstance(
v8::Isolate* isolate,
v8::Local<v8::String> code,
Expand Down
36 changes: 4 additions & 32 deletions src/node_sea.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ using node::ExitCode;
using v8::ArrayBuffer;
using v8::BackingStore;
using v8::Context;
using v8::DataView;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
Expand Down Expand Up @@ -219,6 +218,10 @@ bool SeaResource::use_snapshot() const {
return static_cast<bool>(flags & SeaFlags::kUseSnapshot);
}

bool SeaResource::use_code_cache() const {
return static_cast<bool>(flags & SeaFlags::kUseCodeCache);
}

SeaResource FindSingleExecutableResource() {
static const SeaResource sea_resource = []() -> SeaResource {
std::string_view blob = FindSingleExecutableBlob();
Expand Down Expand Up @@ -258,35 +261,6 @@ void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo<Value>& args) {
sea_resource.flags & SeaFlags::kDisableExperimentalSeaWarning));
}

void GetCodeCache(const FunctionCallbackInfo<Value>& args) {
if (!IsSingleExecutable()) {
return;
}

Isolate* isolate = args.GetIsolate();

SeaResource sea_resource = FindSingleExecutableResource();

if (!static_cast<bool>(sea_resource.flags & SeaFlags::kUseCodeCache)) {
return;
}

std::shared_ptr<BackingStore> backing_store = ArrayBuffer::NewBackingStore(
const_cast<void*>(
static_cast<const void*>(sea_resource.code_cache->data())),
sea_resource.code_cache->length(),
[](void* /* data */, size_t /* length */, void* /* deleter_data */) {
// The code cache data blob is not freed here because it is a static
// blob which is not allocated by the BackingStore allocator.
},
nullptr);
Local<ArrayBuffer> array_buffer = ArrayBuffer::New(isolate, backing_store);
Local<DataView> data_view =
DataView::New(array_buffer, 0, array_buffer->ByteLength());

args.GetReturnValue().Set(data_view);
}

void GetCodePath(const FunctionCallbackInfo<Value>& args) {
DCHECK(IsSingleExecutable());

Expand Down Expand Up @@ -653,15 +627,13 @@ void Initialize(Local<Object> target,
"isExperimentalSeaWarningNeeded",
IsExperimentalSeaWarningNeeded);
SetMethod(context, target, "getCodePath", GetCodePath);
SetMethod(context, target, "getCodeCache", GetCodeCache);
SetMethod(context, target, "getAsset", GetAsset);
}

void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(IsSea);
registry->Register(IsExperimentalSeaWarningNeeded);
registry->Register(GetCodePath);
registry->Register(GetCodeCache);
registry->Register(GetAsset);
}

Expand Down
Loading