Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bootstrap: unify snapshot builder and embedder entry points #48242

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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