sea: add support for V8 bytecode-only caching

Refs: https://github.com/nodejs/single-executable/issues/73
Signed-off-by: Darshan Sen <raisinten@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/48191
Fixes: https://github.com/nodejs/single-executable/issues/73
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
This commit is contained in:
Darshan Sen 2023-07-26 15:40:35 +05:30 committed by GitHub
parent d246536924
commit 6cd678965f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 347 additions and 41 deletions

View File

@ -10,6 +10,9 @@ changes:
- version: REPLACEME - version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/46824 pr-url: https://github.com/nodejs/node/pull/46824
description: Added support for "useSnapshot". description: Added support for "useSnapshot".
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/48191
description: Added support for "useCodeCache".
--> -->
> Stability: 1 - Experimental: This feature is being designed and will change. > Stability: 1 - Experimental: This feature is being designed and will change.
@ -174,7 +177,8 @@ The configuration currently reads the following top-level fields:
"main": "/path/to/bundled/script.js", "main": "/path/to/bundled/script.js",
"output": "/path/to/write/the/generated/blob.blob", "output": "/path/to/write/the/generated/blob.blob",
"disableExperimentalSEAWarning": true, // Default: false "disableExperimentalSEAWarning": true, // Default: false
"useSnapshot": false // Default: false "useSnapshot": false, // Default: false
"useCodeCache": true // Default: false
} }
``` ```
@ -213,6 +217,18 @@ and the main script can use the [`v8.startupSnapshot` API][] to adapt to
these constraints. See these constraints. See
[documentation about startup snapshot support in Node.js][]. [documentation about startup snapshot support in Node.js][].
### V8 code cache support
When `useCodeCache` is set to `true` in the configuration, during the generation
of the single executable preparation blob, Node.js will compile the `main`
script to generate the V8 code cache. The generated code cache would be part of
the preparation blob and get injected into the final executable. When the single
executable application is launched, instead of compiling the `main` script from
scratch, Node.js would use the code cache to speed up the compilation, then
execute the script, which would improve the startup performance.
**Note:** `import()` does not work when `useCodeCache` is `true`.
## Notes ## Notes
### `require(id)` in the injected module is not file based ### `require(id)` in the injected module is not file based

View File

@ -1123,7 +1123,7 @@ Module.prototype.require = function(id) {
let resolvedArgv; let resolvedArgv;
let hasPausedEntry = false; let hasPausedEntry = false;
let Script; let Script;
function wrapSafe(filename, content, cjsModuleInstance) { function wrapSafe(filename, content, cjsModuleInstance, codeCache) {
if (patched) { if (patched) {
const wrapper = Module.wrap(content); const wrapper = Module.wrap(content);
if (Script === undefined) { if (Script === undefined) {
@ -1158,6 +1158,7 @@ function wrapSafe(filename, content, cjsModuleInstance) {
'__dirname', '__dirname',
], { ], {
filename, filename,
cachedData: codeCache,
importModuleDynamically(specifier, _, importAssertions) { importModuleDynamically(specifier, _, importAssertions) {
const cascadedLoader = getCascadedLoader(); const cascadedLoader = getCascadedLoader();
return cascadedLoader.import(specifier, normalizeReferrerURL(filename), return cascadedLoader.import(specifier, normalizeReferrerURL(filename),
@ -1165,6 +1166,13 @@ function wrapSafe(filename, content, cjsModuleInstance) {
}, },
}); });
// The code cache is used for SEAs only.
if (codeCache &&
result.cachedDataRejected !== false &&
internalBinding('sea').isSea()) {
process.emitWarning('Code cache data rejected.');
}
// Cache the source map for the module if present. // Cache the source map for the module if present.
if (result.sourceMapURL) { if (result.sourceMapURL) {
maybeCacheSourceMap(filename, content, this, false, undefined, result.sourceMapURL); maybeCacheSourceMap(filename, content, this, false, undefined, result.sourceMapURL);

View File

@ -1,7 +1,8 @@
'use strict'; 'use strict';
const { codes: { ERR_UNKNOWN_BUILTIN_MODULE } } = require('internal/errors');
const { BuiltinModule: { normalizeRequirableId } } = require('internal/bootstrap/realm'); const { BuiltinModule: { normalizeRequirableId } } = require('internal/bootstrap/realm');
const { Module, wrapSafe } = require('internal/modules/cjs/loader'); const { Module, wrapSafe } = require('internal/modules/cjs/loader');
const { codes: { ERR_UNKNOWN_BUILTIN_MODULE } } = require('internal/errors');
const { getCodeCache, getCodePath, isSea } = internalBinding('sea');
// This is roughly the same as: // This is roughly the same as:
// //
@ -15,7 +16,11 @@ const { Module, wrapSafe } = require('internal/modules/cjs/loader');
function embedderRunCjs(contents) { function embedderRunCjs(contents) {
const filename = process.execPath; const filename = process.execPath;
const compiledWrapper = wrapSafe(filename, contents); const compiledWrapper = wrapSafe(
isSea() ? getCodePath() : filename,
contents,
undefined,
getCodeCache());
const customModule = new Module(filename, null); const customModule = new Module(filename, null);
customModule.filename = filename; customModule.filename = filename;

View File

@ -4,7 +4,6 @@
#include "util-inl.h" #include "util-inl.h"
namespace node { namespace node {
using v8::ArrayBuffer;
using v8::Context; using v8::Context;
using v8::Isolate; using v8::Isolate;
using v8::Local; using v8::Local;
@ -12,26 +11,8 @@ using v8::Object;
using v8::String; using v8::String;
using v8::Value; using v8::Value;
static Isolate* NewIsolate(v8::ArrayBuffer::Allocator* allocator) {
Isolate* isolate = Isolate::Allocate();
CHECK_NOT_NULL(isolate);
per_process::v8_platform.Platform()->RegisterIsolate(isolate,
uv_default_loop());
Isolate::CreateParams params;
params.array_buffer_allocator = allocator;
Isolate::Initialize(isolate, params);
return isolate;
}
void JSONParser::FreeIsolate(Isolate* isolate) {
per_process::v8_platform.Platform()->UnregisterIsolate(isolate);
isolate->Dispose();
}
JSONParser::JSONParser() JSONParser::JSONParser()
: allocator_(ArrayBuffer::Allocator::NewDefaultAllocator()), : handle_scope_(isolate_.get()),
isolate_(NewIsolate(allocator_.get())),
handle_scope_(isolate_.get()),
context_(isolate_.get(), Context::New(isolate_.get())), context_(isolate_.get(), Context::New(isolate_.get())),
context_scope_(context_.Get(isolate_.get())) {} context_scope_(context_.Get(isolate_.get())) {}

View File

@ -24,9 +24,7 @@ class JSONParser {
private: private:
// We might want a lighter-weight JSON parser for this use case. But for now // We might want a lighter-weight JSON parser for this use case. But for now
// using V8 is good enough. // using V8 is good enough.
static void FreeIsolate(v8::Isolate* isolate); RAIIIsolate isolate_;
std::unique_ptr<v8::ArrayBuffer::Allocator> allocator_;
DeleteFnPtr<v8::Isolate, FreeIsolate> isolate_;
v8::HandleScope handle_scope_; v8::HandleScope handle_scope_;
v8::Global<v8::Context> context_; v8::Global<v8::Context> context_;
v8::Context::Scope context_scope_; v8::Context::Scope context_scope_;

View File

@ -935,6 +935,22 @@ Maybe<bool> StoreCodeCacheResult(
return Just(true); return Just(true);
} }
// TODO(RaisinTen): Reuse in ContextifyContext::CompileFunction().
MaybeLocal<Function> CompileFunction(Local<Context> context,
Local<String> filename,
Local<String> content,
std::vector<Local<String>>* parameters) {
ScriptOrigin script_origin(context->GetIsolate(), filename, 0, 0, true);
ScriptCompiler::Source script_source(content, script_origin);
return ScriptCompiler::CompileFunction(context,
&script_source,
parameters->size(),
parameters->data(),
0,
nullptr);
}
bool ContextifyScript::InstanceOf(Environment* env, bool ContextifyScript::InstanceOf(Environment* env,
const Local<Value>& value) { const Local<Value>& value) {
return !value.IsEmpty() && return !value.IsEmpty() &&

View File

@ -210,6 +210,12 @@ v8::Maybe<bool> StoreCodeCacheResult(
bool produce_cached_data, bool produce_cached_data,
std::unique_ptr<v8::ScriptCompiler::CachedData> new_cached_data); std::unique_ptr<v8::ScriptCompiler::CachedData> new_cached_data);
v8::MaybeLocal<v8::Function> CompileFunction(
v8::Local<v8::Context> context,
v8::Local<v8::String> filename,
v8::Local<v8::String> content,
std::vector<v8::Local<v8::String>>* parameters);
} // namespace contextify } // namespace contextify
} // namespace node } // namespace node

View File

@ -4,11 +4,14 @@
#include "debug_utils-inl.h" #include "debug_utils-inl.h"
#include "env-inl.h" #include "env-inl.h"
#include "json_parser.h" #include "json_parser.h"
#include "node_contextify.h"
#include "node_errors.h"
#include "node_external_reference.h" #include "node_external_reference.h"
#include "node_internals.h" #include "node_internals.h"
#include "node_snapshot_builder.h" #include "node_snapshot_builder.h"
#include "node_union_bytes.h" #include "node_union_bytes.h"
#include "node_v8_platform-inl.h" #include "node_v8_platform-inl.h"
#include "util-inl.h"
// The POSTJECT_SENTINEL_FUSE macro is a string of random characters selected by // The POSTJECT_SENTINEL_FUSE macro is a string of random characters selected by
// the Node.js project that is present only once in the entire binary. It is // the Node.js project that is present only once in the entire binary. It is
@ -27,10 +30,19 @@
#if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION) #if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
using node::ExitCode; using node::ExitCode;
using v8::ArrayBuffer;
using v8::BackingStore;
using v8::Context; using v8::Context;
using v8::DataView;
using v8::Function;
using v8::FunctionCallbackInfo; using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Isolate;
using v8::Local; using v8::Local;
using v8::NewStringType;
using v8::Object; using v8::Object;
using v8::ScriptCompiler;
using v8::String;
using v8::Value; using v8::Value;
namespace node { namespace node {
@ -76,6 +88,12 @@ size_t SeaSerializer::Write(const SeaResource& sea) {
written_total += WriteArithmetic<uint32_t>(flags); written_total += WriteArithmetic<uint32_t>(flags);
DCHECK_EQ(written_total, SeaResource::kHeaderSize); DCHECK_EQ(written_total, SeaResource::kHeaderSize);
Debug("Write SEA code path %p, size=%zu\n",
sea.code_path.data(),
sea.code_path.size());
written_total +=
WriteStringView(sea.code_path, StringLogMode::kAddressAndContent);
Debug("Write SEA resource %s %p, size=%zu\n", Debug("Write SEA resource %s %p, size=%zu\n",
sea.use_snapshot() ? "snapshot" : "code", sea.use_snapshot() ? "snapshot" : "code",
sea.main_code_or_snapshot.data(), sea.main_code_or_snapshot.data(),
@ -84,6 +102,14 @@ size_t SeaSerializer::Write(const SeaResource& sea) {
WriteStringView(sea.main_code_or_snapshot, WriteStringView(sea.main_code_or_snapshot,
sea.use_snapshot() ? StringLogMode::kAddressOnly sea.use_snapshot() ? StringLogMode::kAddressOnly
: StringLogMode::kAddressAndContent); : StringLogMode::kAddressAndContent);
if (sea.code_cache.has_value()) {
Debug("Write SEA resource code cache %p, size=%zu\n",
sea.code_cache->data(),
sea.code_cache->size());
written_total +=
WriteStringView(sea.code_cache.value(), StringLogMode::kAddressOnly);
}
return written_total; return written_total;
} }
@ -109,6 +135,11 @@ SeaResource SeaDeserializer::Read() {
Debug("Read SEA flags %x\n", static_cast<uint32_t>(flags)); Debug("Read SEA flags %x\n", static_cast<uint32_t>(flags));
CHECK_EQ(read_total, SeaResource::kHeaderSize); CHECK_EQ(read_total, SeaResource::kHeaderSize);
std::string_view code_path =
ReadStringView(StringLogMode::kAddressAndContent);
Debug(
"Read SEA code path %p, size=%zu\n", code_path.data(), code_path.size());
bool use_snapshot = static_cast<bool>(flags & SeaFlags::kUseSnapshot); bool use_snapshot = static_cast<bool>(flags & SeaFlags::kUseSnapshot);
std::string_view code = std::string_view code =
ReadStringView(use_snapshot ? StringLogMode::kAddressOnly ReadStringView(use_snapshot ? StringLogMode::kAddressOnly
@ -118,7 +149,15 @@ SeaResource SeaDeserializer::Read() {
use_snapshot ? "snapshot" : "code", use_snapshot ? "snapshot" : "code",
code.data(), code.data(),
code.size()); code.size());
return {flags, code};
std::string_view code_cache;
if (static_cast<bool>(flags & SeaFlags::kUseCodeCache)) {
code_cache = ReadStringView(StringLogMode::kAddressOnly);
Debug("Read SEA resource code cache %p, size=%zu\n",
code_cache.data(),
code_cache.size());
}
return {flags, code_path, code, code_cache};
} }
std::string_view FindSingleExecutableBlob() { std::string_view FindSingleExecutableBlob() {
@ -167,6 +206,10 @@ bool IsSingleExecutable() {
return postject_has_resource(); return postject_has_resource();
} }
void IsSea(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(IsSingleExecutable());
}
void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo<Value>& args) { void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo<Value>& args) {
bool is_building_sea = bool is_building_sea =
!per_process::cli_options->experimental_sea_config.empty(); !per_process::cli_options->experimental_sea_config.empty();
@ -185,6 +228,54 @@ void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo<Value>& args) {
sea_resource.flags & SeaFlags::kDisableExperimentalSeaWarning)); 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());
Isolate* isolate = args.GetIsolate();
SeaResource sea_resource = FindSingleExecutableResource();
Local<String> code_path;
if (!String::NewFromUtf8(isolate,
sea_resource.code_path.data(),
NewStringType::kNormal,
sea_resource.code_path.length())
.ToLocal(&code_path)) {
return;
}
args.GetReturnValue().Set(code_path);
}
std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv) { std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv) {
// Repeats argv[0] at position 1 on argv as a replacement for the missing // Repeats argv[0] at position 1 on argv as a replacement for the missing
// entry point file path. // entry point file path.
@ -269,6 +360,17 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
result.flags |= SeaFlags::kUseSnapshot; result.flags |= SeaFlags::kUseSnapshot;
} }
std::optional<bool> use_code_cache =
parser.GetTopLevelBoolField("useCodeCache");
if (!use_code_cache.has_value()) {
FPrintF(
stderr, "\"useCodeCache\" field of %s is not a Boolean\n", config_path);
return std::nullopt;
}
if (use_code_cache.value()) {
result.flags |= SeaFlags::kUseCodeCache;
}
return result; return result;
} }
@ -307,6 +409,59 @@ ExitCode GenerateSnapshotForSEA(const SeaConfig& config,
return ExitCode::kNoFailure; return ExitCode::kNoFailure;
} }
std::optional<std::string> GenerateCodeCache(std::string_view main_path,
std::string_view main_script) {
RAIIIsolate raii_isolate;
Isolate* isolate = raii_isolate.get();
HandleScope handle_scope(isolate);
Local<Context> context = Context::New(isolate);
Context::Scope context_scope(context);
errors::PrinterTryCatch bootstrapCatch(
isolate, errors::PrinterTryCatch::kPrintSourceLine);
Local<String> filename;
if (!String::NewFromUtf8(isolate,
main_path.data(),
NewStringType::kNormal,
main_path.length())
.ToLocal(&filename)) {
return std::nullopt;
}
Local<String> content;
if (!String::NewFromUtf8(isolate,
main_script.data(),
NewStringType::kNormal,
main_script.length())
.ToLocal(&content)) {
return std::nullopt;
}
std::vector<Local<String>> parameters = {
FIXED_ONE_BYTE_STRING(isolate, "exports"),
FIXED_ONE_BYTE_STRING(isolate, "require"),
FIXED_ONE_BYTE_STRING(isolate, "module"),
FIXED_ONE_BYTE_STRING(isolate, "__filename"),
FIXED_ONE_BYTE_STRING(isolate, "__dirname"),
};
// TODO(RaisinTen): Using the V8 code cache prevents us from using `import()`
// in the SEA code. Support it.
// Refs: https://github.com/nodejs/node/pull/48191#discussion_r1213271430
Local<Function> fn;
if (!contextify::CompileFunction(context, filename, content, &parameters)
.ToLocal(&fn)) {
return std::nullopt;
}
std::unique_ptr<ScriptCompiler::CachedData> cache{
ScriptCompiler::CreateCodeCacheForFunction(fn)};
std::string code_cache(cache->data, cache->data + cache->length);
return code_cache;
}
ExitCode GenerateSingleExecutableBlob( ExitCode GenerateSingleExecutableBlob(
const SeaConfig& config, const SeaConfig& config,
const std::vector<std::string>& args, const std::vector<std::string>& args,
@ -331,11 +486,33 @@ ExitCode GenerateSingleExecutableBlob(
} }
} }
std::optional<std::string> optional_code_cache =
GenerateCodeCache(config.main_path, main_script);
if (!optional_code_cache.has_value()) {
FPrintF(stderr, "Cannot generate V8 code cache\n");
return ExitCode::kGenericUserError;
}
std::optional<std::string_view> optional_sv_code_cache;
std::string code_cache;
if (static_cast<bool>(config.flags & SeaFlags::kUseCodeCache)) {
std::optional<std::string> optional_code_cache =
GenerateCodeCache(config.main_path, main_script);
if (!optional_code_cache.has_value()) {
FPrintF(stderr, "Cannot generate V8 code cache\n");
return ExitCode::kGenericUserError;
}
code_cache = optional_code_cache.value();
optional_sv_code_cache = code_cache;
}
SeaResource sea{ SeaResource sea{
config.flags, config.flags,
config.main_path,
builds_snapshot_from_main builds_snapshot_from_main
? std::string_view{snapshot_blob.data(), snapshot_blob.size()} ? std::string_view{snapshot_blob.data(), snapshot_blob.size()}
: std::string_view{main_script.data(), main_script.size()}}; : std::string_view{main_script.data(), main_script.size()},
optional_sv_code_cache};
SeaSerializer serializer; SeaSerializer serializer;
serializer.Write(sea); serializer.Write(sea);
@ -374,14 +551,20 @@ void Initialize(Local<Object> target,
Local<Value> unused, Local<Value> unused,
Local<Context> context, Local<Context> context,
void* priv) { void* priv) {
SetMethod(context, target, "isSea", IsSea);
SetMethod(context, SetMethod(context,
target, target,
"isExperimentalSeaWarningNeeded", "isExperimentalSeaWarningNeeded",
IsExperimentalSeaWarningNeeded); IsExperimentalSeaWarningNeeded);
SetMethod(context, target, "getCodePath", GetCodePath);
SetMethod(context, target, "getCodeCache", GetCodeCache);
} }
void RegisterExternalReferences(ExternalReferenceRegistry* registry) { void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(IsSea);
registry->Register(IsExperimentalSeaWarningNeeded); registry->Register(IsExperimentalSeaWarningNeeded);
registry->Register(GetCodePath);
registry->Register(GetCodeCache);
} }
} // namespace sea } // namespace sea

View File

@ -6,10 +6,12 @@
#if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION) #if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
#include <cinttypes> #include <cinttypes>
#include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <tuple> #include <tuple>
#include <vector> #include <vector>
#include "node_exit_code.h" #include "node_exit_code.h"
namespace node { namespace node {
@ -24,11 +26,14 @@ enum class SeaFlags : uint32_t {
kDefault = 0, kDefault = 0,
kDisableExperimentalSeaWarning = 1 << 0, kDisableExperimentalSeaWarning = 1 << 0,
kUseSnapshot = 1 << 1, kUseSnapshot = 1 << 1,
kUseCodeCache = 1 << 2,
}; };
struct SeaResource { struct SeaResource {
SeaFlags flags = SeaFlags::kDefault; SeaFlags flags = SeaFlags::kDefault;
std::string_view code_path;
std::string_view main_code_or_snapshot; std::string_view main_code_or_snapshot;
std::optional<std::string_view> code_cache;
bool use_snapshot() const; bool use_snapshot() const;
static constexpr size_t kHeaderSize = sizeof(kMagic) + sizeof(SeaFlags); static constexpr size_t kHeaderSize = sizeof(kMagic) + sizeof(SeaFlags);

View File

@ -42,7 +42,6 @@ using v8::MaybeLocal;
using v8::Object; using v8::Object;
using v8::ObjectTemplate; using v8::ObjectTemplate;
using v8::ScriptCompiler; using v8::ScriptCompiler;
using v8::ScriptOrigin;
using v8::SnapshotCreator; using v8::SnapshotCreator;
using v8::StartupData; using v8::StartupData;
using v8::String; using v8::String;
@ -1261,7 +1260,6 @@ void CompileSerializeMain(const FunctionCallbackInfo<Value>& args) {
Local<String> source = args[1].As<String>(); Local<String> source = args[1].As<String>();
Isolate* isolate = args.GetIsolate(); Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext(); Local<Context> context = isolate->GetCurrentContext();
ScriptOrigin origin(isolate, filename, 0, 0, true);
// TODO(joyeecheung): do we need all of these? Maybe we would want a less // TODO(joyeecheung): do we need all of these? Maybe we would want a less
// internal version of them. // internal version of them.
std::vector<Local<String>> parameters = { std::vector<Local<String>> parameters = {
@ -1269,15 +1267,8 @@ void CompileSerializeMain(const FunctionCallbackInfo<Value>& args) {
FIXED_ONE_BYTE_STRING(isolate, "__filename"), FIXED_ONE_BYTE_STRING(isolate, "__filename"),
FIXED_ONE_BYTE_STRING(isolate, "__dirname"), FIXED_ONE_BYTE_STRING(isolate, "__dirname"),
}; };
ScriptCompiler::Source script_source(source, origin);
Local<Function> fn; Local<Function> fn;
if (ScriptCompiler::CompileFunction(context, if (contextify::CompileFunction(context, filename, source, &parameters)
&script_source,
parameters.size(),
parameters.data(),
0,
nullptr,
ScriptCompiler::kNoCompileOptions)
.ToLocal(&fn)) { .ToLocal(&fn)) {
args.GetReturnValue().Set(fn); args.GetReturnValue().Set(fn);
} }

View File

@ -28,6 +28,7 @@
#include "node_errors.h" #include "node_errors.h"
#include "node_internals.h" #include "node_internals.h"
#include "node_util.h" #include "node_util.h"
#include "node_v8_platform-inl.h"
#include "string_bytes.h" #include "string_bytes.h"
#include "uv.h" #include "uv.h"
@ -55,6 +56,7 @@ static std::atomic_int seq = {0}; // Sequence number for diagnostic filenames.
namespace node { namespace node {
using v8::ArrayBuffer;
using v8::ArrayBufferView; using v8::ArrayBufferView;
using v8::Context; using v8::Context;
using v8::FunctionTemplate; using v8::FunctionTemplate;
@ -629,4 +631,20 @@ Local<String> UnionBytes::ToStringChecked(Isolate* isolate) const {
} }
} }
RAIIIsolate::RAIIIsolate()
: allocator_{ArrayBuffer::Allocator::NewDefaultAllocator()} {
isolate_ = Isolate::Allocate();
CHECK_NOT_NULL(isolate_);
per_process::v8_platform.Platform()->RegisterIsolate(isolate_,
uv_default_loop());
Isolate::CreateParams params;
params.array_buffer_allocator = allocator_.get();
Isolate::Initialize(isolate_, params);
}
RAIIIsolate::~RAIIIsolate() {
per_process::v8_platform.Platform()->UnregisterIsolate(isolate_);
isolate_->Dispose();
}
} // namespace node } // namespace node

View File

@ -958,6 +958,19 @@ void SetConstructorFunction(v8::Isolate* isolate,
SetConstructorFunctionFlag flag = SetConstructorFunctionFlag flag =
SetConstructorFunctionFlag::SET_CLASS_NAME); SetConstructorFunctionFlag::SET_CLASS_NAME);
// Simple RAII class to spin up a v8::Isolate instance.
class RAIIIsolate {
public:
RAIIIsolate();
~RAIIIsolate();
v8::Isolate* get() const { return isolate_; }
private:
std::unique_ptr<v8::ArrayBuffer::Allocator> allocator_;
v8::Isolate* isolate_;
};
} // namespace node } // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

View File

@ -4,8 +4,8 @@ throw new Error('Should include grayed stack trace')
Error: Should include grayed stack trace Error: Should include grayed stack trace
at Object.<anonymous> (/test*force_colors.js:1:7) at Object.<anonymous> (/test*force_colors.js:1:7)
 at Module._compile (node:internal*modules*cjs*loader:1233:14)  at Module._compile (node:internal*modules*cjs*loader:1241:14)
 at Module._extensions..js (node:internal*modules*cjs*loader:1287:10)  at Module._extensions..js (node:internal*modules*cjs*loader:1295:10)
 at Module.load (node:internal*modules*cjs*loader:1091:32)  at Module.load (node:internal*modules*cjs*loader:1091:32)
 at Module._load (node:internal*modules*cjs*loader:938:12)  at Module._load (node:internal*modules*cjs*loader:938:12)
 at Function.executeUserEntryPoint [as runMain] (node:internal*modules*run_main:83:12)  at Function.executeUserEntryPoint [as runMain] (node:internal*modules*run_main:83:12)

View File

@ -5,12 +5,15 @@ const createdRequire = createRequire(__filename);
// because we set NODE_TEST_DIR=/Users/iojs/node-tmp on Jenkins CI. // because we set NODE_TEST_DIR=/Users/iojs/node-tmp on Jenkins CI.
const { expectWarning, mustNotCall } = createdRequire(process.env.COMMON_DIRECTORY); const { expectWarning, mustNotCall } = createdRequire(process.env.COMMON_DIRECTORY);
// This additionally makes sure that no unexpected warnings are emitted.
if (createdRequire('./sea-config.json').disableExperimentalSEAWarning) { if (createdRequire('./sea-config.json').disableExperimentalSEAWarning) {
process.on('warning', mustNotCall()); process.on('warning', mustNotCall());
} else { } else {
expectWarning('ExperimentalWarning', expectWarning('ExperimentalWarning',
'Single executable application is an experimental feature and ' + 'Single executable application is an experimental feature and ' +
'might change at any time'); 'might change at any time');
// Any unexpected warning would throw this error:
// https://github.com/nodejs/node/blob/c301404105a7256b79a0b8c4522ce47af96dfa17/test/common/index.js#L697-L700.
} }
// Should be possible to require core modules that optionally require the // Should be possible to require core modules that optionally require the
@ -18,6 +21,9 @@ if (createdRequire('./sea-config.json').disableExperimentalSEAWarning) {
const { deepStrictEqual, strictEqual, throws } = require('assert'); const { deepStrictEqual, strictEqual, throws } = require('assert');
const { dirname } = require('node:path'); const { dirname } = require('node:path');
// Checks that the source filename is used in the error stack trace.
strictEqual(new Error('lol').stack.split('\n')[1], ' at sea.js:25:13');
// Should be possible to require a core module that requires using the "node:" // Should be possible to require a core module that requires using the "node:"
// scheme. // scheme.
{ {

View File

@ -0,0 +1,60 @@
'use strict';
require('../common');
const {
injectAndCodeSign,
skipIfSingleExecutableIsNotSupported,
} = require('../common/sea');
skipIfSingleExecutableIsNotSupported();
// This tests the creation of a single executable application which uses the
// V8 code cache.
const fixtures = require('../common/fixtures');
const tmpdir = require('../common/tmpdir');
const { copyFileSync, writeFileSync, existsSync } = require('fs');
const { execFileSync } = require('child_process');
const { join } = require('path');
const { strictEqual } = require('assert');
const assert = require('assert');
const inputFile = fixtures.path('sea.js');
const requirableFile = join(tmpdir.path, 'requirable.js');
const configFile = join(tmpdir.path, 'sea-config.json');
const seaPrepBlob = join(tmpdir.path, 'sea-prep.blob');
const outputFile = join(tmpdir.path, process.platform === 'win32' ? 'sea.exe' : 'sea');
tmpdir.refresh();
writeFileSync(requirableFile, `
module.exports = {
hello: 'world',
};
`);
writeFileSync(configFile, `
{
"main": "sea.js",
"output": "sea-prep.blob",
"useCodeCache": true
}
`);
// Copy input to working directory
copyFileSync(inputFile, join(tmpdir.path, 'sea.js'));
execFileSync(process.execPath, ['--experimental-sea-config', 'sea-config.json'], {
cwd: tmpdir.path
});
assert(existsSync(seaPrepBlob));
copyFileSync(process.execPath, outputFile);
injectAndCodeSign(outputFile, seaPrepBlob);
const singleExecutableApplicationOutput = execFileSync(
outputFile,
[ '-a', '--b=c', 'd' ],
{ env: { COMMON_DIRECTORY: join(__dirname, '..', 'common') } });
strictEqual(singleExecutableApplicationOutput.toString(), 'Hello, world! 😊\n');