Skip to content

Commit 3803b02

Browse files
authored
src: share common code paths for SEA and embedder script
Since SEA is very similar in principle to embedding functionality, it makes sense to share code paths where possible. This commit does so and addresses a `TODO` while doing so. It also adds a utility to directly run CJS code to the embedder startup callback, which comes in handy for this purpose. Finally, this commit is breaking because it aligns the behavior of `require()`ing internal modules; previously, embedders could use the `require` function that they received to do so. (If this is not considered breaking because accessing internals is not covered by the API, then this would need ABI compatibility patches for becoming fully non-breaking.) PR-URL: nodejs#46825 Reviewed-By: Darshan Sen <raisinten@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
1 parent 7bd909b commit 3803b02

16 files changed

+145
-184
lines changed

lib/internal/main/embedding.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict';
2+
const {
3+
prepareMainThreadExecution,
4+
markBootstrapComplete,
5+
} = require('internal/process/pre_execution');
6+
const { isSea } = internalBinding('sea');
7+
const { emitExperimentalWarning } = require('internal/util');
8+
const { embedderRequire, embedderRunCjs } = require('internal/util/embedding');
9+
const { getEmbedderEntryFunction } = internalBinding('mksnapshot');
10+
11+
prepareMainThreadExecution(false, true);
12+
markBootstrapComplete();
13+
14+
if (isSea()) {
15+
emitExperimentalWarning('Single executable application');
16+
}
17+
18+
return getEmbedderEntryFunction()(embedderRequire, embedderRunCjs);

lib/internal/main/environment.js

-13
This file was deleted.

lib/internal/main/mksnapshot.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -119,16 +119,25 @@ function main() {
119119
const {
120120
prepareMainThreadExecution
121121
} = require('internal/process/pre_execution');
122+
const path = require('path');
122123

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

126127
if (serializeMainFunction) { // embedded case
127128
prepareMainThreadExecution(false, false);
129+
// TODO(addaleax): Make this `embedderRunCjs` once require('module')
130+
// is supported in snapshots.
131+
const filename = process.execPath;
132+
const dirname = path.dirname(filename);
133+
function minimalRunCjs(source) {
134+
const fn = compileSerializeMain(filename, source);
135+
return fn(requireForUserSnapshot, filename, dirname);
136+
}
137+
serializeMainArgs.push(minimalRunCjs);
128138
} else {
129139
prepareMainThreadExecution(true, false);
130140
const file = process.argv[1];
131-
const path = require('path');
132141
const filename = path.resolve(file);
133142
const dirname = path.dirname(filename);
134143
const source = readFileSync(file, 'utf-8');

lib/internal/main/single_executable_application.js

-55
This file was deleted.

lib/internal/util/embedding.js

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
const { codes: { ERR_UNKNOWN_BUILTIN_MODULE } } = require('internal/errors');
3+
const { Module, wrapSafe } = require('internal/modules/cjs/loader');
4+
5+
// This is roughly the same as:
6+
//
7+
// const mod = new Module(filename);
8+
// mod._compile(contents, filename);
9+
//
10+
// but the code has been duplicated because currently there is no way to set the
11+
// value of require.main to module.
12+
//
13+
// TODO(RaisinTen): Find a way to deduplicate this.
14+
15+
function embedderRunCjs(contents) {
16+
const filename = process.execPath;
17+
const compiledWrapper = wrapSafe(filename, contents);
18+
19+
const customModule = new Module(filename, null);
20+
customModule.filename = filename;
21+
customModule.paths = Module._nodeModulePaths(customModule.path);
22+
23+
const customExports = customModule.exports;
24+
25+
embedderRequire.main = customModule;
26+
27+
const customFilename = customModule.filename;
28+
29+
const customDirname = customModule.path;
30+
31+
return compiledWrapper(
32+
customExports,
33+
embedderRequire,
34+
customModule,
35+
customFilename,
36+
customDirname);
37+
}
38+
39+
function embedderRequire(path) {
40+
if (!Module.isBuiltin(path)) {
41+
throw new ERR_UNKNOWN_BUILTIN_MODULE(path);
42+
}
43+
44+
return require(path);
45+
}
46+
47+
module.exports = { embedderRequire, embedderRunCjs };

src/api/environment.cc

+7-9
Original file line numberDiff line numberDiff line change
@@ -528,17 +528,15 @@ MaybeLocal<Value> LoadEnvironment(
528528
return StartExecution(env, cb);
529529
}
530530

531-
MaybeLocal<Value> LoadEnvironment(
532-
Environment* env,
533-
const char* main_script_source_utf8) {
534-
CHECK_NOT_NULL(main_script_source_utf8);
531+
MaybeLocal<Value> LoadEnvironment(Environment* env,
532+
std::string_view main_script_source_utf8) {
533+
CHECK_NOT_NULL(main_script_source_utf8.data());
535534
return LoadEnvironment(
536535
env, [&](const StartExecutionCallbackInfo& info) -> MaybeLocal<Value> {
537-
std::string name = "embedder_main_" + std::to_string(env->thread_id());
538-
env->builtin_loader()->Add(name.c_str(), main_script_source_utf8);
539-
Realm* realm = env->principal_realm();
540-
541-
return realm->ExecuteBootstrapper(name.c_str());
536+
Local<Value> main_script =
537+
ToV8Value(env->context(), main_script_source_utf8).ToLocalChecked();
538+
return info.run_cjs->Call(
539+
env->context(), Null(env->isolate()), 1, &main_script);
542540
});
543541
}
544542

src/env-inl.h

+4-6
Original file line numberDiff line numberDiff line change
@@ -406,14 +406,12 @@ inline builtins::BuiltinLoader* Environment::builtin_loader() {
406406
return &builtin_loader_;
407407
}
408408

409-
inline const StartExecutionCallback&
410-
Environment::embedder_mksnapshot_entry_point() const {
411-
return embedder_mksnapshot_entry_point_;
409+
inline const StartExecutionCallback& Environment::embedder_entry_point() const {
410+
return embedder_entry_point_;
412411
}
413412

414-
inline void Environment::set_embedder_mksnapshot_entry_point(
415-
StartExecutionCallback&& fn) {
416-
embedder_mksnapshot_entry_point_ = std::move(fn);
413+
inline void Environment::set_embedder_entry_point(StartExecutionCallback&& fn) {
414+
embedder_entry_point_ = std::move(fn);
417415
}
418416

419417
inline double Environment::new_async_id() {

src/env.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -948,8 +948,8 @@ class Environment : public MemoryRetainer {
948948

949949
#endif // HAVE_INSPECTOR
950950

951-
inline const StartExecutionCallback& embedder_mksnapshot_entry_point() const;
952-
inline void set_embedder_mksnapshot_entry_point(StartExecutionCallback&& fn);
951+
inline const StartExecutionCallback& embedder_entry_point() const;
952+
inline void set_embedder_entry_point(StartExecutionCallback&& fn);
953953

954954
inline void set_process_exit_handler(
955955
std::function<void(Environment*, ExitCode)>&& handler);
@@ -1133,7 +1133,7 @@ class Environment : public MemoryRetainer {
11331133
std::unique_ptr<Realm> principal_realm_ = nullptr;
11341134

11351135
builtins::BuiltinLoader builtin_loader_;
1136-
StartExecutionCallback embedder_mksnapshot_entry_point_;
1136+
StartExecutionCallback embedder_entry_point_;
11371137

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

src/node.cc

+9-26
Original file line numberDiff line numberDiff line change
@@ -277,22 +277,17 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
277277

278278
if (cb != nullptr) {
279279
EscapableHandleScope scope(env->isolate());
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_entry_point(std::move(cb));
283+
auto reset_entry_point =
284+
OnScopeLeave([&]() { env->set_embedder_entry_point({}); });
280285

281-
if (env->isolate_data()->options()->build_snapshot) {
282-
// TODO(addaleax): pass the callback to the main script more directly,
283-
// e.g. by making StartExecution(env, builtin) parametrizable
284-
env->set_embedder_mksnapshot_entry_point(std::move(cb));
285-
auto reset_entry_point =
286-
OnScopeLeave([&]() { env->set_embedder_mksnapshot_entry_point({}); });
286+
const char* entry = env->isolate_data()->options()->build_snapshot
287+
? "internal/main/mksnapshot"
288+
: "internal/main/embedding";
287289

288-
return StartExecution(env, "internal/main/mksnapshot");
289-
}
290-
291-
if (StartExecution(env, "internal/main/environment").IsEmpty()) return {};
292-
return scope.EscapeMaybe(cb({
293-
env->process_object(),
294-
env->builtin_module_require(),
295-
}));
290+
return scope.EscapeMaybe(StartExecution(env, entry));
296291
}
297292

298293
// TODO(joyeecheung): move these conditions into JS land and let the
@@ -312,18 +307,6 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
312307
first_argv = env->argv()[1];
313308
}
314309

315-
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
316-
if (sea::IsSingleExecutable()) {
317-
// TODO(addaleax): Find a way to reuse:
318-
//
319-
// LoadEnvironment(Environment*, const char*)
320-
//
321-
// instead and not add yet another main entry point here because this
322-
// already duplicates existing code.
323-
return StartExecution(env, "internal/main/single_executable_application");
324-
}
325-
#endif
326-
327310
if (first_argv == "inspect") {
328311
return StartExecution(env, "internal/main/inspect");
329312
}

src/node.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,7 @@ NODE_EXTERN std::unique_ptr<InspectorParentHandle> GetInspectorParentHandle(
682682
struct StartExecutionCallbackInfo {
683683
v8::Local<v8::Object> process_object;
684684
v8::Local<v8::Function> native_require;
685+
v8::Local<v8::Function> run_cjs;
685686
};
686687

687688
using StartExecutionCallback =
@@ -691,8 +692,7 @@ NODE_EXTERN v8::MaybeLocal<v8::Value> LoadEnvironment(
691692
Environment* env,
692693
StartExecutionCallback cb);
693694
NODE_EXTERN v8::MaybeLocal<v8::Value> LoadEnvironment(
694-
Environment* env,
695-
const char* main_script_source_utf8);
695+
Environment* env, std::string_view main_script_source_utf8);
696696
NODE_EXTERN void FreeEnvironment(Environment* env);
697697

698698
// Set a callback that is called when process.exit() is called from JS,

src/node_builtins.cc

+7-22
Original file line numberDiff line numberDiff line change
@@ -185,17 +185,14 @@ static std::string OnDiskFileName(const char* id) {
185185
MaybeLocal<String> BuiltinLoader::LoadBuiltinSource(Isolate* isolate,
186186
const char* id) const {
187187
auto source = source_.read();
188-
#ifdef NODE_BUILTIN_MODULES_PATH
189-
if (strncmp(id, "embedder_main_", strlen("embedder_main_")) == 0) {
190-
#endif // NODE_BUILTIN_MODULES_PATH
191-
const auto source_it = source->find(id);
192-
if (UNLIKELY(source_it == source->end())) {
193-
fprintf(stderr, "Cannot find native builtin: \"%s\".\n", id);
194-
ABORT();
195-
}
196-
return source_it->second.ToStringChecked(isolate);
197-
#ifdef NODE_BUILTIN_MODULES_PATH
188+
#ifndef NODE_BUILTIN_MODULES_PATH
189+
const auto source_it = source->find(id);
190+
if (UNLIKELY(source_it == source->end())) {
191+
fprintf(stderr, "Cannot find native builtin: \"%s\".\n", id);
192+
ABORT();
198193
}
194+
return source_it->second.ToStringChecked(isolate);
195+
#else // !NODE_BUILTIN_MODULES_PATH
199196
std::string filename = OnDiskFileName(id);
200197

201198
std::string contents;
@@ -395,12 +392,6 @@ MaybeLocal<Function> BuiltinLoader::LookupAndCompile(Local<Context> context,
395392
FIXED_ONE_BYTE_STRING(isolate, "internalBinding"),
396393
FIXED_ONE_BYTE_STRING(isolate, "primordials"),
397394
};
398-
} else if (strncmp(id, "embedder_main_", strlen("embedder_main_")) == 0) {
399-
// Synthetic embedder main scripts from LoadEnvironment(): process, require
400-
parameters = {
401-
FIXED_ONE_BYTE_STRING(isolate, "process"),
402-
FIXED_ONE_BYTE_STRING(isolate, "require"),
403-
};
404395
} else {
405396
// others: exports, require, module, process, internalBinding, primordials
406397
parameters = {
@@ -457,12 +448,6 @@ MaybeLocal<Value> BuiltinLoader::CompileAndCall(Local<Context> context,
457448
realm->builtin_module_require(),
458449
realm->internal_binding_loader(),
459450
realm->primordials()};
460-
} else if (strncmp(id, "embedder_main_", strlen("embedder_main_")) == 0) {
461-
// Synthetic embedder main scripts from LoadEnvironment(): process, require
462-
arguments = {
463-
realm->process_object(),
464-
realm->builtin_module_require(),
465-
};
466451
} else {
467452
// This should be invoked with the other CompileAndCall() methods, as
468453
// we are unable to generate the arguments.

src/node_main_instance.cc

+11-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "node_internals.h"
1010
#include "node_options-inl.h"
1111
#include "node_realm.h"
12+
#include "node_sea.h"
1213
#include "node_snapshot_builder.h"
1314
#include "node_snapshotable.h"
1415
#include "node_v8_platform-inl.h"
@@ -86,7 +87,16 @@ ExitCode NodeMainInstance::Run() {
8687

8788
void NodeMainInstance::Run(ExitCode* exit_code, Environment* env) {
8889
if (*exit_code == ExitCode::kNoFailure) {
89-
LoadEnvironment(env, StartExecutionCallback{});
90+
bool is_sea = false;
91+
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
92+
if (sea::IsSingleExecutable()) {
93+
is_sea = true;
94+
LoadEnvironment(env, sea::FindSingleExecutableCode());
95+
}
96+
#endif
97+
if (!is_sea) {
98+
LoadEnvironment(env, StartExecutionCallback{});
99+
}
90100

91101
*exit_code =
92102
SpinEventLoopInternal(env).FromMaybe(ExitCode::kGenericUserError);

0 commit comments

Comments
 (0)