From 189fbb1a1a7d54a0255c8b86db1cc221611c943e Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Sun, 16 Aug 2026 20:45:18 +0000 Subject: [PATCH] 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 an instance passed to CreateEnvironment() (new trailing parameter, forwarded by CommonEnvironmentSetup::Create()) seeds that Environment's loader. CreateEnvironment() runs CachedData::CompatibilityCheck() on the entries first and returns nullptr for a cache made with another V8 version, flag set or read-only snapshot. One instance can be passed to any number of Environments; entries from a snapshot still merge with it (RefreshCodeCache() now merges instead of assuming a single call). 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. embedtest gains --builtin-code-cache-create, --builtin-code-cache and --no-harvest-builtin-code-cache, and a test that generates a cache in one process, checks that another Environment's bootstrap compiles with it, and that a worker does or does not find a harvested cache depending on the flag. Signed-off-by: Shelley Vohr --- src/api/environment.cc | 17 ++- src/node.cc | 4 + src/node.h | 48 +++++++- src/node_builtins.cc | 83 +++++++++++++- src/node_builtins.h | 8 ++ src/node_internals.h | 6 + test/cctest/test_per_process.cc | 45 ++++++++ test/embedding/embedtest.cc | 108 ++++++++++++++++-- .../test-embedding-builtin-code-cache.js | 51 +++++++++ 9 files changed, 351 insertions(+), 19 deletions(-) create mode 100644 test/embedding/test-embedding-builtin-code-cache.js diff --git a/src/api/environment.cc b/src/api/environment.cc index 3b94d860de1e..069a75dbf4b2 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -425,12 +425,23 @@ Environment* CreateEnvironment( EnvironmentFlags::Flags flags, ThreadId thread_id, std::unique_ptr inspector_parent_handle, - std::string_view thread_name) { + std::string_view thread_name, + const EmbedderBuiltinCodeCache* builtin_code_cache) { Isolate* isolate = isolate_data->isolate(); Isolate::Scope isolate_scope(isolate); HandleScope handle_scope(isolate); + if (builtin_code_cache != nullptr) { + auto check = builtin_code_cache->CompatibilityCheck(isolate); + if (check != v8::ScriptCompiler::CachedData::kSuccess) { + per_process::Debug(DebugCategory::CODE_CACHE, + "EmbedderBuiltinCodeCache rejected: %d\n", + static_cast(check)); + return nullptr; + } + } + const bool use_snapshot = context.IsEmpty(); const EnvSerializeInfo* env_snapshot_info = nullptr; if (use_snapshot) { @@ -449,6 +460,10 @@ Environment* CreateEnvironment( thread_id, thread_name); CHECK_NOT_NULL(env); + if (builtin_code_cache != nullptr) { + env->builtin_loader()->RefreshCodeCache( + GetBuiltinCodeCacheEntries(*builtin_code_cache->impl_)); + } if (use_snapshot) { context = Context::FromSnapshot(isolate, diff --git a/src/node.cc b/src/node.cc index 7d51cbd5d0c7..b0ab485f6907 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1274,6 +1274,10 @@ InitializeOncePerProcessInternal(const std::vector& args, cppgc::InitializeProcess(allocator); } + if (flags & ProcessInitializationFlags::kNoHarvestBuiltinCodeCache) { + builtins::BuiltinLoader::SetHarvestCodeCache(false); + } + if (!(flags & ProcessInitializationFlags::kNoInitializeV8)) { V8::Initialize(); diff --git a/src/node.h b/src/node.h index e827a46e14dd..efa86766495a 100644 --- a/src/node.h +++ b/src/node.h @@ -234,6 +234,11 @@ enum Flags : uint32_t { kNoInitializeCppgc = 1 << 13, // Initialize the process for predictable snapshot generation. kGeneratePredictableSnapshot = 1 << 14, + // Do not serialize a code cache for builtins that had to be compiled without + // one. By default such caches are kept so that worker threads created later + // start faster; an embedder that supplies an EmbedderBuiltinCodeCache or + // never creates workers only pays for the serialization. + kNoHarvestBuiltinCodeCache = 1 << 15, // Emulate the behavior of InitializeNodeWithArgs() when passing // a flags argument to the InitializeOncePerProcess() replacement @@ -686,6 +691,46 @@ struct InspectorParentHandle { virtual ~InspectorParentHandle() = default; }; +// Code cache for the built-in JavaScript of Environments that are bootstrapped +// rather than deserialized from a snapshot; pass to CreateEnvironment(). One +// instance can serve many Environments, which share its buffers. +class NODE_EXTERN EmbedderBuiltinCodeCache { + public: + struct Entry { + std::string id; // e.g. "internal/bootstrap/node" + std::unique_ptr data; + }; + explicit EmbedderBuiltinCodeCache(std::vector entries); + ~EmbedderBuiltinCodeCache(); + + // Compiles every built-in module in `context`, which must come from + // NewContext(), and returns their code caches; empty on failure. + static std::vector Generate(v8::Local context); + + // Whether the entries can be used in `isolate`; CreateEnvironment() returns + // nullptr for a cache that does not pass. + v8::ScriptCompiler::CachedData::CompatibilityCheckResult CompatibilityCheck( + v8::Isolate* isolate) const; + + EmbedderBuiltinCodeCache(const EmbedderBuiltinCodeCache&) = delete; + EmbedderBuiltinCodeCache& operator=(const EmbedderBuiltinCodeCache&) = delete; + + struct Impl; + + private: + std::unique_ptr impl_; + friend NODE_EXTERN Environment* CreateEnvironment( + IsolateData*, + v8::Local, + const std::vector&, + const std::vector&, + EnvironmentFlags::Flags, + ThreadId, + std::unique_ptr, + std::string_view, + const EmbedderBuiltinCodeCache*); +}; + // TODO(addaleax): Maybe move per-Environment options parsing here. // Returns nullptr when the Environment cannot be created e.g. there are // pending JavaScript exceptions. @@ -699,7 +744,8 @@ NODE_EXTERN Environment* CreateEnvironment( EnvironmentFlags::Flags flags = EnvironmentFlags::kDefaultFlags, ThreadId thread_id = {} /* allocates a thread id automatically */, std::unique_ptr inspector_parent_handle = {}, - std::string_view thread_name = {}); + std::string_view thread_name = {}, + const EmbedderBuiltinCodeCache* builtin_code_cache = nullptr); // Returns a handle that can be passed to `LoadEnvironment()`, making the // child Environment accessible to the inspector as if it were a Node.js Worker. diff --git a/src/node_builtins.cc b/src/node_builtins.cc index 43a9a388c2ba..7d3bf30e100e 100644 --- a/src/node_builtins.cc +++ b/src/node_builtins.cc @@ -1,4 +1,6 @@ #include "node_builtins.h" +#include +#include #include "debug_utils-inl.h" #include "env-inl.h" #include "module_wrap.h" @@ -12,7 +14,6 @@ #include "v8-value.h" namespace node { -namespace builtins { using loader::HostDefinedOptions; using v8::Boolean; @@ -44,6 +45,16 @@ using v8::TryCatch; using v8::Undefined; using v8::Value; +namespace builtins { + +namespace { +std::atomic harvest_code_cache{true}; +} // namespace + +void BuiltinLoader::SetHarvestCodeCache(bool on) { + harvest_code_cache = on; +} + BuiltinLoader::BuiltinLoader() : config_(GetConfig()), code_cache_(std::make_shared()) { LoadJavaScriptSource(); @@ -422,6 +433,7 @@ MaybeLocal BuiltinLoader::LookupAndCompile( } if (result == Result::kWithoutCache && optional_realm != nullptr && + harvest_code_cache && !optional_realm->env()->isolate_data()->is_building_snapshot()) { // We failed to accept this cache, maybe because it was rejected, maybe // because it wasn't present. Either way, we'll attempt to replace this @@ -593,12 +605,13 @@ bool BuiltinLoader::CompileAllBuiltinsAndCopyCodeCache( void BuiltinLoader::RefreshCodeCache(const std::vector& in) { RwLock::ScopedLock lock(code_cache_->mutex); - code_cache_->map.reserve(in.size()); - DCHECK(code_cache_->map.empty()); + // May be called more than once, e.g. first with the code cache carried by + // the snapshot and then by an embedder with caches it built for additional + // (or the same) builtin ids against this isolate: merge, and let the entry + // supplied last win for an id present in both. + code_cache_->map.reserve(code_cache_->map.size() + in.size()); for (auto const& [id, data] : in) { - auto result = code_cache_->map.emplace(id, data); - USE(result.second); - DCHECK(result.second); + code_cache_->map.insert_or_assign(id, data); } code_cache_->has_code_cache = true; } @@ -918,6 +931,64 @@ void BuiltinLoader::RegisterExternalReferences( } } // namespace builtins + +struct EmbedderBuiltinCodeCache::Impl { + std::vector entries; +}; + +EmbedderBuiltinCodeCache::EmbedderBuiltinCodeCache(std::vector entries) + : impl_(std::make_unique()) { + impl_->entries.reserve(entries.size()); + for (Entry& e : entries) { + impl_->entries.push_back( + {std::move(e.id), + builtins::BuiltinCodeCacheData( + std::shared_ptr(std::move(e.data)))}); + } +} + +EmbedderBuiltinCodeCache::~EmbedderBuiltinCodeCache() = default; + +ScriptCompiler::CachedData::CompatibilityCheckResult +EmbedderBuiltinCodeCache::CompatibilityCheck(Isolate* isolate) const { + for (const builtins::CodeCacheInfo& info : impl_->entries) { + ScriptCompiler::CachedData probe( + info.data.data, + static_cast(info.data.length), + ScriptCompiler::CachedData::BufferNotOwned); + auto result = probe.CompatibilityCheck(isolate); + if (result != ScriptCompiler::CachedData::kSuccess) return result; + } + return ScriptCompiler::CachedData::kSuccess; +} + +std::vector EmbedderBuiltinCodeCache::Generate( + Local context) { + std::vector out; + builtins::BuiltinLoader loader; + loader.SetEagerCompile(); + std::vector infos; + if (!loader.CompileAllBuiltinsAndCopyCodeCache(context, {}, &infos)) { + return out; + } + out.reserve(infos.size()); + for (const builtins::CodeCacheInfo& info : infos) { + uint8_t* copy = new uint8_t[info.data.length]; + memcpy(copy, info.data.data, info.data.length); + out.push_back({info.id, + std::make_unique( + copy, + static_cast(info.data.length), + ScriptCompiler::CachedData::BufferOwned)}); + } + return out; +} + +const std::vector& GetBuiltinCodeCacheEntries( + const EmbedderBuiltinCodeCache::Impl& impl) { + return impl.entries; +} + } // namespace node NODE_BINDING_PER_ISOLATE_INIT( diff --git a/src/node_builtins.h b/src/node_builtins.h index b51b85ff6f23..89fe7f5c1aa7 100644 --- a/src/node_builtins.h +++ b/src/node_builtins.h @@ -125,8 +125,16 @@ class NODE_EXTERN_PRIVATE BuiltinLoader { v8::Local context, const std::vector& lazy_builtins, std::vector* out); + // Adds the given code cache entries, replacing existing entries with the + // same id. Can be called more than once (e.g. with the snapshot's code cache + // and then with caches an embedder built for further builtin ids). void RefreshCodeCache(const std::vector& in); + // Whether builtins compiled without a cache serialize one for later + // consumers (worker threads copy it). See + // ProcessInitializationFlags::kNoHarvestBuiltinCodeCache. + static void SetHarvestCodeCache(bool on); + void CopySourceAndCodeCacheReferenceFrom(const BuiltinLoader* other); [[nodiscard]] std::ranges::keys_view< diff --git a/src/node_internals.h b/src/node_internals.h index 631a8d7ccdd9..3039b23ee1ad 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -45,6 +45,12 @@ struct sockaddr; namespace node { +namespace builtins { +struct CodeCacheInfo; +} +const std::vector& GetBuiltinCodeCacheEntries( + const EmbedderBuiltinCodeCache::Impl& impl); + namespace builtins { class BuiltinLoader; } diff --git a/test/cctest/test_per_process.cc b/test/cctest/test_per_process.cc index 7a6f53d56222..937e9cab54dd 100644 --- a/test/cctest/test_per_process.cc +++ b/test/cctest/test_per_process.cc @@ -4,18 +4,45 @@ #include "gtest/gtest.h" #include "node_test_fixture.h" +#include +#include #include +#include +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> RefreshCodeCacheWith( + const std::vector>& batches) { + BuiltinLoader loader; + for (const auto& batch : batches) loader.RefreshCodeCache(batch); + std::vector> 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( + bytes, 4, v8::ScriptCompiler::CachedData::BufferOwned); + return CodeCacheInfo{id, BuiltinCodeCacheData(std::move(cached_data))}; +} + namespace { TEST_F(PerProcessTest, EmbeddedSources) { @@ -29,4 +56,22 @@ TEST_F(PerProcessTest, EmbeddedSources) { })) << "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> 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 diff --git a/test/embedding/embedtest.cc b/test/embedding/embedtest.cc index 045c01211cf2..d77939e8dcc3 100644 --- a/test/embedding/embedtest.cc +++ b/test/embedding/embedtest.cc @@ -2,6 +2,7 @@ #undef NDEBUG #endif #include +#include #include "cppgc/platform.h" #include "executable_wrapper.h" #include "node.h" @@ -24,6 +25,61 @@ using v8::MaybeLocal; using v8::V8; using v8::Value; +// Builtin code cache file used by --builtin-code-cache[-create]: +// u32 count, then per entry: u32 id length, id bytes, u32 data length, data. +static std::vector code_cache_file; // backs the entries for the process + +static std::unique_ptr LoadBuiltinCodeCache( + const std::string& path) { + FILE* fp = fopen(path.c_str(), "rb"); + assert(fp != nullptr); + fseek(fp, 0, SEEK_END); + code_cache_file.resize(ftell(fp)); + fseek(fp, 0, SEEK_SET); + size_t r = fread(code_cache_file.data(), 1, code_cache_file.size(), fp); + assert(r == code_cache_file.size()); + fclose(fp); + const char* p = code_cache_file.data(); + auto u32 = [&p]() { + uint32_t v; + memcpy(&v, p, 4); + p += 4; + return v; + }; + std::vector entries(u32()); + for (node::EmbedderBuiltinCodeCache::Entry& e : entries) { + uint32_t idlen = u32(); + e.id.assign(p, idlen); + p += idlen; + uint32_t length = u32(); + e.data = std::make_unique( + reinterpret_cast(p), + static_cast(length), + v8::ScriptCompiler::CachedData::BufferNotOwned); + p += length; + } + return std::make_unique(std::move(entries)); +} + +static int WriteBuiltinCodeCache(v8::Local context, + const std::string& path) { + std::vector entries = + node::EmbedderBuiltinCodeCache::Generate(context); + if (entries.empty()) return 1; + FILE* fp = fopen(path.c_str(), "wb"); + assert(fp != nullptr); + auto u32 = [fp](uint32_t v) { fwrite(&v, 4, 1, fp); }; + u32(static_cast(entries.size())); + for (const node::EmbedderBuiltinCodeCache::Entry& e : entries) { + u32(static_cast(e.id.size())); + fwrite(e.id.data(), 1, e.id.size(), fp); + u32(static_cast(e.data->length)); + fwrite(e.data->data, 1, e.data->length, fp); + } + fclose(fp); + return 0; +} + static int RunNodeInstance(MultiIsolatePlatform* platform, const std::vector& args, const std::vector& exec_args); @@ -79,19 +135,24 @@ NODE_MAIN(int argc, node::argv_type raw_argv[]) { node::FixupMain(argc, raw_argv, &argv); std::vector args(argv, argv + argc); + uint32_t flags = + node::ProcessInitializationFlags::kNoInitializeV8 | + node::ProcessInitializationFlags::kNoInitializeNodeV8Platform | + // This is used to test NODE_REPL_EXTERNAL_MODULE is disabled with + // kDisableNodeOptionsEnv. If other tests need NODE_OPTIONS + // support in the future, split this configuration out as a + // command line option. + node::ProcessInitializationFlags::kDisableNodeOptionsEnv | + node::ProcessInitializationFlags::kNoInitializeCppgc; + auto it = + std::find(args.begin(), args.end(), "--no-harvest-builtin-code-cache"); + if (it != args.end()) { + args.erase(it); + flags |= node::ProcessInitializationFlags::kNoHarvestBuiltinCodeCache; + } std::shared_ptr result = node::InitializeOncePerProcess( - args, - { - node::ProcessInitializationFlags::kNoInitializeV8, - node::ProcessInitializationFlags::kNoInitializeNodeV8Platform, - // This is used to test NODE_REPL_EXTERNAL_MODULE is disabled with - // kDisableNodeOptionsEnv. If other tests need NODE_OPTIONS - // support in the future, split this configuration out as a - // command line option. - node::ProcessInitializationFlags::kDisableNodeOptionsEnv, - node::ProcessInitializationFlags::kNoInitializeCppgc, - }); + args, static_cast(flags)); for (const std::string& error : result->errors()) fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str()); @@ -141,6 +202,8 @@ int RunNodeInstance(MultiIsolatePlatform* platform, bool snapshot_as_file = false; std::optional snapshot_config; std::string snapshot_blob_path; + std::string code_cache_out_path; + std::unique_ptr builtin_code_cache; for (size_t i = 0; i < args.size(); ++i) { const std::string& arg = args[i]; if (arg == "--embedder-snapshot-create") { @@ -179,6 +242,14 @@ int RunNodeInstance(MultiIsolatePlatform* platform, } snapshot_config.value().base_blob = &base_blob; i++; + } else if (arg == "--builtin-code-cache-create") { + assert(i + 1 < args.size()); + code_cache_out_path = args[i + 1]; + i++; + } else if (arg == "--builtin-code-cache") { + assert(i + 1 < args.size()); + builtin_code_cache = LoadBuiltinCodeCache(args[i + 1]); + i++; } else { filtered_args.push_back(arg); } @@ -231,6 +302,17 @@ int RunNodeInstance(MultiIsolatePlatform* platform, setup = CommonEnvironmentSetup::CreateForSnapshotting( platform, &errors, filtered_args, exec_args); } + } else if (builtin_code_cache) { + setup = CommonEnvironmentSetup::Create( + platform, + &errors, + filtered_args, + exec_args, + node::EnvironmentFlags::kDefaultFlags, + node::ThreadId{}, + std::unique_ptr{}, + std::string_view{}, + builtin_code_cache.get()); } else { setup = CommonEnvironmentSetup::Create( platform, &errors, filtered_args, exec_args); @@ -250,6 +332,10 @@ int RunNodeInstance(MultiIsolatePlatform* platform, HandleScope handle_scope(isolate); Context::Scope context_scope(setup->context()); + if (!code_cache_out_path.empty()) { + return WriteBuiltinCodeCache(setup->context(), code_cache_out_path); + } + MaybeLocal loadenv_ret; if (snapshot) { // Deserializing snapshot loadenv_ret = node::LoadEnvironment(env, node::StartExecutionCallback{}); diff --git a/test/embedding/test-embedding-builtin-code-cache.js b/test/embedding/test-embedding-builtin-code-cache.js new file mode 100644 index 000000000000..0e75a2540d8b --- /dev/null +++ b/test/embedding/test-embedding-builtin-code-cache.js @@ -0,0 +1,51 @@ +'use strict'; +// An embedder that bootstraps Node.js without a snapshot can generate a code +// cache for the builtins ahead of time (EmbedderBuiltinCodeCache::Generate) +// and pass it to CreateEnvironment(); the bootstrap then compiles with that +// cache. Independently, it can ask Node.js not to serialize caches at runtime +// (kNoHarvestBuiltinCodeCache). +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +const assert = require('assert'); +const { spawnSyncAndAssert, spawnSyncAndExitWithoutError } = require('../common/child_process'); +const fs = require('fs'); + +tmpdir.refresh(); +const embedtest = common.resolveBuiltBinary('embedtest'); +const cacheFile = tmpdir.resolve('builtins.codecache'); + +spawnSyncAndExitWithoutError(embedtest, ['--', '--builtin-code-cache-create', cacheFile], { cwd: tmpdir.path }); +assert.ok(fs.statSync(cacheFile).size > 1024 * 1024); + +function compileLog(args) { + let log; + spawnSyncAndAssert( + embedtest, ['--', ...args, 'globalThis.ran = 40 + 2'], + { cwd: tmpdir.path, env: { ...process.env, NODE_DEBUG_NATIVE: 'CODE_CACHE' } }, + { stderr(output) { log = output; return true; } }); + return log; +} + +const without = compileLog([]); +assert.match(without, /Compiling internal\/bootstrap\/node without code cache/); + +const withCache = compileLog(['--builtin-code-cache', cacheFile]); +assert.doesNotMatch(withCache, /Compiling (?!internal\/per_context\/)\S+ without code cache/); +assert.match(withCache, /Code cache of internal\/bootstrap\/node \(BufferNotOwned\) is accepted/); + +// Harvesting: by default a builtin compiled without a cache serializes one that +// worker threads then start from; with kNoHarvestBuiltinCodeCache they do not. +const workerScript = 'new (require("worker_threads").Worker)("", { eval: true })'; +function workerCompileLog(args) { + let log; + spawnSyncAndAssert( + embedtest, ['--', ...args, workerScript], + { cwd: tmpdir.path, env: { ...process.env, NODE_DEBUG_NATIVE: 'CODE_CACHE' } }, + { stderr(output) { log = output; return true; } }); + const worker = log.slice(log.lastIndexOf('Compiling internal/bootstrap/realm')); + assert.notStrictEqual(worker, log); + return worker; +} +assert.match(workerCompileLog([]), /Code cache of internal\/bootstrap\/node \(\w+\) is accepted/); +assert.match(workerCompileLog(['--no-harvest-builtin-code-cache']), + /Compiling internal\/bootstrap\/node without code cache/);