Skip to content

Commit 683a1f8

Browse files
addaleaxMylesBorins
authored andcommitted
src: allow snapshotting from the embedder API
PR-URL: #45888 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Joyee Cheung <[email protected]>
1 parent 658d2f4 commit 683a1f8

13 files changed

+307
-197
lines changed

lib/internal/main/mksnapshot.js

+17-10
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const {
1212
const binding = internalBinding('mksnapshot');
1313
const { BuiltinModule } = require('internal/bootstrap/loaders');
1414
const {
15+
getEmbedderEntryFunction,
1516
compileSerializeMain,
1617
} = binding;
1718

@@ -119,14 +120,21 @@ function main() {
119120
prepareMainThreadExecution
120121
} = require('internal/process/pre_execution');
121122

122-
prepareMainThreadExecution(true, false);
123+
let serializeMainFunction = getEmbedderEntryFunction();
124+
const serializeMainArgs = [requireForUserSnapshot];
123125

124-
const file = process.argv[1];
125-
const path = require('path');
126-
const filename = path.resolve(file);
127-
const dirname = path.dirname(filename);
128-
const source = readFileSync(file, 'utf-8');
129-
const serializeMainFunction = compileSerializeMain(filename, source);
126+
if (serializeMainFunction) { // embedded case
127+
prepareMainThreadExecution(false, false);
128+
} else {
129+
prepareMainThreadExecution(true, false);
130+
const file = process.argv[1];
131+
const path = require('path');
132+
const filename = path.resolve(file);
133+
const dirname = path.dirname(filename);
134+
const source = readFileSync(file, 'utf-8');
135+
serializeMainFunction = compileSerializeMain(filename, source);
136+
serializeMainArgs.push(filename, dirname);
137+
}
130138

131139
const {
132140
initializeCallbacks,
@@ -146,10 +154,9 @@ function main() {
146154

147155
if (getOptionValue('--inspect-brk')) {
148156
internalBinding('inspector').callAndPauseOnStart(
149-
serializeMainFunction, undefined,
150-
requireForUserSnapshot, filename, dirname);
157+
serializeMainFunction, undefined, ...serializeMainArgs);
151158
} else {
152-
serializeMainFunction(requireForUserSnapshot, filename, dirname);
159+
serializeMainFunction(...serializeMainArgs);
153160
}
154161

155162
addSerializeCallback(() => {

src/api/embed_helpers.cc

+85-11
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ using v8::Locker;
1414
using v8::Maybe;
1515
using v8::Nothing;
1616
using v8::SealHandleScope;
17+
using v8::SnapshotCreator;
1718

1819
namespace node {
1920

@@ -78,16 +79,18 @@ struct CommonEnvironmentSetup::Impl {
7879
MultiIsolatePlatform* platform = nullptr;
7980
uv_loop_t loop;
8081
std::shared_ptr<ArrayBufferAllocator> allocator;
82+
std::optional<SnapshotCreator> snapshot_creator;
8183
Isolate* isolate = nullptr;
8284
DeleteFnPtr<IsolateData, FreeIsolateData> isolate_data;
8385
DeleteFnPtr<Environment, FreeEnvironment> env;
84-
Global<Context> context;
86+
Global<Context> main_context;
8587
};
8688

8789
CommonEnvironmentSetup::CommonEnvironmentSetup(
8890
MultiIsolatePlatform* platform,
8991
std::vector<std::string>* errors,
9092
const EmbedderSnapshotData* snapshot_data,
93+
uint32_t flags,
9194
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
9295
: impl_(new Impl()) {
9396
CHECK_NOT_NULL(platform);
@@ -105,28 +108,43 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
105108
}
106109
loop->data = this;
107110

108-
impl_->allocator = ArrayBufferAllocator::Create();
109-
impl_->isolate =
110-
NewIsolate(impl_->allocator, &impl_->loop, platform, snapshot_data);
111-
Isolate* isolate = impl_->isolate;
111+
Isolate* isolate;
112+
if (flags & Flags::kIsForSnapshotting) {
113+
const std::vector<intptr_t>& external_references =
114+
SnapshotBuilder::CollectExternalReferences();
115+
isolate = impl_->isolate = Isolate::Allocate();
116+
// Must be done before the SnapshotCreator creation so that the
117+
// memory reducer can be initialized.
118+
platform->RegisterIsolate(isolate, loop);
119+
impl_->snapshot_creator.emplace(isolate, external_references.data());
120+
isolate->SetCaptureStackTraceForUncaughtExceptions(
121+
true, 10, v8::StackTrace::StackTraceOptions::kDetailed);
122+
SetIsolateMiscHandlers(isolate, {});
123+
} else {
124+
impl_->allocator = ArrayBufferAllocator::Create();
125+
isolate = impl_->isolate =
126+
NewIsolate(impl_->allocator, &impl_->loop, platform, snapshot_data);
127+
}
112128

113129
{
114130
Locker locker(isolate);
115131
Isolate::Scope isolate_scope(isolate);
116132
impl_->isolate_data.reset(CreateIsolateData(
117133
isolate, loop, platform, impl_->allocator.get(), snapshot_data));
134+
impl_->isolate_data->options()->build_snapshot =
135+
impl_->snapshot_creator.has_value();
118136

119137
HandleScope handle_scope(isolate);
120138
if (snapshot_data) {
121139
impl_->env.reset(make_env(this));
122140
if (impl_->env) {
123-
impl_->context.Reset(isolate, impl_->env->context());
141+
impl_->main_context.Reset(isolate, impl_->env->context());
124142
}
125143
return;
126144
}
127145

128146
Local<Context> context = NewContext(isolate);
129-
impl_->context.Reset(isolate, context);
147+
impl_->main_context.Reset(isolate, context);
130148
if (context.IsEmpty()) {
131149
errors->push_back("Failed to initialize V8 Context");
132150
return;
@@ -141,7 +159,37 @@ CommonEnvironmentSetup::CommonEnvironmentSetup(
141159
MultiIsolatePlatform* platform,
142160
std::vector<std::string>* errors,
143161
std::function<Environment*(const CommonEnvironmentSetup*)> make_env)
144-
: CommonEnvironmentSetup(platform, errors, nullptr, make_env) {}
162+
: CommonEnvironmentSetup(platform, errors, nullptr, false, make_env) {}
163+
164+
std::unique_ptr<CommonEnvironmentSetup>
165+
CommonEnvironmentSetup::CreateForSnapshotting(
166+
MultiIsolatePlatform* platform,
167+
std::vector<std::string>* errors,
168+
const std::vector<std::string>& args,
169+
const std::vector<std::string>& exec_args) {
170+
// It's not guaranteed that a context that goes through
171+
// v8_inspector::V8Inspector::contextCreated() is runtime-independent,
172+
// so do not start the inspector on the main context when building
173+
// the default snapshot.
174+
uint64_t env_flags =
175+
EnvironmentFlags::kDefaultFlags | EnvironmentFlags::kNoCreateInspector;
176+
177+
auto ret = std::unique_ptr<CommonEnvironmentSetup>(new CommonEnvironmentSetup(
178+
platform,
179+
errors,
180+
nullptr,
181+
true,
182+
[&](const CommonEnvironmentSetup* setup) -> Environment* {
183+
return CreateEnvironment(
184+
setup->isolate_data(),
185+
setup->context(),
186+
args,
187+
exec_args,
188+
static_cast<EnvironmentFlags::Flags>(env_flags));
189+
}));
190+
if (!errors->empty()) ret.reset();
191+
return ret;
192+
}
145193

146194
CommonEnvironmentSetup::~CommonEnvironmentSetup() {
147195
if (impl_->isolate != nullptr) {
@@ -150,7 +198,7 @@ CommonEnvironmentSetup::~CommonEnvironmentSetup() {
150198
Locker locker(isolate);
151199
Isolate::Scope isolate_scope(isolate);
152200

153-
impl_->context.Reset();
201+
impl_->main_context.Reset();
154202
impl_->env.reset();
155203
impl_->isolate_data.reset();
156204
}
@@ -160,7 +208,10 @@ CommonEnvironmentSetup::~CommonEnvironmentSetup() {
160208
*static_cast<bool*>(data) = true;
161209
}, &platform_finished);
162210
impl_->platform->UnregisterIsolate(isolate);
163-
isolate->Dispose();
211+
if (impl_->snapshot_creator.has_value())
212+
impl_->snapshot_creator.reset();
213+
else
214+
isolate->Dispose();
164215

165216
// Wait until the platform has cleaned up all relevant resources.
166217
while (!platform_finished)
@@ -173,6 +224,21 @@ CommonEnvironmentSetup::~CommonEnvironmentSetup() {
173224
delete impl_;
174225
}
175226

227+
EmbedderSnapshotData::Pointer CommonEnvironmentSetup::CreateSnapshot() {
228+
CHECK_NOT_NULL(snapshot_creator());
229+
SnapshotData* snapshot_data = new SnapshotData();
230+
EmbedderSnapshotData::Pointer result{
231+
new EmbedderSnapshotData(snapshot_data, true)};
232+
233+
auto exit_code = SnapshotBuilder::CreateSnapshot(
234+
snapshot_data,
235+
this,
236+
static_cast<uint8_t>(SnapshotMetadata::Type::kFullyCustomized));
237+
if (exit_code != ExitCode::kNoFailure) return {};
238+
239+
return result;
240+
}
241+
176242
Maybe<int> SpinEventLoop(Environment* env) {
177243
Maybe<ExitCode> result = SpinEventLoopInternal(env);
178244
if (result.IsNothing()) {
@@ -203,7 +269,11 @@ Environment* CommonEnvironmentSetup::env() const {
203269
}
204270

205271
v8::Local<v8::Context> CommonEnvironmentSetup::context() const {
206-
return impl_->context.Get(impl_->isolate);
272+
return impl_->main_context.Get(impl_->isolate);
273+
}
274+
275+
v8::SnapshotCreator* CommonEnvironmentSetup::snapshot_creator() {
276+
return impl_->snapshot_creator ? &impl_->snapshot_creator.value() : nullptr;
207277
}
208278

209279
void EmbedderSnapshotData::DeleteSnapshotData::operator()(
@@ -232,6 +302,10 @@ EmbedderSnapshotData::Pointer EmbedderSnapshotData::FromFile(FILE* in) {
232302
return result;
233303
}
234304

305+
void EmbedderSnapshotData::ToFile(FILE* out) const {
306+
impl_->ToBlob(out);
307+
}
308+
235309
EmbedderSnapshotData::EmbedderSnapshotData(const SnapshotData* impl,
236310
bool owns_impl)
237311
: impl_(impl), owns_impl_(owns_impl) {}

src/env-inl.h

+10
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,16 @@ inline builtins::BuiltinLoader* Environment::builtin_loader() {
438438
return &builtin_loader_;
439439
}
440440

441+
inline const StartExecutionCallback&
442+
Environment::embedder_mksnapshot_entry_point() const {
443+
return embedder_mksnapshot_entry_point_;
444+
}
445+
446+
inline void Environment::set_embedder_mksnapshot_entry_point(
447+
StartExecutionCallback&& fn) {
448+
embedder_mksnapshot_entry_point_ = std::move(fn);
449+
}
450+
441451
inline double Environment::new_async_id() {
442452
async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter] += 1;
443453
return async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter];

src/env.h

+4
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,9 @@ class Environment : public MemoryRetainer {
959959

960960
#endif // HAVE_INSPECTOR
961961

962+
inline const StartExecutionCallback& embedder_mksnapshot_entry_point() const;
963+
inline void set_embedder_mksnapshot_entry_point(StartExecutionCallback&& fn);
964+
962965
inline void set_process_exit_handler(
963966
std::function<void(Environment*, ExitCode)>&& handler);
964967

@@ -1134,6 +1137,7 @@ class Environment : public MemoryRetainer {
11341137
std::unique_ptr<Realm> principal_realm_ = nullptr;
11351138

11361139
builtins::BuiltinLoader builtin_loader_;
1140+
StartExecutionCallback embedder_mksnapshot_entry_point_;
11371141

11381142
// Used by allocate_managed_buffer() and release_managed_buffer() to keep
11391143
// track of the BackingStore for a given pointer.

src/node.cc

+12-5
Original file line numberDiff line numberDiff line change
@@ -276,14 +276,21 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
276276
if (cb != nullptr) {
277277
EscapableHandleScope scope(env->isolate());
278278

279-
if (StartExecution(env, "internal/main/environment").IsEmpty()) return {};
279+
if (env->isolate_data()->options()->build_snapshot) {
280+
// TODO(addaleax): pass the callback to the main script more directly,
281+
// e.g. by making StartExecution(env, builtin) parametrizable
282+
env->set_embedder_mksnapshot_entry_point(std::move(cb));
283+
auto reset_entry_point =
284+
OnScopeLeave([&]() { env->set_embedder_mksnapshot_entry_point({}); });
285+
286+
return StartExecution(env, "internal/main/mksnapshot");
287+
}
280288

281-
StartExecutionCallbackInfo info = {
289+
if (StartExecution(env, "internal/main/environment").IsEmpty()) return {};
290+
return scope.EscapeMaybe(cb({
282291
env->process_object(),
283292
env->builtin_module_require(),
284-
};
285-
286-
return scope.EscapeMaybe(cb(info));
293+
}));
287294
}
288295

289296
// TODO(joyeecheung): move these conditions into JS land and let the

0 commit comments

Comments
 (0)