mirror of https://github.com/nodejs/node.git
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:
parent
d246536924
commit
6cd678965f
|
@ -10,6 +10,9 @@ changes:
|
|||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/46824
|
||||
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.
|
||||
|
@ -174,7 +177,8 @@ The configuration currently reads the following top-level fields:
|
|||
"main": "/path/to/bundled/script.js",
|
||||
"output": "/path/to/write/the/generated/blob.blob",
|
||||
"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
|
||||
[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
|
||||
|
||||
### `require(id)` in the injected module is not file based
|
||||
|
|
|
@ -1123,7 +1123,7 @@ Module.prototype.require = function(id) {
|
|||
let resolvedArgv;
|
||||
let hasPausedEntry = false;
|
||||
let Script;
|
||||
function wrapSafe(filename, content, cjsModuleInstance) {
|
||||
function wrapSafe(filename, content, cjsModuleInstance, codeCache) {
|
||||
if (patched) {
|
||||
const wrapper = Module.wrap(content);
|
||||
if (Script === undefined) {
|
||||
|
@ -1158,6 +1158,7 @@ function wrapSafe(filename, content, cjsModuleInstance) {
|
|||
'__dirname',
|
||||
], {
|
||||
filename,
|
||||
cachedData: codeCache,
|
||||
importModuleDynamically(specifier, _, importAssertions) {
|
||||
const cascadedLoader = getCascadedLoader();
|
||||
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.
|
||||
if (result.sourceMapURL) {
|
||||
maybeCacheSourceMap(filename, content, this, false, undefined, result.sourceMapURL);
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
'use strict';
|
||||
const { codes: { ERR_UNKNOWN_BUILTIN_MODULE } } = require('internal/errors');
|
||||
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');
|
||||
|
||||
// This is roughly the same as:
|
||||
//
|
||||
|
@ -15,7 +16,11 @@ const { Module, wrapSafe } = require('internal/modules/cjs/loader');
|
|||
|
||||
function embedderRunCjs(contents) {
|
||||
const filename = process.execPath;
|
||||
const compiledWrapper = wrapSafe(filename, contents);
|
||||
const compiledWrapper = wrapSafe(
|
||||
isSea() ? getCodePath() : filename,
|
||||
contents,
|
||||
undefined,
|
||||
getCodeCache());
|
||||
|
||||
const customModule = new Module(filename, null);
|
||||
customModule.filename = filename;
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include "util-inl.h"
|
||||
|
||||
namespace node {
|
||||
using v8::ArrayBuffer;
|
||||
using v8::Context;
|
||||
using v8::Isolate;
|
||||
using v8::Local;
|
||||
|
@ -12,26 +11,8 @@ using v8::Object;
|
|||
using v8::String;
|
||||
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()
|
||||
: allocator_(ArrayBuffer::Allocator::NewDefaultAllocator()),
|
||||
isolate_(NewIsolate(allocator_.get())),
|
||||
handle_scope_(isolate_.get()),
|
||||
: handle_scope_(isolate_.get()),
|
||||
context_(isolate_.get(), Context::New(isolate_.get())),
|
||||
context_scope_(context_.Get(isolate_.get())) {}
|
||||
|
||||
|
|
|
@ -24,9 +24,7 @@ class JSONParser {
|
|||
private:
|
||||
// We might want a lighter-weight JSON parser for this use case. But for now
|
||||
// using V8 is good enough.
|
||||
static void FreeIsolate(v8::Isolate* isolate);
|
||||
std::unique_ptr<v8::ArrayBuffer::Allocator> allocator_;
|
||||
DeleteFnPtr<v8::Isolate, FreeIsolate> isolate_;
|
||||
RAIIIsolate isolate_;
|
||||
v8::HandleScope handle_scope_;
|
||||
v8::Global<v8::Context> context_;
|
||||
v8::Context::Scope context_scope_;
|
||||
|
|
|
@ -935,6 +935,22 @@ Maybe<bool> StoreCodeCacheResult(
|
|||
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,
|
||||
const Local<Value>& value) {
|
||||
return !value.IsEmpty() &&
|
||||
|
|
|
@ -210,6 +210,12 @@ v8::Maybe<bool> StoreCodeCacheResult(
|
|||
bool produce_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 node
|
||||
|
||||
|
|
187
src/node_sea.cc
187
src/node_sea.cc
|
@ -4,11 +4,14 @@
|
|||
#include "debug_utils-inl.h"
|
||||
#include "env-inl.h"
|
||||
#include "json_parser.h"
|
||||
#include "node_contextify.h"
|
||||
#include "node_errors.h"
|
||||
#include "node_external_reference.h"
|
||||
#include "node_internals.h"
|
||||
#include "node_snapshot_builder.h"
|
||||
#include "node_union_bytes.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 Node.js project that is present only once in the entire binary. It is
|
||||
|
@ -27,10 +30,19 @@
|
|||
#if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
|
||||
|
||||
using node::ExitCode;
|
||||
using v8::ArrayBuffer;
|
||||
using v8::BackingStore;
|
||||
using v8::Context;
|
||||
using v8::DataView;
|
||||
using v8::Function;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::HandleScope;
|
||||
using v8::Isolate;
|
||||
using v8::Local;
|
||||
using v8::NewStringType;
|
||||
using v8::Object;
|
||||
using v8::ScriptCompiler;
|
||||
using v8::String;
|
||||
using v8::Value;
|
||||
|
||||
namespace node {
|
||||
|
@ -76,6 +88,12 @@ size_t SeaSerializer::Write(const SeaResource& sea) {
|
|||
written_total += WriteArithmetic<uint32_t>(flags);
|
||||
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",
|
||||
sea.use_snapshot() ? "snapshot" : "code",
|
||||
sea.main_code_or_snapshot.data(),
|
||||
|
@ -84,6 +102,14 @@ size_t SeaSerializer::Write(const SeaResource& sea) {
|
|||
WriteStringView(sea.main_code_or_snapshot,
|
||||
sea.use_snapshot() ? StringLogMode::kAddressOnly
|
||||
: 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;
|
||||
}
|
||||
|
||||
|
@ -109,6 +135,11 @@ SeaResource SeaDeserializer::Read() {
|
|||
Debug("Read SEA flags %x\n", static_cast<uint32_t>(flags));
|
||||
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);
|
||||
std::string_view code =
|
||||
ReadStringView(use_snapshot ? StringLogMode::kAddressOnly
|
||||
|
@ -118,7 +149,15 @@ SeaResource SeaDeserializer::Read() {
|
|||
use_snapshot ? "snapshot" : "code",
|
||||
code.data(),
|
||||
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() {
|
||||
|
@ -167,6 +206,10 @@ bool IsSingleExecutable() {
|
|||
return postject_has_resource();
|
||||
}
|
||||
|
||||
void IsSea(const FunctionCallbackInfo<Value>& args) {
|
||||
args.GetReturnValue().Set(IsSingleExecutable());
|
||||
}
|
||||
|
||||
void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo<Value>& args) {
|
||||
bool is_building_sea =
|
||||
!per_process::cli_options->experimental_sea_config.empty();
|
||||
|
@ -185,6 +228,54 @@ 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());
|
||||
|
||||
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) {
|
||||
// Repeats argv[0] at position 1 on argv as a replacement for the missing
|
||||
// entry point file path.
|
||||
|
@ -269,6 +360,17 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -307,6 +409,59 @@ ExitCode GenerateSnapshotForSEA(const SeaConfig& config,
|
|||
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, ¶meters)
|
||||
.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(
|
||||
const SeaConfig& config,
|
||||
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{
|
||||
config.flags,
|
||||
config.main_path,
|
||||
builds_snapshot_from_main
|
||||
? 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;
|
||||
serializer.Write(sea);
|
||||
|
@ -374,14 +551,20 @@ void Initialize(Local<Object> target,
|
|||
Local<Value> unused,
|
||||
Local<Context> context,
|
||||
void* priv) {
|
||||
SetMethod(context, target, "isSea", IsSea);
|
||||
SetMethod(context,
|
||||
target,
|
||||
"isExperimentalSeaWarningNeeded",
|
||||
IsExperimentalSeaWarningNeeded);
|
||||
SetMethod(context, target, "getCodePath", GetCodePath);
|
||||
SetMethod(context, target, "getCodeCache", GetCodeCache);
|
||||
}
|
||||
|
||||
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||
registry->Register(IsSea);
|
||||
registry->Register(IsExperimentalSeaWarningNeeded);
|
||||
registry->Register(GetCodePath);
|
||||
registry->Register(GetCodeCache);
|
||||
}
|
||||
|
||||
} // namespace sea
|
||||
|
|
|
@ -6,10 +6,12 @@
|
|||
#if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
|
||||
|
||||
#include <cinttypes>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "node_exit_code.h"
|
||||
|
||||
namespace node {
|
||||
|
@ -24,11 +26,14 @@ enum class SeaFlags : uint32_t {
|
|||
kDefault = 0,
|
||||
kDisableExperimentalSeaWarning = 1 << 0,
|
||||
kUseSnapshot = 1 << 1,
|
||||
kUseCodeCache = 1 << 2,
|
||||
};
|
||||
|
||||
struct SeaResource {
|
||||
SeaFlags flags = SeaFlags::kDefault;
|
||||
std::string_view code_path;
|
||||
std::string_view main_code_or_snapshot;
|
||||
std::optional<std::string_view> code_cache;
|
||||
|
||||
bool use_snapshot() const;
|
||||
static constexpr size_t kHeaderSize = sizeof(kMagic) + sizeof(SeaFlags);
|
||||
|
|
|
@ -42,7 +42,6 @@ using v8::MaybeLocal;
|
|||
using v8::Object;
|
||||
using v8::ObjectTemplate;
|
||||
using v8::ScriptCompiler;
|
||||
using v8::ScriptOrigin;
|
||||
using v8::SnapshotCreator;
|
||||
using v8::StartupData;
|
||||
using v8::String;
|
||||
|
@ -1261,7 +1260,6 @@ void CompileSerializeMain(const FunctionCallbackInfo<Value>& args) {
|
|||
Local<String> source = args[1].As<String>();
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
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
|
||||
// internal version of them.
|
||||
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, "__dirname"),
|
||||
};
|
||||
ScriptCompiler::Source script_source(source, origin);
|
||||
Local<Function> fn;
|
||||
if (ScriptCompiler::CompileFunction(context,
|
||||
&script_source,
|
||||
parameters.size(),
|
||||
parameters.data(),
|
||||
0,
|
||||
nullptr,
|
||||
ScriptCompiler::kNoCompileOptions)
|
||||
if (contextify::CompileFunction(context, filename, source, ¶meters)
|
||||
.ToLocal(&fn)) {
|
||||
args.GetReturnValue().Set(fn);
|
||||
}
|
||||
|
|
18
src/util.cc
18
src/util.cc
|
@ -28,6 +28,7 @@
|
|||
#include "node_errors.h"
|
||||
#include "node_internals.h"
|
||||
#include "node_util.h"
|
||||
#include "node_v8_platform-inl.h"
|
||||
#include "string_bytes.h"
|
||||
#include "uv.h"
|
||||
|
||||
|
@ -55,6 +56,7 @@ static std::atomic_int seq = {0}; // Sequence number for diagnostic filenames.
|
|||
|
||||
namespace node {
|
||||
|
||||
using v8::ArrayBuffer;
|
||||
using v8::ArrayBufferView;
|
||||
using v8::Context;
|
||||
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
|
||||
|
|
13
src/util.h
13
src/util.h
|
@ -958,6 +958,19 @@ void SetConstructorFunction(v8::Isolate* isolate,
|
|||
SetConstructorFunctionFlag flag =
|
||||
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
|
||||
|
||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
|
|
@ -4,8 +4,8 @@ throw new Error('Should include grayed stack trace')
|
|||
|
||||
Error: Should include grayed stack trace
|
||||
at Object.<anonymous> [90m(/[39mtest*force_colors.js:1:7[90m)[39m
|
||||
[90m at Module._compile (node:internal*modules*cjs*loader:1233:14)[39m
|
||||
[90m at Module._extensions..js (node:internal*modules*cjs*loader:1287:10)[39m
|
||||
[90m at Module._compile (node:internal*modules*cjs*loader:1241:14)[39m
|
||||
[90m at Module._extensions..js (node:internal*modules*cjs*loader:1295:10)[39m
|
||||
[90m at Module.load (node:internal*modules*cjs*loader:1091:32)[39m
|
||||
[90m at Module._load (node:internal*modules*cjs*loader:938:12)[39m
|
||||
[90m at Function.executeUserEntryPoint [as runMain] (node:internal*modules*run_main:83:12)[39m
|
||||
|
|
|
@ -5,12 +5,15 @@ const createdRequire = createRequire(__filename);
|
|||
// because we set NODE_TEST_DIR=/Users/iojs/node-tmp on Jenkins CI.
|
||||
const { expectWarning, mustNotCall } = createdRequire(process.env.COMMON_DIRECTORY);
|
||||
|
||||
// This additionally makes sure that no unexpected warnings are emitted.
|
||||
if (createdRequire('./sea-config.json').disableExperimentalSEAWarning) {
|
||||
process.on('warning', mustNotCall());
|
||||
} else {
|
||||
expectWarning('ExperimentalWarning',
|
||||
'Single executable application is an experimental feature and ' +
|
||||
'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
|
||||
|
@ -18,6 +21,9 @@ if (createdRequire('./sea-config.json').disableExperimentalSEAWarning) {
|
|||
const { deepStrictEqual, strictEqual, throws } = require('assert');
|
||||
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:"
|
||||
// scheme.
|
||||
{
|
||||
|
|
|
@ -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');
|
Loading…
Reference in New Issue