Skip to content

Commit b7aded3

Browse files
joyeecheungBridgeAR
authored andcommitted
src: compile native modules and their code cache in C++
This patch refactors out a part of NativeModule.prototype.compile (in JS land) into a C++ NativeModule class, this enables a couple of possibilities: 1. By moving the code to the C++ land, we have more opportunity to specialize the compilation process of the native modules (e.g. compilation options, code cache) that is orthogonal to how user land modules are compiled 2. We can reuse the code to compile bootstrappers and context fixers and enable them to be compiled with the code cache later, since they are not loaded by NativeModule in the JS land their caching must be done in C++. 3. Since there is no need to pass the static data to JS for compilation anymore, this enables us to use (std::map<std::string, const char*>) in the generated node_code_cache.cc and node_javascript.cc later, and scope every actual access to the source of native modules to a std::map lookup instead of a lookup on a v8::Object in dictionary mode. This patch also refactor the code cache generator and tests a bit and trace the `withCodeCache` and `withoutCodeCache` in a Set instead of an Array, and makes sure that all the cachable builtins are tested. PR-URL: #24221 Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent 4709fe6 commit b7aded3

12 files changed

+680
-364
lines changed

lib/internal/bootstrap/cache.js

+8-22
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,13 @@
88
const {
99
NativeModule
1010
} = require('internal/bootstrap/loaders');
11+
const {
12+
source,
13+
compileCodeCache
14+
} = internalBinding('native_module');
1115
const { hasTracing } = process.binding('config');
1216

13-
function getCodeCache(id) {
14-
const cached = NativeModule.getCached(id);
15-
if (cached && (cached.loaded || cached.loading)) {
16-
return cached.script.createCachedData();
17-
}
18-
19-
// The script has not been compiled and run
20-
NativeModule.require(id);
21-
return getCodeCache(id);
22-
}
23-
24-
const depsModule = Object.keys(NativeModule._source).filter(
17+
const depsModule = Object.keys(source).filter(
2518
(key) => NativeModule.isDepsModule(key) || key.startsWith('internal/deps')
2619
);
2720

@@ -75,17 +68,10 @@ if (!process.versions.openssl) {
7568
}
7669

7770
module.exports = {
78-
cachableBuiltins: Object.keys(NativeModule._source).filter(
71+
cachableBuiltins: Object.keys(source).filter(
7972
(key) => !cannotUseCache.includes(key)
8073
),
81-
builtinSource: Object.assign({}, NativeModule._source),
82-
getCodeCache,
83-
getSource: NativeModule.getSource,
84-
codeCache: internalBinding('code_cache'),
85-
compiledWithoutCache: NativeModule.compiledWithoutCache,
86-
compiledWithCache: NativeModule.compiledWithCache,
87-
nativeModuleWrap(script) {
88-
return NativeModule.wrap(script);
89-
},
74+
getSource(id) { return source[id]; },
75+
getCodeCache: compileCodeCache,
9076
cannotUseCache
9177
};

lib/internal/bootstrap/loaders.js

+3-60
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,6 @@
149149

150150
// Create this WeakMap in js-land because V8 has no C++ API for WeakMap
151151
internalBinding('module_wrap').callbackMap = new WeakMap();
152-
const { ContextifyScript } = internalBinding('contextify');
153152

154153
// Set up NativeModule
155154
function NativeModule(id) {
@@ -160,20 +159,13 @@
160159
this.exportKeys = undefined;
161160
this.loaded = false;
162161
this.loading = false;
163-
this.script = null; // The ContextifyScript of the module
164162
}
165163

166164
NativeModule._source = getInternalBinding('natives');
167165
NativeModule._cache = {};
168166

169167
const config = getBinding('config');
170168

171-
const codeCache = getInternalBinding('code_cache');
172-
const codeCacheHash = getInternalBinding('code_cache_hash');
173-
const sourceHash = getInternalBinding('natives_hash');
174-
const compiledWithoutCache = NativeModule.compiledWithoutCache = [];
175-
const compiledWithCache = NativeModule.compiledWithCache = [];
176-
177169
// Think of this as module.exports in this file even though it is not
178170
// written in CommonJS style.
179171
const loaderExports = { internalBinding, NativeModule };
@@ -332,67 +324,18 @@
332324
this.exports = new Proxy(this.exports, handler);
333325
};
334326

327+
const { compileFunction } = getInternalBinding('native_module');
335328
NativeModule.prototype.compile = function() {
336329
const id = this.id;
337-
let source = NativeModule.getSource(id);
338-
source = NativeModule.wrap(source);
339330

340331
this.loading = true;
341332

342333
try {
343-
// Currently V8 only checks that the length of the source code is the
344-
// same as the code used to generate the hash, so we add an additional
345-
// check here:
346-
// 1. During compile time, when generating node_javascript.cc and
347-
// node_code_cache.cc, we compute and include the hash of the
348-
// (unwrapped) JavaScript source in both.
349-
// 2. At runtime, we check that the hash of the code being compiled
350-
// and the hash of the code used to generate the cache
351-
// (inside the wrapper) is the same.
352-
// This is based on the assumptions:
353-
// 1. `internalBinding('code_cache_hash')` must be in sync with
354-
// `internalBinding('code_cache')` (same C++ file)
355-
// 2. `internalBinding('natives_hash')` must be in sync with
356-
// `internalBinding('natives')` (same C++ file)
357-
// 3. If `internalBinding('natives_hash')` is in sync with
358-
// `internalBinding('natives_hash')`, then the (unwrapped)
359-
// code used to generate `internalBinding('code_cache')`
360-
// should be in sync with the (unwrapped) code in
361-
// `internalBinding('natives')`
362-
// There will be, however, false positives if the wrapper used
363-
// to generate the cache is different from the one used at run time,
364-
// and the length of the wrapper somehow stays the same.
365-
// But that should be rare and can be eased once we make the
366-
// two bootstrappers cached and checked as well.
367-
const cache = codeCacheHash[id] &&
368-
(codeCacheHash[id] === sourceHash[id]) ? codeCache[id] : undefined;
369-
370-
// (code, filename, lineOffset, columnOffset
371-
// cachedData, produceCachedData, parsingContext)
372-
const script = new ContextifyScript(
373-
source, this.filename, 0, 0,
374-
cache, false, undefined
375-
);
376-
377-
// This will be used to create code cache in tools/generate_code_cache.js
378-
this.script = script;
379-
380-
// One of these conditions may be false when any of the inputs
381-
// of the `node_js2c` target in node.gyp is modified.
382-
// FIXME(joyeecheung): Figure out how to resolve the dependency issue.
383-
// When the code cache was introduced we were at a point where refactoring
384-
// node.gyp may not be worth the effort.
385-
if (!cache || script.cachedDataRejected) {
386-
compiledWithoutCache.push(this.id);
387-
} else {
388-
compiledWithCache.push(this.id);
389-
}
390-
391-
// Arguments: timeout, displayErrors, breakOnSigint
392-
const fn = script.runInThisContext(-1, true, false);
393334
const requireFn = this.id.startsWith('internal/deps/') ?
394335
NativeModule.requireForDeps :
395336
NativeModule.require;
337+
338+
const fn = compileFunction(id);
396339
fn(this.exports, requireFn, this, process, internalBinding);
397340

398341
if (config.experimentalModules && !NativeModule.isInternal(this.id)) {

node.gyp

+2
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,8 @@
416416
'src/node_javascript.h',
417417
'src/node_messaging.h',
418418
'src/node_mutex.h',
419+
'src/node_native_module.h',
420+
'src/node_native_module.cc',
419421
'src/node_options.h',
420422
'src/node_options-inl.h',
421423
'src/node_perf.h',

0 commit comments

Comments
 (0)