Skip to content

Commit

Permalink
bootstrap: unify snapshot builder and embedder entry points
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
joyeecheung committed May 29, 2023
1 parent c0365fd commit 47b4ca1
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 136 deletions.
4 changes: 2 additions & 2 deletions lib/internal/main/embedding.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const {
const { isExperimentalSeaWarningNeeded } = internalBinding('sea');
const { emitExperimentalWarning } = require('internal/util');
const { embedderRequire, embedderRunCjs } = require('internal/util/embedding');
const { getEmbedderEntryFunction } = internalBinding('mksnapshot');
const { runEmbedderEntryPoint } = internalBinding('mksnapshot');

prepareMainThreadExecution(false, true);
markBootstrapComplete();
Expand All @@ -15,4 +15,4 @@ if (isExperimentalSeaWarningNeeded()) {
emitExperimentalWarning('Single executable application');
}

return getEmbedderEntryFunction()(embedderRequire, embedderRunCjs);
return runEmbedderEntryPoint(process, embedderRequire, embedderRunCjs);
90 changes: 41 additions & 49 deletions lib/internal/main/mksnapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,30 @@ const {
SafeSet,
} = primordials;

const binding = internalBinding('mksnapshot');
const { BuiltinModule: { normalizeRequirableId } } = require('internal/bootstrap/realm');
const {
getEmbedderEntryFunction,
runEmbedderEntryPoint,
compileSerializeMain,
} = binding;
anonymousMainPath,
} = internalBinding('mksnapshot');;

const {
getOptionValue,
} = require('internal/options');

const {
readFileSync,
} = require('fs');
initializeCallbacks,
namespace: {
addSerializeCallback,
addDeserializeCallback,
},
} = require('internal/v8/startup_snapshot');

const {
prepareMainThreadExecution,
} = require('internal/process/pre_execution');

const path = require('path');

const supportedModules = new SafeSet(new SafeArrayIterator([
// '_http_agent',
Expand Down Expand Up @@ -117,42 +127,7 @@ function requireForUserSnapshot(id) {
}

function main() {
const {
prepareMainThreadExecution,
} = require('internal/process/pre_execution');
const path = require('path');

let serializeMainFunction = getEmbedderEntryFunction();
const serializeMainArgs = [requireForUserSnapshot];

if (serializeMainFunction) { // embedded case
prepareMainThreadExecution(false, false);
// TODO(addaleax): Make this `embedderRunCjs` once require('module')
// is supported in snapshots.
const filename = process.execPath;
const dirname = path.dirname(filename);
function minimalRunCjs(source) {
const fn = compileSerializeMain(filename, source);
return fn(requireForUserSnapshot, filename, dirname);
}
serializeMainArgs.push(minimalRunCjs);
} else {
prepareMainThreadExecution(true, false);
const file = process.argv[1];
const filename = path.resolve(file);
const dirname = path.dirname(filename);
const source = readFileSync(file, 'utf-8');
serializeMainFunction = compileSerializeMain(filename, source);
serializeMainArgs.push(filename, dirname);
}

const {
initializeCallbacks,
namespace: {
addSerializeCallback,
addDeserializeCallback,
},
} = require('internal/v8/startup_snapshot');
prepareMainThreadExecution(true, false);
initializeCallbacks();

let stackTraceLimitDesc;
Expand All @@ -161,14 +136,6 @@ function main() {
ObjectDefineProperty(Error, 'stackTraceLimit', stackTraceLimitDesc);
}
});

if (getOptionValue('--inspect-brk')) {
internalBinding('inspector').callAndPauseOnStart(
serializeMainFunction, undefined, ...serializeMainArgs);
} else {
serializeMainFunction(...serializeMainArgs);
}

addSerializeCallback(() => {
stackTraceLimitDesc = ObjectGetOwnPropertyDescriptor(Error, 'stackTraceLimit');

Expand All @@ -181,6 +148,31 @@ function main() {
delete Error.stackTraceLimit;
}
});

// TODO(addaleax): Make this `embedderRunCjs` once require('module')
// is supported in snapshots.
function minimalRunCjs(source) {
let filename;
let dirname;
if (process.argv[1] === anonymousMainPath) {
filename = dirname = process.argv[1];
} else {
filename = path.resolve(process.argv[1]);
dirname = path.dirname(filename);
}

const fn = compileSerializeMain(filename, source);
return fn(requireForUserSnapshot, filename, dirname);
}

const serializeMainArgs = [process, requireForUserSnapshot, minimalRunCjs];

if (getOptionValue('--inspect-brk')) {
internalBinding('inspector').callAndPauseOnStart(
runEmbedderEntryPoint, undefined, ...serializeMainArgs);
} else {
runEmbedderEntryPoint(...serializeMainArgs);
}
}

main();
27 changes: 19 additions & 8 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
return scope.EscapeMaybe(StartExecution(env, entry));
}

CHECK(!env->isolate_data()->is_building_snapshot());

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

if (env->isolate_data()->is_building_snapshot()) {
return StartExecution(env, "internal/main/mksnapshot");
}

if (per_process::cli_options->print_help) {
return StartExecution(env, "internal/main/print_help");
}


if (env->options()->prof_process) {
return StartExecution(env, "internal/main/prof_process");
}
Expand Down Expand Up @@ -1118,7 +1115,8 @@ ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr,

// node:embedded_snapshot_main indicates that we are using the
// embedded snapshot and we are not supposed to clean it up.
if (result->args()[1] == "node:embedded_snapshot_main") {
const std::string& main_script = result->args()[1];
if (main_script == "node:embedded_snapshot_main") {
*snapshot_data_ptr = SnapshotBuilder::GetEmbeddedSnapshotData();
if (*snapshot_data_ptr == nullptr) {
// The Node.js binary is built without embedded snapshot
Expand All @@ -1133,8 +1131,21 @@ ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr,
// Otherwise, load and run the specified main script.
std::unique_ptr<SnapshotData> generated_data =
std::make_unique<SnapshotData>();
exit_code = node::SnapshotBuilder::Generate(
generated_data.get(), result->args(), result->exec_args());
std::string main_script_content;
int r = ReadFileSync(&main_script_content, main_script.c_str());
if (r != 0) {
FPrintF(stderr,
"Cannot read main script %s for building snapshot. %s: %s",
main_script,
uv_err_name(r),
uv_strerror(r));
return ExitCode::kGenericUserError;
}

exit_code = node::SnapshotBuilder::Generate(generated_data.get(),
result->args(),
result->exec_args(),
main_script_content);
if (exit_code == ExitCode::kNoFailure) {
*snapshot_data_ptr = generated_data.release();
} else {
Expand Down
10 changes: 10 additions & 0 deletions src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,8 @@ NODE_EXTERN struct uv_loop_s* GetCurrentEventLoop(v8::Isolate* isolate);
// This function only works if `env` has an associated `MultiIsolatePlatform`.
NODE_EXTERN v8::Maybe<int> SpinEventLoop(Environment* env);

NODE_EXTERN std::string GetAnonymousMainPath();

class NODE_EXTERN CommonEnvironmentSetup {
public:
~CommonEnvironmentSetup();
Expand Down Expand Up @@ -848,6 +850,13 @@ class NODE_EXTERN CommonEnvironmentSetup {
// no support for native/host objects other than Node.js builtins
// in the snapshot.
//
// If the embedder wants to use LoadEnvironment() later to run a snapshot
// builder script they should make sure args[1] contains the path of the
// snapshot script, which will be used to create __filename and __dirname
// in the context where the builder script is run. If they do not want to
// include the build-time paths into the snapshot, use the string returned
// by GetAnonymousMainPath() as args[1] to anonymize the script.
//
// Snapshots are an *experimental* feature. In particular, the embedder API
// exposed through this class is subject to change or removal between Node.js
// versions, including possible API and ABI breakage.
Expand Down Expand Up @@ -909,6 +918,7 @@ std::unique_ptr<CommonEnvironmentSetup> CommonEnvironmentSetup::Create(
if (!errors->empty()) ret.reset();
return ret;
}

// Implementation for ::CreateFromSnapshot -- the ::Create() method
// could call this with a nullptr snapshot_data in a major version.
template <typename... EnvironmentArgs>
Expand Down
8 changes: 6 additions & 2 deletions src/node_snapshot_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include <cstdint>
#include <optional>
#include <string_view>
#include "node_exit_code.h"
#include "node_mutex.h"
#include "v8.h"
Expand All @@ -18,12 +20,14 @@ class NODE_EXTERN_PRIVATE SnapshotBuilder {
public:
static ExitCode Generate(std::ostream& out,
const std::vector<std::string> args,
const std::vector<std::string> exec_args);
const std::vector<std::string> exec_args,
std::optional<std::string_view> main_script);

// Generate the snapshot into out.
static ExitCode Generate(SnapshotData* out,
const std::vector<std::string> args,
const std::vector<std::string> exec_args);
const std::vector<std::string> exec_args,
std::optional<std::string_view> main_script);

// If nullptr is returned, the binary is not built with embedded
// snapshot.
Expand Down
Loading

0 comments on commit 47b4ca1

Please sign in to comment.