Skip to content

Commit d29b195

Browse files
legendecasRafaelGSS
authored andcommitted
module: link module with a module request record
When a module is being statically linked with module requests, if two module requests with a same specifier but different attributes are resolved to two modules, the module requests should be linked to these two modules. PR-URL: #58886 Refs: https://tc39.es/ecma262/#sec-HostLoadImportedModule Refs: https://github.com/tc39/proposal-import-attributes?tab=readme-ov-file#how-would-this-proposal-work-with-caching Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent 94725fc commit d29b195

File tree

10 files changed

+216
-56
lines changed

10 files changed

+216
-56
lines changed

lib/internal/modules/esm/module_job.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,7 @@ class ModuleJob extends ModuleJobBase {
190190
const evaluationDepJobs = Array(moduleRequests.length);
191191
ObjectSetPrototypeOf(evaluationDepJobs, null);
192192

193-
// Specifiers should be aligned with the moduleRequests array in order.
194-
const specifiers = Array(moduleRequests.length);
193+
// Modules should be aligned with the moduleRequests array in order.
195194
const modulePromises = Array(moduleRequests.length);
196195
// Track each loop for whether it is an evaluation phase or source phase request.
197196
let isEvaluation;
@@ -217,11 +216,10 @@ class ModuleJob extends ModuleJobBase {
217216
return job.modulePromise;
218217
});
219218
modulePromises[idx] = modulePromise;
220-
specifiers[idx] = specifier;
221219
}
222220

223221
const modules = await SafePromiseAllReturnArrayLike(modulePromises);
224-
this.module.link(specifiers, modules);
222+
this.module.link(modules);
225223

226224
return evaluationDepJobs;
227225
}
@@ -433,22 +431,20 @@ class ModuleJobSync extends ModuleJobBase {
433431
this.#loader.loadCache.set(this.url, this.type, this);
434432
try {
435433
const moduleRequests = this.module.getModuleRequests();
436-
// Specifiers should be aligned with the moduleRequests array in order.
437-
const specifiers = Array(moduleRequests.length);
434+
// Modules should be aligned with the moduleRequests array in order.
438435
const modules = Array(moduleRequests.length);
439436
const evaluationDepJobs = Array(moduleRequests.length);
440437
let j = 0;
441438
for (let i = 0; i < moduleRequests.length; ++i) {
442439
const { specifier, attributes, phase } = moduleRequests[i];
443440
const job = this.#loader.getModuleJobForRequire(specifier, this.url, attributes, phase);
444-
specifiers[i] = specifier;
445441
modules[i] = job.module;
446442
if (phase === kEvaluationPhase) {
447443
evaluationDepJobs[j++] = job;
448444
}
449445
}
450446
evaluationDepJobs.length = j;
451-
this.module.link(specifiers, modules);
447+
this.module.link(modules);
452448
this.linked = evaluationDepJobs;
453449
} finally {
454450
// Restore it - if it succeeds, we'll reset in the caller; Otherwise it's

lib/internal/vm/module.js

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -148,23 +148,10 @@ class Module {
148148
});
149149
}
150150

151-
let registry = { __proto__: null };
152151
if (sourceText !== undefined) {
153152
this[kWrap] = new ModuleWrap(identifier, context, sourceText,
154153
options.lineOffset, options.columnOffset,
155154
options.cachedData);
156-
registry = {
157-
__proto__: null,
158-
initializeImportMeta: options.initializeImportMeta,
159-
importModuleDynamically: options.importModuleDynamically ?
160-
importModuleDynamicallyWrap(options.importModuleDynamically) :
161-
undefined,
162-
};
163-
// This will take precedence over the referrer as the object being
164-
// passed into the callbacks.
165-
registry.callbackReferrer = this;
166-
const { registerModule } = require('internal/modules/esm/utils');
167-
registerModule(this[kWrap], registry);
168155
} else {
169156
assert(syntheticEvaluationSteps);
170157
this[kWrap] = new ModuleWrap(identifier, context,
@@ -315,6 +302,19 @@ class SourceTextModule extends Module {
315302
importModuleDynamically,
316303
});
317304

305+
const registry = {
306+
__proto__: null,
307+
initializeImportMeta: options.initializeImportMeta,
308+
importModuleDynamically: options.importModuleDynamically ?
309+
importModuleDynamicallyWrap(options.importModuleDynamically) :
310+
undefined,
311+
};
312+
// This will take precedence over the referrer as the object being
313+
// passed into the callbacks.
314+
registry.callbackReferrer = this;
315+
const { registerModule } = require('internal/modules/esm/utils');
316+
registerModule(this[kWrap], registry);
317+
318318
this.#moduleRequests = ObjectFreeze(ArrayPrototypeMap(this[kWrap].getModuleRequests(), (request) => {
319319
return ObjectFreeze({
320320
__proto__: null,
@@ -329,8 +329,7 @@ class SourceTextModule extends Module {
329329
this.#statusOverride = 'linking';
330330

331331
// Iterates the module requests and links with the linker.
332-
// Specifiers should be aligned with the moduleRequests array in order.
333-
const specifiers = Array(this.#moduleRequests.length);
332+
// Modules should be aligned with the moduleRequests array in order.
334333
const modulePromises = Array(this.#moduleRequests.length);
335334
// Iterates with index to avoid calling into userspace with `Symbol.iterator`.
336335
for (let idx = 0; idx < this.#moduleRequests.length; idx++) {
@@ -357,12 +356,11 @@ class SourceTextModule extends Module {
357356
return module[kWrap];
358357
});
359358
modulePromises[idx] = modulePromise;
360-
specifiers[idx] = specifier;
361359
}
362360

363361
try {
364362
const modules = await SafePromiseAllReturnArrayLike(modulePromises);
365-
this[kWrap].link(specifiers, modules);
363+
this[kWrap].link(modules);
366364
} catch (e) {
367365
this.#error = e;
368366
throw e;

src/module_wrap.cc

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,51 @@ using v8::UnboundModuleScript;
6363
using v8::Undefined;
6464
using v8::Value;
6565

66+
void ModuleCacheKey::MemoryInfo(MemoryTracker* tracker) const {
67+
tracker->TrackField("specifier", specifier);
68+
tracker->TrackField("import_attributes", import_attributes);
69+
}
70+
71+
template <int elements_per_attribute>
72+
ModuleCacheKey ModuleCacheKey::From(Local<Context> context,
73+
Local<String> specifier,
74+
Local<FixedArray> import_attributes) {
75+
CHECK_EQ(import_attributes->Length() % elements_per_attribute, 0);
76+
Isolate* isolate = context->GetIsolate();
77+
std::size_t h1 = specifier->GetIdentityHash();
78+
size_t num_attributes = import_attributes->Length() / elements_per_attribute;
79+
ImportAttributeVector attributes;
80+
attributes.reserve(num_attributes);
81+
82+
std::size_t h2 = 0;
83+
84+
for (int i = 0; i < import_attributes->Length();
85+
i += elements_per_attribute) {
86+
Local<String> v8_key = import_attributes->Get(context, i).As<String>();
87+
Local<String> v8_value =
88+
import_attributes->Get(context, i + 1).As<String>();
89+
Utf8Value key_utf8(isolate, v8_key);
90+
Utf8Value value_utf8(isolate, v8_value);
91+
92+
attributes.emplace_back(key_utf8.ToString(), value_utf8.ToString());
93+
h2 ^= v8_key->GetIdentityHash();
94+
h2 ^= v8_value->GetIdentityHash();
95+
}
96+
97+
// Combine the hashes using a simple XOR and bit shift to reduce
98+
// collisions. Note that the hash does not guarantee uniqueness.
99+
std::size_t hash = h1 ^ (h2 << 1);
100+
101+
Utf8Value utf8_specifier(isolate, specifier);
102+
return ModuleCacheKey{utf8_specifier.ToString(), attributes, hash};
103+
}
104+
105+
ModuleCacheKey ModuleCacheKey::From(Local<Context> context,
106+
Local<ModuleRequest> v8_request) {
107+
return From(
108+
context, v8_request->GetSpecifier(), v8_request->GetImportAttributes());
109+
}
110+
66111
ModuleWrap::ModuleWrap(Realm* realm,
67112
Local<Object> object,
68113
Local<Module> module,
@@ -509,7 +554,7 @@ void ModuleWrap::GetModuleRequests(const FunctionCallbackInfo<Value>& args) {
509554
realm, isolate, module->GetModuleRequests()));
510555
}
511556

512-
// moduleWrap.link(specifiers, moduleWraps)
557+
// moduleWrap.link(moduleWraps)
513558
void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
514559
Realm* realm = Realm::GetCurrent(args);
515560
Isolate* isolate = args.GetIsolate();
@@ -518,33 +563,28 @@ void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
518563
ModuleWrap* dependent;
519564
ASSIGN_OR_RETURN_UNWRAP(&dependent, args.This());
520565

521-
CHECK_EQ(args.Length(), 2);
566+
CHECK_EQ(args.Length(), 1);
522567

523-
Local<Array> specifiers = args[0].As<Array>();
524-
Local<Array> modules = args[1].As<Array>();
525-
CHECK_EQ(specifiers->Length(), modules->Length());
568+
Local<FixedArray> requests =
569+
dependent->module_.Get(isolate)->GetModuleRequests();
570+
Local<Array> modules = args[0].As<Array>();
571+
CHECK_EQ(modules->Length(), static_cast<uint32_t>(requests->Length()));
526572

527-
std::vector<Global<Value>> specifiers_buffer;
528-
if (FromV8Array(context, specifiers, &specifiers_buffer).IsNothing()) {
529-
return;
530-
}
531573
std::vector<Global<Value>> modules_buffer;
532574
if (FromV8Array(context, modules, &modules_buffer).IsNothing()) {
533575
return;
534576
}
535577

536-
for (uint32_t i = 0; i < specifiers->Length(); i++) {
537-
Local<String> specifier_str =
538-
specifiers_buffer[i].Get(isolate).As<String>();
578+
for (uint32_t i = 0; i < modules_buffer.size(); i++) {
539579
Local<Object> module_object = modules_buffer[i].Get(isolate).As<Object>();
540580

541581
CHECK(
542582
realm->isolate_data()->module_wrap_constructor_template()->HasInstance(
543583
module_object));
544584

545-
Utf8Value specifier(isolate, specifier_str);
546-
dependent->resolve_cache_[specifier.ToString()].Reset(isolate,
547-
module_object);
585+
ModuleCacheKey module_cache_key = ModuleCacheKey::From(
586+
context, requests->Get(context, i).As<ModuleRequest>());
587+
dependent->resolve_cache_[module_cache_key].Reset(isolate, module_object);
548588
}
549589
}
550590

@@ -924,27 +964,27 @@ MaybeLocal<Module> ModuleWrap::ResolveModuleCallback(
924964
return MaybeLocal<Module>();
925965
}
926966

927-
Utf8Value specifier_utf8(isolate, specifier);
928-
std::string specifier_std(*specifier_utf8, specifier_utf8.length());
967+
ModuleCacheKey cache_key =
968+
ModuleCacheKey::From(context, specifier, import_attributes);
929969

930970
ModuleWrap* dependent = GetFromModule(env, referrer);
931971
if (dependent == nullptr) {
932972
THROW_ERR_VM_MODULE_LINK_FAILURE(
933-
env, "request for '%s' is from invalid module", specifier_std);
973+
env, "request for '%s' is from invalid module", cache_key.specifier);
934974
return MaybeLocal<Module>();
935975
}
936976

937-
if (dependent->resolve_cache_.count(specifier_std) != 1) {
977+
if (dependent->resolve_cache_.count(cache_key) != 1) {
938978
THROW_ERR_VM_MODULE_LINK_FAILURE(
939-
env, "request for '%s' is not in cache", specifier_std);
979+
env, "request for '%s' is not in cache", cache_key.specifier);
940980
return MaybeLocal<Module>();
941981
}
942982

943983
Local<Object> module_object =
944-
dependent->resolve_cache_[specifier_std].Get(isolate);
984+
dependent->resolve_cache_[cache_key].Get(isolate);
945985
if (module_object.IsEmpty() || !module_object->IsObject()) {
946986
THROW_ERR_VM_MODULE_LINK_FAILURE(
947-
env, "request for '%s' did not return an object", specifier_std);
987+
env, "request for '%s' did not return an object", cache_key.specifier);
948988
return MaybeLocal<Module>();
949989
}
950990

@@ -965,27 +1005,27 @@ MaybeLocal<Object> ModuleWrap::ResolveSourceCallback(
9651005
return MaybeLocal<Object>();
9661006
}
9671007

968-
Utf8Value specifier_utf8(isolate, specifier);
969-
std::string specifier_std(*specifier_utf8, specifier_utf8.length());
1008+
ModuleCacheKey cache_key =
1009+
ModuleCacheKey::From(context, specifier, import_attributes);
9701010

9711011
ModuleWrap* dependent = GetFromModule(env, referrer);
9721012
if (dependent == nullptr) {
9731013
THROW_ERR_VM_MODULE_LINK_FAILURE(
974-
env, "request for '%s' is from invalid module", specifier_std);
1014+
env, "request for '%s' is from invalid module", cache_key.specifier);
9751015
return MaybeLocal<Object>();
9761016
}
9771017

978-
if (dependent->resolve_cache_.count(specifier_std) != 1) {
1018+
if (dependent->resolve_cache_.count(cache_key) != 1) {
9791019
THROW_ERR_VM_MODULE_LINK_FAILURE(
980-
env, "request for '%s' is not in cache", specifier_std);
1020+
env, "request for '%s' is not in cache", cache_key.specifier);
9811021
return MaybeLocal<Object>();
9821022
}
9831023

9841024
Local<Object> module_object =
985-
dependent->resolve_cache_[specifier_std].Get(isolate);
1025+
dependent->resolve_cache_[cache_key].Get(isolate);
9861026
if (module_object.IsEmpty() || !module_object->IsObject()) {
9871027
THROW_ERR_VM_MODULE_LINK_FAILURE(
988-
env, "request for '%s' did not return an object", specifier_std);
1028+
env, "request for '%s' did not return an object", cache_key.specifier);
9891029
return MaybeLocal<Object>();
9901030
}
9911031

src/module_wrap.h

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,57 @@ enum ModulePhase : int {
3838
kEvaluationPhase = 2,
3939
};
4040

41+
/**
42+
* ModuleCacheKey is used to uniquely identify a module request
43+
* in the module cache. It is a composition of the module specifier
44+
* and the import attributes. ModuleImportPhase is not included
45+
* in the key.
46+
*/
47+
struct ModuleCacheKey : public MemoryRetainer {
48+
using ImportAttributeVector =
49+
std::vector<std::pair<std::string, std::string>>;
50+
51+
std::string specifier;
52+
ImportAttributeVector import_attributes;
53+
// A hash of the specifier, and import attributes.
54+
// This does not guarantee uniqueness, but is used to reduce
55+
// the number of comparisons needed when checking for equality.
56+
std::size_t hash;
57+
58+
SET_MEMORY_INFO_NAME(ModuleCacheKey)
59+
SET_SELF_SIZE(ModuleCacheKey)
60+
void MemoryInfo(MemoryTracker* tracker) const override;
61+
62+
template <int elements_per_attribute = 3>
63+
static ModuleCacheKey From(v8::Local<v8::Context> context,
64+
v8::Local<v8::String> specifier,
65+
v8::Local<v8::FixedArray> import_attributes);
66+
static ModuleCacheKey From(v8::Local<v8::Context> context,
67+
v8::Local<v8::ModuleRequest> v8_request);
68+
69+
struct Hash {
70+
std::size_t operator()(const ModuleCacheKey& request) const {
71+
return request.hash;
72+
}
73+
};
74+
75+
// Equality operator for ModuleCacheKey.
76+
bool operator==(const ModuleCacheKey& other) const {
77+
// Hash does not provide uniqueness guarantee, so ignore it.
78+
return specifier == other.specifier &&
79+
import_attributes == other.import_attributes;
80+
}
81+
82+
private:
83+
// Use public ModuleCacheKey::From to create instances.
84+
ModuleCacheKey(std::string specifier,
85+
ImportAttributeVector import_attributes,
86+
std::size_t hash)
87+
: specifier(specifier),
88+
import_attributes(import_attributes),
89+
hash(hash) {}
90+
};
91+
4192
class ModuleWrap : public BaseObject {
4293
public:
4394
enum InternalFields {
@@ -149,7 +200,10 @@ class ModuleWrap : public BaseObject {
149200
static ModuleWrap* GetFromModule(node::Environment*, v8::Local<v8::Module>);
150201

151202
v8::Global<v8::Module> module_;
152-
std::unordered_map<std::string, v8::Global<v8::Object>> resolve_cache_;
203+
std::unordered_map<ModuleCacheKey,
204+
v8::Global<v8::Object>,
205+
ModuleCacheKey::Hash>
206+
resolve_cache_;
153207
contextify::ContextifyContext* contextify_context_ = nullptr;
154208
bool synthetic_ = false;
155209
int module_hash_;

0 commit comments

Comments
 (0)