Skip to content

Commit 657764b

Browse files
committed
sea: add support for V8 bytecode-only caching
Refs: nodejs/single-executable#73 Signed-off-by: Darshan Sen <raisinten@gmail.com>
1 parent 9117d45 commit 657764b

11 files changed

Lines changed: 172 additions & 38 deletions

File tree

lib/internal/modules/cjs/loader.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ let hasLoadedAnyUserCJSModule = false;
127127

128128
const {
129129
codes: {
130+
ERR_INTERNAL_ASSERTION,
130131
ERR_INVALID_ARG_VALUE,
131132
ERR_INVALID_MODULE_SPECIFIER,
132133
ERR_REQUIRE_ESM,
@@ -1147,7 +1148,7 @@ Module.prototype.require = function(id) {
11471148
let resolvedArgv;
11481149
let hasPausedEntry = false;
11491150
let Script;
1150-
function wrapSafe(filename, content, cjsModuleInstance) {
1151+
function wrapSafe(filename, content, cjsModuleInstance, codeCache) {
11511152
if (patched) {
11521153
const wrapper = Module.wrap(content);
11531154
if (Script === undefined) {
@@ -1182,13 +1183,18 @@ function wrapSafe(filename, content, cjsModuleInstance) {
11821183
'__dirname',
11831184
], {
11841185
filename,
1186+
cachedData: codeCache,
11851187
importModuleDynamically(specifier, _, importAssertions) {
11861188
const cascadedLoader = getCascadedLoader();
11871189
return cascadedLoader.import(specifier, normalizeReferrerURL(filename),
11881190
importAssertions);
11891191
},
11901192
});
11911193

1194+
if (codeCache && result.cachedDataRejected !== false) {
1195+
throw new ERR_INTERNAL_ASSERTION('Code cache data rejected.');
1196+
}
1197+
11921198
// Cache the source map for the module if present.
11931199
if (result.sourceMapURL) {
11941200
maybeCacheSourceMap(filename, content, this, false, undefined, result.sourceMapURL);

lib/internal/util/embedding.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
'use strict';
2-
const { codes: { ERR_UNKNOWN_BUILTIN_MODULE } } = require('internal/errors');
32
const { BuiltinModule: { normalizeRequirableId } } = require('internal/bootstrap/realm');
43
const { Module, wrapSafe } = require('internal/modules/cjs/loader');
4+
const { codes: { ERR_UNKNOWN_BUILTIN_MODULE } } = require('internal/errors');
5+
const { getCodeCache } = internalBinding('sea');
56

67
// This is roughly the same as:
78
//
@@ -15,7 +16,7 @@ const { Module, wrapSafe } = require('internal/modules/cjs/loader');
1516

1617
function embedderRunCjs(contents) {
1718
const filename = process.execPath;
18-
const compiledWrapper = wrapSafe(filename, contents);
19+
const compiledWrapper = wrapSafe(filename, contents, undefined, getCodeCache());
1920

2021
const customModule = new Module(filename, null);
2122
customModule.filename = filename;

src/json_parser.cc

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,15 @@
44
#include "util-inl.h"
55

66
namespace node {
7-
using v8::ArrayBuffer;
87
using v8::Context;
98
using v8::Isolate;
109
using v8::Local;
1110
using v8::Object;
1211
using v8::String;
1312
using v8::Value;
1413

15-
static Isolate* NewIsolate(v8::ArrayBuffer::Allocator* allocator) {
16-
Isolate* isolate = Isolate::Allocate();
17-
CHECK_NOT_NULL(isolate);
18-
per_process::v8_platform.Platform()->RegisterIsolate(isolate,
19-
uv_default_loop());
20-
Isolate::CreateParams params;
21-
params.array_buffer_allocator = allocator;
22-
Isolate::Initialize(isolate, params);
23-
return isolate;
24-
}
25-
26-
void JSONParser::FreeIsolate(Isolate* isolate) {
27-
per_process::v8_platform.Platform()->UnregisterIsolate(isolate);
28-
isolate->Dispose();
29-
}
30-
3114
JSONParser::JSONParser()
32-
: allocator_(ArrayBuffer::Allocator::NewDefaultAllocator()),
33-
isolate_(NewIsolate(allocator_.get())),
34-
handle_scope_(isolate_.get()),
15+
: handle_scope_(isolate_.get()),
3516
context_(isolate_.get(), Context::New(isolate_.get())),
3617
context_scope_(context_.Get(isolate_.get())) {}
3718

src/json_parser.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ class JSONParser {
2424
private:
2525
// We might want a lighter-weight JSON parser for this use case. But for now
2626
// using V8 is good enough.
27-
static void FreeIsolate(v8::Isolate* isolate);
28-
std::unique_ptr<v8::ArrayBuffer::Allocator> allocator_;
29-
DeleteFnPtr<v8::Isolate, FreeIsolate> isolate_;
27+
RAIIIsolate isolate_;
3028
v8::HandleScope handle_scope_;
3129
v8::Global<v8::Context> context_;
3230
v8::Context::Scope context_scope_;

src/node_contextify.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,30 @@ Maybe<bool> StoreCodeCacheResult(
935935
return Just(true);
936936
}
937937

938+
// TODO(RaisinTen): Reuse in ContextifyContext::CompileFunction().
939+
MaybeLocal<Function> CompileFunction(Isolate* isolate,
940+
Local<Context> context,
941+
Local<String> filename,
942+
Local<String> content,
943+
std::vector<Local<String>> parameters) {
944+
ScriptOrigin script_origin(isolate, filename, 0, 0, true);
945+
ScriptCompiler::Source script_source(content, script_origin);
946+
947+
Local<Function> fn;
948+
if (!ScriptCompiler::CompileFunction(context,
949+
&script_source,
950+
parameters.size(),
951+
parameters.data(),
952+
0,
953+
nullptr,
954+
ScriptCompiler::kEagerCompile)
955+
.ToLocal(&fn)) {
956+
return {};
957+
}
958+
959+
return fn;
960+
}
961+
938962
bool ContextifyScript::InstanceOf(Environment* env,
939963
const Local<Value>& value) {
940964
return !value.IsEmpty() &&

src/node_contextify.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,13 @@ v8::Maybe<bool> StoreCodeCacheResult(
210210
bool produce_cached_data,
211211
std::unique_ptr<v8::ScriptCompiler::CachedData> new_cached_data);
212212

213+
v8::MaybeLocal<v8::Function> CompileFunction(
214+
v8::Isolate* isolate,
215+
v8::Local<v8::Context> context,
216+
v8::Local<v8::String> filename,
217+
v8::Local<v8::String> content,
218+
std::vector<v8::Local<v8::String>> parameters);
219+
213220
} // namespace contextify
214221
} // namespace node
215222

src/node_sea.cc

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
#include "debug_utils-inl.h"
55
#include "env-inl.h"
66
#include "json_parser.h"
7+
#include "node_contextify.h"
8+
#include "node_errors.h"
79
#include "node_external_reference.h"
810
#include "node_internals.h"
911
#include "node_union_bytes.h"
12+
#include "node_v8_platform-inl.h"
13+
#include "util-inl.h"
1014

1115
// The POSTJECT_SENTINEL_FUSE macro is a string of random characters selected by
1216
// the Node.js project that is present only once in the entire binary. It is
@@ -26,9 +30,14 @@
2630

2731
using node::ExitCode;
2832
using v8::Context;
33+
using v8::Function;
2934
using v8::FunctionCallbackInfo;
35+
using v8::HandleScope;
36+
using v8::Isolate;
3037
using v8::Local;
3138
using v8::Object;
39+
using v8::ScriptCompiler;
40+
using v8::String;
3241
using v8::Value;
3342

3443
namespace node {
@@ -78,6 +87,11 @@ size_t SeaSerializer::Write(const SeaResource& sea) {
7887
sea.code.data(),
7988
sea.code.size());
8089
written_total += WriteStringView(sea.code, StringLogMode::kAddressAndContent);
90+
91+
Debug("Write SEA resource code cache %p, size=%zu\n",
92+
sea.code_cache.data(),
93+
sea.code_cache.size());
94+
written_total += WriteStringView(sea.code_cache, StringLogMode::kAddressOnly);
8195
return written_total;
8296
}
8397

@@ -105,7 +119,12 @@ SeaResource SeaDeserializer::Read() {
105119

106120
std::string_view code = ReadStringView(StringLogMode::kAddressAndContent);
107121
Debug("Read SEA resource code %p, size=%zu\n", code.data(), code.size());
108-
return {flags, code};
122+
123+
std::string_view code_cache = ReadStringView(StringLogMode::kAddressOnly);
124+
Debug("Read SEA resource code cache %p, size=%zu\n",
125+
code_cache.data(),
126+
code_cache.size());
127+
return {flags, code, code_cache};
109128
}
110129

111130
std::string_view FindSingleExecutableBlob() {
@@ -161,6 +180,27 @@ void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo<Value>& args) {
161180
sea_resource.flags & SeaFlags::kDisableExperimentalSeaWarning));
162181
}
163182

183+
void GetCodeCache(const FunctionCallbackInfo<Value>& args) {
184+
if (!IsSingleExecutable()) {
185+
return;
186+
}
187+
188+
Environment* env = Environment::GetCurrent(args);
189+
Isolate* isolate = env->isolate();
190+
HandleScope scope(isolate);
191+
192+
SeaResource sea_resource = FindSingleExecutableResource();
193+
194+
Local<Object> buf =
195+
Buffer::Copy(
196+
env,
197+
reinterpret_cast<const char*>(sea_resource.code_cache.data()),
198+
sea_resource.code_cache.length())
199+
.ToLocalChecked();
200+
201+
args.GetReturnValue().Set(buf);
202+
}
203+
164204
std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv) {
165205
// Repeats argv[0] at position 1 on argv as a replacement for the missing
166206
// entry point file path.
@@ -238,6 +278,49 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
238278
return result;
239279
}
240280

281+
std::optional<std::string> GenerateCodeCache(std::string_view main_path,
282+
std::string_view main_script) {
283+
RAIIIsolate raii_isolate;
284+
Isolate* isolate = raii_isolate.get();
285+
286+
HandleScope handle_scope(isolate);
287+
Local<Context> context = Context::New(isolate);
288+
Context::Scope context_scope(context);
289+
290+
errors::PrinterTryCatch bootstrapCatch(
291+
isolate, errors::PrinterTryCatch::kPrintSourceLine);
292+
293+
Local<String> filename;
294+
if (!String::NewFromUtf8(isolate, main_path.data()).ToLocal(&filename)) {
295+
return {};
296+
}
297+
298+
Local<String> content;
299+
if (!String::NewFromUtf8(isolate, main_script.data()).ToLocal(&content)) {
300+
return {};
301+
}
302+
303+
std::vector<Local<String>> parameters = {
304+
FIXED_ONE_BYTE_STRING(isolate, "exports"),
305+
FIXED_ONE_BYTE_STRING(isolate, "require"),
306+
FIXED_ONE_BYTE_STRING(isolate, "module"),
307+
FIXED_ONE_BYTE_STRING(isolate, "__filename"),
308+
FIXED_ONE_BYTE_STRING(isolate, "__dirname"),
309+
};
310+
311+
Local<Function> fn;
312+
if (!contextify::CompileFunction(
313+
isolate, context, filename, content, std::move(parameters))
314+
.ToLocal(&fn)) {
315+
return {};
316+
}
317+
318+
std::unique_ptr<ScriptCompiler::CachedData> cache{
319+
ScriptCompiler::CreateCodeCacheForFunction(fn)};
320+
std::string code_cache(cache->data, cache->data + cache->length);
321+
return code_cache;
322+
}
323+
241324
ExitCode GenerateSingleExecutableBlob(const SeaConfig& config) {
242325
std::string main_script;
243326
// TODO(joyeecheung): unify the file utils.
@@ -248,7 +331,15 @@ ExitCode GenerateSingleExecutableBlob(const SeaConfig& config) {
248331
return ExitCode::kGenericUserError;
249332
}
250333

251-
SeaResource sea{config.flags, main_script};
334+
std::optional<std::string> optional_code_cache =
335+
GenerateCodeCache(config.main_path, main_script);
336+
if (!optional_code_cache.has_value()) {
337+
FPrintF(stderr, "Cannot generate V8 code cache\n");
338+
return ExitCode::kGenericUserError;
339+
}
340+
std::string code_cache = optional_code_cache.value();
341+
342+
SeaResource sea{config.flags, main_script, code_cache};
252343

253344
SeaSerializer serializer;
254345
serializer.Write(sea);
@@ -288,10 +379,12 @@ void Initialize(Local<Object> target,
288379
target,
289380
"isExperimentalSeaWarningNeeded",
290381
IsExperimentalSeaWarningNeeded);
382+
SetMethod(context, target, "getCodeCache", GetCodeCache);
291383
}
292384

293385
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
294386
registry->Register(IsExperimentalSeaWarningNeeded);
387+
registry->Register(GetCodeCache);
295388
}
296389

297390
} // namespace sea

src/node_sea.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ enum class SeaFlags : uint32_t {
2626
struct SeaResource {
2727
SeaFlags flags = SeaFlags::kDefault;
2828
std::string_view code;
29+
std::string_view code_cache;
2930

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

src/node_snapshotable.cc

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ using v8::MaybeLocal;
4242
using v8::Object;
4343
using v8::ObjectTemplate;
4444
using v8::ScriptCompiler;
45-
using v8::ScriptOrigin;
4645
using v8::SnapshotCreator;
4746
using v8::StartupData;
4847
using v8::String;
@@ -1254,23 +1253,16 @@ void CompileSerializeMain(const FunctionCallbackInfo<Value>& args) {
12541253
Local<String> source = args[1].As<String>();
12551254
Isolate* isolate = args.GetIsolate();
12561255
Local<Context> context = isolate->GetCurrentContext();
1257-
ScriptOrigin origin(isolate, filename, 0, 0, true);
12581256
// TODO(joyeecheung): do we need all of these? Maybe we would want a less
12591257
// internal version of them.
12601258
std::vector<Local<String>> parameters = {
12611259
FIXED_ONE_BYTE_STRING(isolate, "require"),
12621260
FIXED_ONE_BYTE_STRING(isolate, "__filename"),
12631261
FIXED_ONE_BYTE_STRING(isolate, "__dirname"),
12641262
};
1265-
ScriptCompiler::Source script_source(source, origin);
12661263
Local<Function> fn;
1267-
if (ScriptCompiler::CompileFunction(context,
1268-
&script_source,
1269-
parameters.size(),
1270-
parameters.data(),
1271-
0,
1272-
nullptr,
1273-
ScriptCompiler::kEagerCompile)
1264+
if (contextify::CompileFunction(
1265+
isolate, context, filename, source, std::move(parameters))
12741266
.ToLocal(&fn)) {
12751267
args.GetReturnValue().Set(fn);
12761268
}

src/util.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "node_errors.h"
2929
#include "node_internals.h"
3030
#include "node_util.h"
31+
#include "node_v8_platform-inl.h"
3132
#include "string_bytes.h"
3233
#include "uv.h"
3334

@@ -55,6 +56,7 @@ static std::atomic_int seq = {0}; // Sequence number for diagnostic filenames.
5556

5657
namespace node {
5758

59+
using v8::ArrayBuffer;
5860
using v8::ArrayBufferView;
5961
using v8::Context;
6062
using v8::FunctionTemplate;
@@ -618,4 +620,20 @@ Local<String> UnionBytes::ToStringChecked(Isolate* isolate) const {
618620
}
619621
}
620622

623+
RAIIIsolate::RAIIIsolate()
624+
: allocator_{ArrayBuffer::Allocator::NewDefaultAllocator()} {
625+
isolate_ = Isolate::Allocate();
626+
CHECK_NOT_NULL(isolate_);
627+
per_process::v8_platform.Platform()->RegisterIsolate(isolate_,
628+
uv_default_loop());
629+
Isolate::CreateParams params;
630+
params.array_buffer_allocator = allocator_.get();
631+
Isolate::Initialize(isolate_, params);
632+
}
633+
634+
RAIIIsolate::~RAIIIsolate() {
635+
per_process::v8_platform.Platform()->UnregisterIsolate(isolate_);
636+
isolate_->Dispose();
637+
}
638+
621639
} // namespace node

0 commit comments

Comments
 (0)