Skip to content

Commit

Permalink
src: use dedicated routine to compile function for builtin CJS loader
Browse files Browse the repository at this point in the history
So that we can use it to handle code caching in a central place.

Drive-by: use per-isolate persistent strings for the parameters
and mark GetHostDefinedOptions() since it's only used in one
compilation unit

PR-URL: nodejs#52016
Refs: nodejs#47472
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
  • Loading branch information
joyeecheung authored and rdw-msft committed Mar 26, 2024
1 parent 3c9f963 commit 94e2738
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 96 deletions.
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 All @@ -56,10 +60,7 @@ const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
const moduleWrap = internalBinding('module_wrap');
const { ModuleWrap } = moduleWrap;
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 @@ -58,6 +58,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.
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

0 comments on commit 94e2738

Please sign in to comment.