Files
node/test/cctest/test_per_process.cc
Shelley Vohr 26bd28cbdb src: let embedders supply a builtin code cache without a snapshot
Environments created from the built-in snapshot get the builtins' code
cache from that snapshot. An embedder that bootstraps an Environment
from scratch (its own isolate and context, no EmbedderSnapshotData) has
no way to provide one: every builtin the bootstrap touches is compiled
from source in every such process, and each of them then serializes a
fresh cache (SaveCodeCache) that only a later worker thread would ever
consume.

Add node::EmbedderBuiltinCodeCache for that case. Its entries pair a
builtin id with a v8::ScriptCompiler::CachedData; Generate(context)
compiles every builtin in a context of the right kind of isolate and
returns them for a build step to embed, and
SetBuiltinCodeCache(isolate_data, cache) attaches them to an
IsolateData, next to where a snapshot's code cache lives, so that every
Environment created from it afterwards starts with them. The setter
runs CachedData::CompatibilityCheck() against the isolate first and
returns the result, leaving the IsolateData untouched for a cache made
with another V8 version, flag set or read-only snapshot. Entries from a
snapshot still merge with it (RefreshCodeCache() now merges instead of
assuming a single call). CreateEnvironment() plus LoadEnvironment() of
an empty script goes from about 38 ms to 13 ms with a cache attached.

ProcessInitializationFlags::kNoHarvestBuiltinCodeCache stops
serializing caches for builtins compiled without one, for embedders
that supply their own or never create workers. The default is
unchanged because worker threads copy the harvested cache.

A cctest generates a cache, attaches it, checks that a new
Environment's bootstrap compiles from it and that a corrupted cache is
refused; embedtest gains --no-harvest-builtin-code-cache for a test
that a worker does or does not find a harvested cache depending on the
flag.

Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65352
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
2026-09-02 14:04:24 +00:00

78 lines
2.8 KiB
C++

#include "node_builtins.h"
#include "node_threadsafe_cow-inl.h"
#include "gtest/gtest.h"
#include "node_test_fixture.h"
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
using node::builtins::BuiltinCodeCacheData;
using node::builtins::BuiltinLoader;
using node::builtins::BuiltinSourceMap;
using node::builtins::CodeCacheInfo;
class PerProcessTest : public ::testing::Test {
protected:
static const BuiltinSourceMap get_sources_for_test() {
return *BuiltinLoader().source_.read();
}
// id -> first byte of the cached data, after feeding `batches` in order.
static std::vector<std::pair<std::string, uint8_t>> RefreshCodeCacheWith(
const std::vector<std::vector<CodeCacheInfo>>& batches) {
BuiltinLoader loader;
for (const auto& batch : batches) loader.RefreshCodeCache(batch);
std::vector<std::pair<std::string, uint8_t>> out;
node::RwLock::ScopedReadLock lock(loader.code_cache_->mutex);
EXPECT_TRUE(loader.code_cache_->has_code_cache);
for (const auto& [id, data] : loader.code_cache_->map) {
out.emplace_back(id, data.data[0]);
}
std::sort(out.begin(), out.end());
return out;
}
};
CodeCacheInfo MakeCodeCacheInfo(const std::string& id, uint8_t marker) {
auto* bytes = new uint8_t[4]{marker, marker, marker, marker};
auto cached_data = std::make_shared<v8::ScriptCompiler::CachedData>(
bytes, 4, v8::ScriptCompiler::CachedData::BufferOwned);
return CodeCacheInfo{id, BuiltinCodeCacheData(std::move(cached_data))};
}
namespace {
TEST_F(PerProcessTest, EmbeddedSources) {
const auto& sources = PerProcessTest::get_sources_for_test();
ASSERT_TRUE(std::any_of(sources.cbegin(), sources.cend(), [](auto p) {
return p.second.source.is_one_byte();
})) << "BuiltinLoader::source_ should have some 8bit items";
ASSERT_TRUE(std::any_of(sources.cbegin(), sources.cend(), [](auto p) {
return !p.second.source.is_one_byte();
})) << "BuiltinLoader::source_ should have some 16bit items";
}
// RefreshCodeCache() merges: it can be fed the snapshot's code cache and then
// an embedder's, and the entry supplied last wins for a shared id.
TEST_F(PerProcessTest, RefreshCodeCacheMerges) {
const auto merged = PerProcessTest::RefreshCodeCacheWith({
{MakeCodeCacheInfo("internal/a", 1), MakeCodeCacheInfo("internal/b", 1)},
{MakeCodeCacheInfo("internal/b", 2), MakeCodeCacheInfo("embedder/c", 2)},
});
const std::vector<std::pair<std::string, uint8_t>> expected = {
{"embedder/c", 2}, {"internal/a", 1}, {"internal/b", 2}};
EXPECT_EQ(merged, expected);
// A single call still behaves as before.
const auto single = PerProcessTest::RefreshCodeCacheWith(
{{MakeCodeCacheInfo("internal/a", 7)}});
ASSERT_EQ(single.size(), 1u);
EXPECT_EQ(single[0].second, 7);
}
} // end namespace