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
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
@@ -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');
@@ -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()) {
33 changes: 7 additions & 26 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
@@ -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');
@@ -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;
/**
@@ -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;
6 changes: 2 additions & 4 deletions lib/internal/util/embedding.js
Original file line number Diff line number Diff line change
@@ -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:
//
@@ -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;
2 changes: 2 additions & 0 deletions src/env_properties.h
Original file line number Diff line number Diff line change
@@ -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") \
131 changes: 116 additions & 15 deletions src/node_contextify.cc
Original file line number Diff line number Diff line change
@@ -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"
@@ -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);
@@ -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,
@@ -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,
@@ -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);
@@ -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);
@@ -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,
@@ -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);
2 changes: 0 additions & 2 deletions src/node_contextify.h
Original file line number Diff line number Diff line change
@@ -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,
36 changes: 4 additions & 32 deletions src/node_sea.cc
Original file line number Diff line number Diff line change
@@ -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;
@@ -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();
@@ -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());

@@ -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);
}

2 changes: 2 additions & 0 deletions src/node_sea.h
Original file line number Diff line number Diff line change
@@ -37,6 +37,8 @@ struct SeaResource {
std::unordered_map<std::string_view, std::string_view> assets;

bool use_snapshot() const;
bool use_code_cache() const;

static constexpr size_t kHeaderSize = sizeof(kMagic) + sizeof(SeaFlags);
};