Skip to content

Commit 4fd7193

Browse files
joyeecheungrefack
authored andcommitted
tools: implement mkcodecache as an executable
This patch implement a mkcodecache executable on top of the `NativeModuleLoader` singleton. This makes it possible to build a Node.js binary with embedded code cache without building itself using the code cache stub - the cache is now initialized by `NativeModuleEnv` instead which can be refactored out of the mkcodecache dependencies. PR-URL: #27161 Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent 1c26169 commit 4fd7193

9 files changed

+322
-172
lines changed

Makefile

+3-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ with-code-cache:
115115
$(PYTHON) ./configure $(CONFIG_FLAGS)
116116
$(MAKE)
117117
mkdir -p $(CODE_CACHE_DIR)
118-
out/$(BUILDTYPE)/$(NODE_EXE) --expose-internals tools/generate_code_cache.js $(CODE_CACHE_FILE)
118+
out/$(BUILDTYPE)/mkcodecache $(CODE_CACHE_FILE)
119119
$(PYTHON) ./configure --code-cache-path $(CODE_CACHE_FILE) $(CONFIG_FLAGS)
120120
$(MAKE)
121121

@@ -1232,6 +1232,8 @@ LINT_CPP_FILES = $(filter-out $(LINT_CPP_EXCLUDE), $(wildcard \
12321232
test/node-api/*/*.h \
12331233
tools/icu/*.cc \
12341234
tools/icu/*.h \
1235+
tools/code_cache/*.cc \
1236+
tools/code_cache/*.h \
12351237
))
12361238

12371239
# Code blocks don't have newline at the end,

node.gyp

+53
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,59 @@
11031103
}],
11041104
],
11051105
}, # cctest
1106+
# TODO(joyeecheung): do not depend on node_lib,
1107+
# instead create a smaller static library node_lib_base that does
1108+
# just enough for node_native_module.cc and the cache builder to
1109+
# compile without compiling the generated code cache C++ file.
1110+
# So generate_code_cache -> mkcodecache -> node_lib_base,
1111+
# node_lib -> node_lib_base & generate_code_cache
1112+
{
1113+
'target_name': 'mkcodecache',
1114+
'type': 'executable',
1115+
1116+
'dependencies': [
1117+
'<(node_lib_target_name)',
1118+
'deps/histogram/histogram.gyp:histogram',
1119+
],
1120+
1121+
'includes': [
1122+
'node.gypi'
1123+
],
1124+
1125+
'include_dirs': [
1126+
'src',
1127+
'tools/msvs/genfiles',
1128+
'deps/v8/include',
1129+
'deps/cares/include',
1130+
'deps/uv/include',
1131+
],
1132+
1133+
'defines': [ 'NODE_WANT_INTERNALS=1' ],
1134+
1135+
'sources': [
1136+
'tools/code_cache/mkcodecache.cc',
1137+
'tools/code_cache/cache_builder.cc'
1138+
],
1139+
1140+
'conditions': [
1141+
[ 'node_report=="true"', {
1142+
'conditions': [
1143+
['OS=="win"', {
1144+
'libraries': [
1145+
'dbghelp.lib',
1146+
'PsApi.lib',
1147+
'Ws2_32.lib',
1148+
],
1149+
'dll_files': [
1150+
'dbghelp.dll',
1151+
'PsApi.dll',
1152+
'Ws2_32.dll',
1153+
],
1154+
}],
1155+
],
1156+
}],
1157+
],
1158+
}, # cache_builder
11061159
], # end targets
11071160

11081161
'conditions': [

src/node_native_module_env.cc

-24
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
namespace node {
55
namespace native_module {
66

7-
using v8::ArrayBuffer;
87
using v8::Context;
98
using v8::DEFAULT;
109
using v8::Function;
@@ -18,11 +17,9 @@ using v8::Name;
1817
using v8::None;
1918
using v8::Object;
2019
using v8::PropertyCallbackInfo;
21-
using v8::ScriptCompiler;
2220
using v8::Set;
2321
using v8::SideEffectType;
2422
using v8::String;
25-
using v8::Uint8Array;
2623
using v8::Value;
2724

2825
// TODO(joyeecheung): make these more general and put them into util.h
@@ -154,26 +151,6 @@ MaybeLocal<Function> NativeModuleEnv::LookupAndCompile(
154151
return maybe;
155152
}
156153

157-
// This is supposed to be run only by the main thread in
158-
// tools/generate_code_cache.js
159-
void NativeModuleEnv::GetCodeCache(const FunctionCallbackInfo<Value>& args) {
160-
Environment* env = Environment::GetCurrent(args);
161-
Isolate* isolate = env->isolate();
162-
CHECK(env->is_main_thread());
163-
164-
CHECK(args[0]->IsString());
165-
node::Utf8Value id_v(isolate, args[0].As<String>());
166-
const char* id = *id_v;
167-
168-
ScriptCompiler::CachedData* cached_data =
169-
NativeModuleLoader::GetInstance()->GetCodeCache(id);
170-
if (cached_data != nullptr) {
171-
Local<ArrayBuffer> buf = ArrayBuffer::New(isolate, cached_data->length);
172-
memcpy(buf->GetContents().Data(), cached_data->data, cached_data->length);
173-
args.GetReturnValue().Set(Uint8Array::New(buf, 0, cached_data->length));
174-
}
175-
}
176-
177154
// TODO(joyeecheung): It is somewhat confusing that Class::Initialize
178155
// is used to initilaize to the binding, but it is the current convention.
179156
// Rename this across the code base to something that makes more sense.
@@ -216,7 +193,6 @@ void NativeModuleEnv::Initialize(Local<Object> target,
216193
.Check();
217194

218195
env->SetMethod(target, "getCacheUsage", NativeModuleEnv::GetCacheUsage);
219-
env->SetMethod(target, "getCodeCache", NativeModuleEnv::GetCodeCache);
220196
env->SetMethod(target, "compileFunction", NativeModuleEnv::CompileFunction);
221197
// internalBinding('native_module') should be frozen
222198
target->SetIntegrityLevel(context, IntegrityLevel::kFrozen).FromJust();

src/node_native_module_env.h

-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ class NativeModuleEnv {
5252
const v8::PropertyCallbackInfo<v8::Value>& info);
5353
// Compile a specific native module as a function
5454
static void CompileFunction(const v8::FunctionCallbackInfo<v8::Value>& args);
55-
static void GetCodeCache(const v8::FunctionCallbackInfo<v8::Value>& args);
5655
};
5756

5857
} // namespace native_module

test/code-cache/test-code-cache-generator.js

+23-11
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,40 @@
11
'use strict';
22

3-
// This test verifies that the binary is compiled with code cache and the
4-
// cache is used when built in modules are compiled.
3+
// This test verifies the code cache generator can generate a C++
4+
// file that contains the code cache. This can be removed once we
5+
// actually build that C++ file into our binary.
56

67
const common = require('../common');
7-
88
const tmpdir = require('../common/tmpdir');
99
const { spawnSync } = require('child_process');
1010
const assert = require('assert');
1111
const path = require('path');
1212
const fs = require('fs');
1313
const readline = require('readline');
1414

15-
const generator = path.join(
16-
__dirname, '..', '..', 'tools', 'generate_code_cache.js'
17-
);
15+
console.log('Looking for mkcodecache executable');
16+
let buildDir;
17+
const stat = fs.statSync(process.execPath);
18+
if (stat.isSymbolicLink()) {
19+
console.log('Binary is a symbolic link');
20+
buildDir = path.dirname(fs.readlinkSync(process.execPath));
21+
} else {
22+
buildDir = path.dirname(process.execPath);
23+
}
24+
25+
const ext = common.isWindows ? '.exe' : '';
26+
const generator = path.join(buildDir, `mkcodecache${ext}`);
27+
if (!fs.existsSync(generator)) {
28+
common.skip('Could not find mkcodecache');
29+
}
30+
31+
console.log(`mkcodecache is ${generator}`);
32+
1833
tmpdir.refresh();
1934
const dest = path.join(tmpdir.path, 'cache.cc');
2035

21-
// Run tools/generate_code_cache.js
22-
const child = spawnSync(
23-
process.execPath,
24-
['--expose-internals', generator, dest]
25-
);
36+
// Run mkcodecache
37+
const child = spawnSync(generator, [dest]);
2638
assert.ifError(child.error);
2739
if (child.status !== 0) {
2840
console.log(child.stderr.toString());

tools/code_cache/cache_builder.cc

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#include "cache_builder.h"
2+
#include <iostream>
3+
#include <map>
4+
#include <sstream>
5+
#include <vector>
6+
#include <cstdlib>
7+
#include "util.h"
8+
9+
#include "node_native_module.h"
10+
11+
namespace node {
12+
namespace native_module {
13+
14+
using v8::Context;
15+
using v8::Function;
16+
using v8::Isolate;
17+
using v8::Local;
18+
using v8::MaybeLocal;
19+
using v8::ScriptCompiler;
20+
21+
static std::string GetDefName(const std::string& id) {
22+
char buf[64] = {0};
23+
size_t size = id.size();
24+
CHECK_LT(size, sizeof(buf));
25+
for (size_t i = 0; i < size; ++i) {
26+
char ch = id[i];
27+
buf[i] = (ch == '-' || ch == '/') ? '_' : ch;
28+
}
29+
return buf;
30+
}
31+
32+
static std::string FormatSize(size_t size) {
33+
char buf[64] = {0};
34+
if (size < 1024) {
35+
snprintf(buf, sizeof(buf), "%.2fB", static_cast<double>(size));
36+
} else if (size < 1024 * 1024) {
37+
snprintf(buf, sizeof(buf), "%.2fKB", static_cast<double>(size / 1024));
38+
} else {
39+
snprintf(
40+
buf, sizeof(buf), "%.2fMB", static_cast<double>(size / 1024 / 1024));
41+
}
42+
return buf;
43+
}
44+
45+
static std::string GetDefinition(const std::string& id,
46+
size_t size,
47+
const uint8_t* data) {
48+
std::stringstream ss;
49+
ss << "static const uint8_t " << GetDefName(id) << "[] = {\n";
50+
for (size_t i = 0; i < size; ++i) {
51+
uint8_t ch = data[i];
52+
ss << std::to_string(ch) << (i == size - 1 ? '\n' : ',');
53+
}
54+
ss << "};";
55+
return ss.str();
56+
}
57+
58+
static std::string GetInitializer(const std::string& id) {
59+
std::string def_name = GetDefName(id);
60+
char buf[256] = {0};
61+
snprintf(buf,
62+
sizeof(buf),
63+
"code_cache->emplace(\n"
64+
" \"%s\",\n"
65+
" std::make_unique<v8::ScriptCompiler::CachedData>"
66+
"(%s, static_cast<int>(arraysize(%s)), policy)\n"
67+
");",
68+
id.c_str(),
69+
def_name.c_str(),
70+
def_name.c_str());
71+
return buf;
72+
}
73+
74+
static std::string GenerateCodeCache(
75+
std::map<std::string, ScriptCompiler::CachedData*> data,
76+
std::vector<std::string> ids,
77+
bool log_progress) {
78+
std::stringstream ss;
79+
ss << R"(#include <cinttypes>
80+
#include "node_native_module_env.h"
81+
82+
// This file is generated by tools/mkcodecache
83+
// and is used when configure is run with \`--code-cache-path\`
84+
85+
namespace node {
86+
namespace native_module {
87+
)";
88+
89+
size_t total = 0;
90+
for (const auto& x : data) {
91+
const std::string& id = x.first;
92+
ScriptCompiler::CachedData* cached_data = x.second;
93+
total += cached_data->length;
94+
std::string def = GetDefinition(id, cached_data->length, cached_data->data);
95+
ss << def << "\n\n";
96+
if (log_progress) {
97+
std::cout << "Generated cache for " << id
98+
<< ", size = " << FormatSize(cached_data->length)
99+
<< ", total = " << FormatSize(total) << "\n";
100+
}
101+
}
102+
103+
ss << R"(void NativeModuleEnv::InitializeCodeCache() {
104+
NativeModuleCacheMap* code_cache =
105+
NativeModuleLoader::GetInstance()->code_cache();
106+
if (!code_cache->empty()) {
107+
return;
108+
}
109+
auto policy = v8::ScriptCompiler::CachedData::BufferPolicy::BufferNotOwned;
110+
)";
111+
112+
for (const auto& x : data) {
113+
const std::string& id = x.first;
114+
ss << GetInitializer(id) << "\n\n";
115+
}
116+
117+
ss << R"(}
118+
119+
} // namespace native_module
120+
} // namespace node
121+
)";
122+
return ss.str();
123+
}
124+
125+
std::string CodeCacheBuilder::Generate(Local<Context> context) {
126+
NativeModuleLoader* loader = NativeModuleLoader::GetInstance();
127+
std::vector<std::string> ids = loader->GetModuleIds();
128+
129+
std::vector<std::string> modules;
130+
modules.reserve(ids.size());
131+
132+
std::map<std::string, ScriptCompiler::CachedData*> data;
133+
134+
NativeModuleLoader::Result result;
135+
for (const auto& id : ids) {
136+
// TODO(joyeecheung): we can only compile the modules that can be
137+
// required here because the parameters for other types of builtins
138+
// are still very flexible. We should look into auto-generating
139+
// the paramters from the source somehow.
140+
if (loader->CanBeRequired(id.c_str())) {
141+
modules.push_back(id);
142+
USE(loader->CompileAsModule(context, id.c_str(), &result));
143+
ScriptCompiler::CachedData* cached_data =
144+
loader->GetCodeCache(id.c_str());
145+
if (cached_data == nullptr) {
146+
// TODO(joyeecheung): display syntax errors
147+
std::cerr << "Failed to complile " << id << "\n";
148+
} else {
149+
data.emplace(id, cached_data);
150+
}
151+
}
152+
}
153+
154+
char env_buf[32];
155+
size_t env_size = sizeof(env_buf);
156+
int ret = uv_os_getenv("NODE_DEBUG", env_buf, &env_size);
157+
bool log_progress = false;
158+
if (ret == 0 && strcmp(env_buf, "mkcodecache") == 0) {
159+
log_progress = true;
160+
}
161+
return GenerateCodeCache(data, modules, log_progress);
162+
}
163+
164+
} // namespace native_module
165+
} // namespace node

tools/code_cache/cache_builder.h

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#ifndef TOOLS_CODE_CACHE_CACHE_BUILDER_H_
2+
#define TOOLS_CODE_CACHE_CACHE_BUILDER_H_
3+
4+
#include <string>
5+
#include "v8.h"
6+
7+
namespace node {
8+
namespace native_module {
9+
class CodeCacheBuilder {
10+
public:
11+
static std::string Generate(v8::Local<v8::Context> context);
12+
};
13+
} // namespace native_module
14+
} // namespace node
15+
16+
#endif // TOOLS_CODE_CACHE_CACHE_BUILDER_H_

0 commit comments

Comments
 (0)