Skip to content

Commit 718f62b

Browse files
committed
bootstrap: unify snapshot builder and embedder entry points
- Run the embedder entry point directly through runEmbedderEntryPoint(), instead of going through another JS -> C++ trip through the function returned by getEmbedderEntryFunction() - For --build-snapshot, read the snapshot script code directly in C++ and pass it to SnapshotBuilder::Generate(), this makes the entry point more explicit instead of hiding it in JS land, and also makes it possible to invoke SnapshotBuilder::Generate() internally to create a custom snapshot. - Previously we used process.execPath for the embedder to create __filename and __dirname in the snapshot builder script while using process.argv[1] for --build-snapshot (where it's always set) which results in inconsistencies. We now require the embedder to also set args[1] when creating the Environment if they intend to run snapshot scripts with a context that contains __filename and __dirname, which would be derived from args[1]. If they prefer not to include build-time paths in the snapshot, we now provide node::GetAnonymousMainPath() as an alternative. PR-URL: #48242 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent ad0bbaf commit 718f62b

File tree

9 files changed

+198
-138
lines changed

9 files changed

+198
-138
lines changed

lib/internal/main/embedding.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const {
66
const { isExperimentalSeaWarningNeeded } = internalBinding('sea');
77
const { emitExperimentalWarning } = require('internal/util');
88
const { embedderRequire, embedderRunCjs } = require('internal/util/embedding');
9-
const { getEmbedderEntryFunction } = internalBinding('mksnapshot');
9+
const { runEmbedderEntryPoint } = internalBinding('mksnapshot');
1010

1111
prepareMainThreadExecution(false, true);
1212
markBootstrapComplete();
@@ -15,4 +15,4 @@ if (isExperimentalSeaWarningNeeded()) {
1515
emitExperimentalWarning('Single executable application');
1616
}
1717

18-
return getEmbedderEntryFunction()(embedderRequire, embedderRunCjs);
18+
return runEmbedderEntryPoint(process, embedderRequire, embedderRunCjs);

lib/internal/main/mksnapshot.js

+41-49
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,30 @@ const {
99
SafeSet,
1010
} = primordials;
1111

12-
const binding = internalBinding('mksnapshot');
1312
const { BuiltinModule: { normalizeRequirableId } } = require('internal/bootstrap/realm');
1413
const {
15-
getEmbedderEntryFunction,
14+
runEmbedderEntryPoint,
1615
compileSerializeMain,
17-
} = binding;
16+
anonymousMainPath,
17+
} = internalBinding('mksnapshot');
1818

1919
const {
2020
getOptionValue,
2121
} = require('internal/options');
2222

2323
const {
24-
readFileSync,
25-
} = require('fs');
24+
initializeCallbacks,
25+
namespace: {
26+
addSerializeCallback,
27+
addDeserializeCallback,
28+
},
29+
} = require('internal/v8/startup_snapshot');
30+
31+
const {
32+
prepareMainThreadExecution,
33+
} = require('internal/process/pre_execution');
34+
35+
const path = require('path');
2636

2737
const supportedModules = new SafeSet(new SafeArrayIterator([
2838
// '_http_agent',
@@ -117,42 +127,7 @@ function requireForUserSnapshot(id) {
117127
}
118128

119129
function main() {
120-
const {
121-
prepareMainThreadExecution,
122-
} = require('internal/process/pre_execution');
123-
const path = require('path');
124-
125-
let serializeMainFunction = getEmbedderEntryFunction();
126-
const serializeMainArgs = [requireForUserSnapshot];
127-
128-
if (serializeMainFunction) { // embedded case
129-
prepareMainThreadExecution(false, false);
130-
// TODO(addaleax): Make this `embedderRunCjs` once require('module')
131-
// is supported in snapshots.
132-
const filename = process.execPath;
133-
const dirname = path.dirname(filename);
134-
function minimalRunCjs(source) {
135-
const fn = compileSerializeMain(filename, source);
136-
return fn(requireForUserSnapshot, filename, dirname);
137-
}
138-
serializeMainArgs.push(minimalRunCjs);
139-
} else {
140-
prepareMainThreadExecution(true, false);
141-
const file = process.argv[1];
142-
const filename = path.resolve(file);
143-
const dirname = path.dirname(filename);
144-
const source = readFileSync(file, 'utf-8');
145-
serializeMainFunction = compileSerializeMain(filename, source);
146-
serializeMainArgs.push(filename, dirname);
147-
}
148-
149-
const {
150-
initializeCallbacks,
151-
namespace: {
152-
addSerializeCallback,
153-
addDeserializeCallback,
154-
},
155-
} = require('internal/v8/startup_snapshot');
130+
prepareMainThreadExecution(true, false);
156131
initializeCallbacks();
157132

158133
let stackTraceLimitDesc;
@@ -161,14 +136,6 @@ function main() {
161136
ObjectDefineProperty(Error, 'stackTraceLimit', stackTraceLimitDesc);
162137
}
163138
});
164-
165-
if (getOptionValue('--inspect-brk')) {
166-
internalBinding('inspector').callAndPauseOnStart(
167-
serializeMainFunction, undefined, ...serializeMainArgs);
168-
} else {
169-
serializeMainFunction(...serializeMainArgs);
170-
}
171-
172139
addSerializeCallback(() => {
173140
stackTraceLimitDesc = ObjectGetOwnPropertyDescriptor(Error, 'stackTraceLimit');
174141

@@ -181,6 +148,31 @@ function main() {
181148
delete Error.stackTraceLimit;
182149
}
183150
});
151+
152+
// TODO(addaleax): Make this `embedderRunCjs` once require('module')
153+
// is supported in snapshots.
154+
function minimalRunCjs(source) {
155+
let filename;
156+
let dirname;
157+
if (process.argv[1] === anonymousMainPath) {
158+
filename = dirname = process.argv[1];
159+
} else {
160+
filename = path.resolve(process.argv[1]);
161+
dirname = path.dirname(filename);
162+
}
163+
164+
const fn = compileSerializeMain(filename, source);
165+
return fn(requireForUserSnapshot, filename, dirname);
166+
}
167+
168+
const serializeMainArgs = [process, requireForUserSnapshot, minimalRunCjs];
169+
170+
if (getOptionValue('--inspect-brk')) {
171+
internalBinding('inspector').callAndPauseOnStart(
172+
runEmbedderEntryPoint, undefined, ...serializeMainArgs);
173+
} else {
174+
runEmbedderEntryPoint(...serializeMainArgs);
175+
}
184176
}
185177

186178
main();

src/node.cc

+19-8
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,8 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
290290
return scope.EscapeMaybe(StartExecution(env, entry));
291291
}
292292

293+
CHECK(!env->isolate_data()->is_building_snapshot());
294+
293295
// TODO(joyeecheung): move these conditions into JS land and let the
294296
// deserialize main function take precedence. For workers, we need to
295297
// move the pre-execution part into a different file that can be
@@ -311,15 +313,10 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
311313
return StartExecution(env, "internal/main/inspect");
312314
}
313315

314-
if (env->isolate_data()->is_building_snapshot()) {
315-
return StartExecution(env, "internal/main/mksnapshot");
316-
}
317-
318316
if (per_process::cli_options->print_help) {
319317
return StartExecution(env, "internal/main/print_help");
320318
}
321319

322-
323320
if (env->options()->prof_process) {
324321
return StartExecution(env, "internal/main/prof_process");
325322
}
@@ -1119,7 +1116,8 @@ ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr,
11191116

11201117
// node:embedded_snapshot_main indicates that we are using the
11211118
// embedded snapshot and we are not supposed to clean it up.
1122-
if (result->args()[1] == "node:embedded_snapshot_main") {
1119+
const std::string& main_script = result->args()[1];
1120+
if (main_script == "node:embedded_snapshot_main") {
11231121
*snapshot_data_ptr = SnapshotBuilder::GetEmbeddedSnapshotData();
11241122
if (*snapshot_data_ptr == nullptr) {
11251123
// The Node.js binary is built without embedded snapshot
@@ -1134,8 +1132,21 @@ ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr,
11341132
// Otherwise, load and run the specified main script.
11351133
std::unique_ptr<SnapshotData> generated_data =
11361134
std::make_unique<SnapshotData>();
1137-
exit_code = node::SnapshotBuilder::Generate(
1138-
generated_data.get(), result->args(), result->exec_args());
1135+
std::string main_script_content;
1136+
int r = ReadFileSync(&main_script_content, main_script.c_str());
1137+
if (r != 0) {
1138+
FPrintF(stderr,
1139+
"Cannot read main script %s for building snapshot. %s: %s",
1140+
main_script,
1141+
uv_err_name(r),
1142+
uv_strerror(r));
1143+
return ExitCode::kGenericUserError;
1144+
}
1145+
1146+
exit_code = node::SnapshotBuilder::Generate(generated_data.get(),
1147+
result->args(),
1148+
result->exec_args(),
1149+
main_script_content);
11391150
if (exit_code == ExitCode::kNoFailure) {
11401151
*snapshot_data_ptr = generated_data.release();
11411152
} else {

src/node.h

+10
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,8 @@ NODE_EXTERN struct uv_loop_s* GetCurrentEventLoop(v8::Isolate* isolate);
816816
// This function only works if `env` has an associated `MultiIsolatePlatform`.
817817
NODE_EXTERN v8::Maybe<int> SpinEventLoop(Environment* env);
818818

819+
NODE_EXTERN std::string GetAnonymousMainPath();
820+
819821
class NODE_EXTERN CommonEnvironmentSetup {
820822
public:
821823
~CommonEnvironmentSetup();
@@ -848,6 +850,13 @@ class NODE_EXTERN CommonEnvironmentSetup {
848850
// no support for native/host objects other than Node.js builtins
849851
// in the snapshot.
850852
//
853+
// If the embedder wants to use LoadEnvironment() later to run a snapshot
854+
// builder script they should make sure args[1] contains the path of the
855+
// snapshot script, which will be used to create __filename and __dirname
856+
// in the context where the builder script is run. If they do not want to
857+
// include the build-time paths into the snapshot, use the string returned
858+
// by GetAnonymousMainPath() as args[1] to anonymize the script.
859+
//
851860
// Snapshots are an *experimental* feature. In particular, the embedder API
852861
// exposed through this class is subject to change or removal between Node.js
853862
// versions, including possible API and ABI breakage.
@@ -909,6 +918,7 @@ std::unique_ptr<CommonEnvironmentSetup> CommonEnvironmentSetup::Create(
909918
if (!errors->empty()) ret.reset();
910919
return ret;
911920
}
921+
912922
// Implementation for ::CreateFromSnapshot -- the ::Create() method
913923
// could call this with a nullptr snapshot_data in a major version.
914924
template <typename... EnvironmentArgs>

src/node_snapshot_builder.h

+8-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
66

77
#include <cstdint>
8+
#include <optional>
9+
#include <string_view>
810
#include "node_exit_code.h"
911
#include "node_mutex.h"
1012
#include "v8.h"
@@ -17,13 +19,15 @@ struct SnapshotData;
1719
class NODE_EXTERN_PRIVATE SnapshotBuilder {
1820
public:
1921
static ExitCode Generate(std::ostream& out,
20-
const std::vector<std::string> args,
21-
const std::vector<std::string> exec_args);
22+
const std::vector<std::string>& args,
23+
const std::vector<std::string>& exec_args,
24+
std::optional<std::string_view> main_script);
2225

2326
// Generate the snapshot into out.
2427
static ExitCode Generate(SnapshotData* out,
25-
const std::vector<std::string> args,
26-
const std::vector<std::string> exec_args);
28+
const std::vector<std::string>& args,
29+
const std::vector<std::string>& exec_args,
30+
std::optional<std::string_view> main_script);
2731

2832
// If nullptr is returned, the binary is not built with embedded
2933
// snapshot.

0 commit comments

Comments
 (0)