From 1cf140aecc4e77876fb33ef8bb87d16aa4a4cb37 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 18 Aug 2023 14:33:54 +0200 Subject: [PATCH 1/2] src: support snapshot deserialization in RAIIIsolate --- src/util.cc | 6 +++++- src/util.h | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/util.cc b/src/util.cc index 76a61aef592641..19fb91c959a205 100644 --- a/src/util.cc +++ b/src/util.cc @@ -27,6 +27,7 @@ #include "node_buffer.h" #include "node_errors.h" #include "node_internals.h" +#include "node_snapshot_builder.h" #include "node_v8_platform-inl.h" #include "string_bytes.h" #include "uv.h" @@ -677,13 +678,16 @@ Local UnionBytes::ToStringChecked(Isolate* isolate) const { } } -RAIIIsolate::RAIIIsolate() +RAIIIsolate::RAIIIsolate(const SnapshotData* data) : allocator_{ArrayBuffer::Allocator::NewDefaultAllocator()} { isolate_ = Isolate::Allocate(); CHECK_NOT_NULL(isolate_); per_process::v8_platform.Platform()->RegisterIsolate(isolate_, uv_default_loop()); Isolate::CreateParams params; + if (data != nullptr) { + SnapshotBuilder::InitializeIsolateParams(data, ¶ms); + } params.array_buffer_allocator = allocator_.get(); Isolate::Initialize(isolate_, params); } diff --git a/src/util.h b/src/util.h index b9369867eed316..344f7753dab2b1 100644 --- a/src/util.h +++ b/src/util.h @@ -971,7 +971,7 @@ void SetConstructorFunction(v8::Isolate* isolate, // Simple RAII class to spin up a v8::Isolate instance. class RAIIIsolate { public: - RAIIIsolate(); + explicit RAIIIsolate(const SnapshotData* data = nullptr); ~RAIIIsolate(); v8::Isolate* get() const { return isolate_; } From 661edfea950a7306c24a56a21262c89271e0fe70 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 18 Aug 2023 14:16:55 +0200 Subject: [PATCH 2/2] sea: generate code cache with deserialized isolate V8 now requires code cache to be compiled from an isolate with the same RO space layout as the one that's going to deserialize the cache, so for a binary built with snapshot, we need to compile the code cache using a deserialized isolate. Drive-by: ignore "useCodeCache" when "useSnapshot" is true because the compilation would've been done during build time anyway in that case, and print a warning for it. --- src/node_sea.cc | 21 ++++--- ...ble-application-snapshot-and-code-cache.js | 63 +++++++++++++++++++ 2 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 test/sequential/test-single-executable-application-snapshot-and-code-cache.js diff --git a/src/node_sea.cc b/src/node_sea.cc index a8dbfeaa424943..521f2f670b28c8 100644 --- a/src/node_sea.cc +++ b/src/node_sea.cc @@ -411,7 +411,7 @@ ExitCode GenerateSnapshotForSEA(const SeaConfig& config, std::optional GenerateCodeCache(std::string_view main_path, std::string_view main_script) { - RAIIIsolate raii_isolate; + RAIIIsolate raii_isolate(SnapshotBuilder::GetEmbeddedSnapshotData()); Isolate* isolate = raii_isolate.get(); HandleScope handle_scope(isolate); @@ -489,14 +489,19 @@ ExitCode GenerateSingleExecutableBlob( std::optional optional_sv_code_cache; std::string code_cache; if (static_cast(config.flags & SeaFlags::kUseCodeCache)) { - std::optional optional_code_cache = - GenerateCodeCache(config.main_path, main_script); - if (!optional_code_cache.has_value()) { - FPrintF(stderr, "Cannot generate V8 code cache\n"); - return ExitCode::kGenericUserError; + if (builds_snapshot_from_main) { + FPrintF(stderr, + "\"useCodeCache\" is redundant when \"useSnapshot\" is true\n"); + } else { + std::optional optional_code_cache = + GenerateCodeCache(config.main_path, main_script); + if (!optional_code_cache.has_value()) { + FPrintF(stderr, "Cannot generate V8 code cache\n"); + return ExitCode::kGenericUserError; + } + code_cache = optional_code_cache.value(); + optional_sv_code_cache = code_cache; } - code_cache = optional_code_cache.value(); - optional_sv_code_cache = code_cache; } SeaResource sea{ diff --git a/test/sequential/test-single-executable-application-snapshot-and-code-cache.js b/test/sequential/test-single-executable-application-snapshot-and-code-cache.js new file mode 100644 index 00000000000000..66012e38a4faa6 --- /dev/null +++ b/test/sequential/test-single-executable-application-snapshot-and-code-cache.js @@ -0,0 +1,63 @@ +'use strict'; + +require('../common'); + +const { + injectAndCodeSign, + skipIfSingleExecutableIsNotSupported, +} = require('../common/sea'); + +skipIfSingleExecutableIsNotSupported(); + +// This tests "useCodeCache" is ignored when "useSnapshot" is true. + +const tmpdir = require('../common/tmpdir'); +const { copyFileSync, writeFileSync, existsSync } = require('fs'); +const { spawnSync } = require('child_process'); +const { join } = require('path'); +const assert = require('assert'); + +const configFile = join(tmpdir.path, 'sea-config.json'); +const seaPrepBlob = join(tmpdir.path, 'sea-prep.blob'); +const outputFile = join(tmpdir.path, process.platform === 'win32' ? 'sea.exe' : 'sea'); + +{ + tmpdir.refresh(); + const code = ` + const { + setDeserializeMainFunction, + } = require('v8').startupSnapshot; + + setDeserializeMainFunction(() => { + console.log('Hello from snapshot'); + }); + `; + + writeFileSync(join(tmpdir.path, 'snapshot.js'), code, 'utf-8'); + writeFileSync(configFile, ` + { + "main": "snapshot.js", + "output": "sea-prep.blob", + "useSnapshot": true, + "useCodeCache": true + } + `); + + let child = spawnSync( + process.execPath, + ['--experimental-sea-config', 'sea-config.json'], + { + cwd: tmpdir.path + }); + assert.match( + child.stderr.toString(), + /"useCodeCache" is redundant when "useSnapshot" is true/); + + assert(existsSync(seaPrepBlob)); + + copyFileSync(process.execPath, outputFile); + injectAndCodeSign(outputFile, seaPrepBlob); + + child = spawnSync(outputFile); + assert.strictEqual(child.stdout.toString().trim(), 'Hello from snapshot'); +}