Skip to content

Commit

Permalink
build: add --node-snapshot-main configure option
Browse files Browse the repository at this point in the history
This adds a --build-snapshot runtime option which is currently only
supported by the node_mksnapshot binary, and a --node-snapshot-main
configure option that makes use it to run a custom script when
building the embedded snapshot. The idea is to have this experimental
feature in core as a configure-time feature for now, and investigate
the renaming V8 bugs before we make it available to more users via
making it a runtime option.

PR-URL: nodejs/node#42466
Reviewed-By: Darshan Sen <[email protected]>
Reviewed-By: Mohammed Keyvanzadeh <[email protected]>
Reviewed-By: Khaidi Chu <[email protected]>
Reviewed-By: Chengzhong Wu <[email protected]>
  • Loading branch information
joyeecheung authored and guangwong committed Oct 10, 2022
1 parent 4d012b2 commit ce52a4b
Show file tree
Hide file tree
Showing 12 changed files with 365 additions and 36 deletions.
19 changes: 19 additions & 0 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,13 @@
default=False,
help='node will load builtin modules from disk instead of from binary')

parser.add_argument('--node-snapshot-main',
action='store',
dest='node_snapshot_main',
default=None,
help='Run a file when building the embedded snapshot. Currently ' +
'experimental.')

# Create compile_commands.json in out/Debug and out/Release.
parser.add_argument('-C',
action='store_true',
Expand Down Expand Up @@ -1225,6 +1232,18 @@ def configure_node(o):

o['variables']['want_separate_host_toolset'] = int(cross_compiling)

if options.node_snapshot_main is not None:
if options.shared:
# This should be possible to fix, but we will need to refactor the
# libnode target to avoid building it twice.
error('--node-snapshot-main is incompatible with --shared')
if options.without_node_snapshot:
error('--node-snapshot-main is incompatible with ' +
'--without-node-snapshot')
if cross_compiling:
error('--node-snapshot-main is incompatible with cross compilation')
o['variables']['node_snapshot_main'] = options.node_snapshot_main

if options.without_node_snapshot or options.node_builtin_modules_path:
o['variables']['node_use_node_snapshot'] = 'false'
else:
Expand Down
12 changes: 9 additions & 3 deletions lib/internal/bootstrap/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ const { Buffer } = require('buffer');
const { ERR_MANIFEST_ASSERT_INTEGRITY } = require('internal/errors').codes;
const assert = require('internal/assert');

function prepareMainThreadExecution(expandArgv1 = false) {
function prepareMainThreadExecution(expandArgv1 = false,
initialzeModules = true) {
refreshRuntimeOptions();

// TODO(joyeecheung): this is also necessary for workers when they deserialize
Expand Down Expand Up @@ -80,9 +81,13 @@ function prepareMainThreadExecution(expandArgv1 = false) {
initializeSourceMapsHandlers();
initializeDeprecations();
initializeWASI();

if (!initialzeModules) {
return;
}

initializeCJSLoader();
initializeESMLoader();

const CJSLoader = require('internal/modules/cjs/loader');
assert(!CJSLoader.hasLoadedAnyUserCJSModule);
loadPreloadModules();
Expand All @@ -101,7 +106,8 @@ function patchProcessObject(expandArgv1) {

ObjectDefineProperty(process, 'argv0', {
enumerable: true,
configurable: false,
// Only set it to true during snapshot building.
configurable: getOptionValue('--build-snapshot'),
value: process.argv[0]
});
process.argv[0] = process.execPath;
Expand Down
142 changes: 142 additions & 0 deletions lib/internal/main/mksnapshot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
'use strict';

const {
Error,
SafeSet,
SafeArrayIterator
} = primordials;

const binding = internalBinding('mksnapshot');
const { NativeModule } = require('internal/bootstrap/loaders');
const {
compileSnapshotMain,
} = binding;

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

const {
readFileSync
} = require('fs');

const supportedModules = new SafeSet(new SafeArrayIterator([
// '_http_agent',
// '_http_client',
// '_http_common',
// '_http_incoming',
// '_http_outgoing',
// '_http_server',
'_stream_duplex',
'_stream_passthrough',
'_stream_readable',
'_stream_transform',
'_stream_wrap',
'_stream_writable',
// '_tls_common',
// '_tls_wrap',
'assert',
'assert/strict',
// 'async_hooks',
'buffer',
// 'child_process',
// 'cluster',
'console',
'constants',
'crypto',
// 'dgram',
// 'diagnostics_channel',
// 'dns',
// 'dns/promises',
// 'domain',
'events',
'fs',
'fs/promises',
// 'http',
// 'http2',
// 'https',
// 'inspector',
// 'module',
// 'net',
'os',
'path',
'path/posix',
'path/win32',
// 'perf_hooks',
'process',
'punycode',
'querystring',
// 'readline',
// 'repl',
'stream',
'stream/promises',
'string_decoder',
'sys',
'timers',
'timers/promises',
// 'tls',
// 'trace_events',
// 'tty',
'url',
'util',
'util/types',
'v8',
// 'vm',
// 'worker_threads',
// 'zlib',
]));

const warnedModules = new SafeSet();
function supportedInUserSnapshot(id) {
return supportedModules.has(id);
}

function requireForUserSnapshot(id) {
if (!NativeModule.canBeRequiredByUsers(id)) {
// eslint-disable-next-line no-restricted-syntax
const err = new Error(
`Cannot find module '${id}'. `
);
err.code = 'MODULE_NOT_FOUND';
throw err;
}
if (!supportedInUserSnapshot(id)) {
if (!warnedModules.has(id)) {
process.emitWarning(
`built-in module ${id} is not yet supported in user snapshots`);
warnedModules.add(id);
}
}

return require(id);
}

function main() {
const {
prepareMainThreadExecution
} = require('internal/bootstrap/pre_execution');

prepareMainThreadExecution(true, false);
process.once('beforeExit', function runCleanups() {
for (const cleanup of binding.cleanups) {
cleanup();
}
});

const file = process.argv[1];
const path = require('path');
const filename = path.resolve(file);
const dirname = path.dirname(filename);
const source = readFileSync(file, 'utf-8');
const snapshotMainFunction = compileSnapshotMain(filename, source);

if (getOptionValue('--inspect-brk')) {
internalBinding('inspector').callAndPauseOnStart(
snapshotMainFunction, undefined,
requireForUserSnapshot, filename, dirname);
} else {
snapshotMainFunction(requireForUserSnapshot, filename, dirname);
}
}

main();
53 changes: 39 additions & 14 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
'node_use_dtrace%': 'false',
'node_use_etw%': 'false',
'node_no_browser_globals%': 'false',
'node_snapshot_main%': '',
'node_use_node_snapshot%': 'false',
'node_use_v8_platform%': 'true',
'node_use_bundled_v8%': 'true',
Expand Down Expand Up @@ -315,23 +316,47 @@
'dependencies': [
'node_mksnapshot',
],
'actions': [
{
'action_name': 'node_mksnapshot',
'process_outputs_as_sources': 1,
'inputs': [
'<(node_mksnapshot_exec)',
],
'outputs': [
'<(SHARED_INTERMEDIATE_DIR)/node_snapshot.cc',
'conditions': [
['node_snapshot_main!=""', {
'actions': [
{
'action_name': 'node_mksnapshot',
'process_outputs_as_sources': 1,
'inputs': [
'<(node_mksnapshot_exec)',
'<(node_snapshot_main)',
],
'outputs': [
'<(SHARED_INTERMEDIATE_DIR)/node_snapshot.cc',
],
'action': [
'<(node_mksnapshot_exec)',
'--build-snapshot',
'<(node_snapshot_main)',
'<@(_outputs)',
],
},
],
'action': [
'<@(_inputs)',
'<@(_outputs)',
}, {
'actions': [
{
'action_name': 'node_mksnapshot',
'process_outputs_as_sources': 1,
'inputs': [
'<(node_mksnapshot_exec)',
],
'outputs': [
'<(SHARED_INTERMEDIATE_DIR)/node_snapshot.cc',
],
'action': [
'<@(_inputs)',
'<@(_outputs)',
],
},
],
},
}],
],
}, {
}, {
'sources': [
'src/node_snapshot_stub.cc'
],
Expand Down
10 changes: 10 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,10 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
return StartExecution(env, "internal/main/inspect");
}

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

if (per_process::cli_options->print_help) {
return StartExecution(env, "internal/main/print_help");
}
Expand Down Expand Up @@ -1184,6 +1188,12 @@ int Start(int argc, char** argv) {
return result.exit_code;
}

if (per_process::cli_options->build_snapshot) {
fprintf(stderr,
"--build-snapshot is not yet supported in the node binary\n");
return 1;
}

{
bool use_node_snapshot =
per_process::cli_options->per_isolate->node_snapshot;
Expand Down
1 change: 1 addition & 0 deletions src/node_binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
V(js_udp_wrap) \
V(messaging) \
V(module_wrap) \
V(mksnapshot) \
V(native_module) \
V(options) \
V(os) \
Expand Down
1 change: 1 addition & 0 deletions src/node_external_reference.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class ExternalReferenceRegistry {
V(handle_wrap) \
V(heap_utils) \
V(messaging) \
V(mksnapshot) \
V(native_module) \
V(options) \
V(os) \
Expand Down
5 changes: 5 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,11 @@ PerProcessOptionsParser::PerProcessOptionsParser(
"disable Object.prototype.__proto__",
&PerProcessOptions::disable_proto,
kAllowedInEnvironment);
AddOption("--build-snapshot",
"Generate a snapshot blob when the process exits."
"Currently only supported in the node_mksnapshot binary.",
&PerProcessOptions::build_snapshot,
kDisallowedInEnvironment);

// 12.x renamed this inadvertently, so alias it for consistency within the
// release line, while using the original name for consistency with older
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ class PerProcessOptions : public Options {
bool zero_fill_all_buffers = false;
bool debug_arraybuffer_allocations = false;
std::string disable_proto;
bool build_snapshot;

std::vector<std::string> security_reverts;
bool print_bash_completion = false;
Expand Down
Loading

0 comments on commit ce52a4b

Please sign in to comment.