Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions lib/internal/modules/esm/module_job.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,7 @@ class ModuleJob extends ModuleJobBase {
const evaluationDepJobs = Array(moduleRequests.length);
ObjectSetPrototypeOf(evaluationDepJobs, null);

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

const modules = await SafePromiseAllReturnArrayLike(modulePromises);
this.module.link(specifiers, modules);
this.module.link(modules);

return evaluationDepJobs;
}
Expand Down Expand Up @@ -433,22 +431,20 @@ class ModuleJobSync extends ModuleJobBase {
this.#loader.loadCache.set(this.url, this.type, this);
try {
const moduleRequests = this.module.getModuleRequests();
// Specifiers should be aligned with the moduleRequests array in order.
const specifiers = Array(moduleRequests.length);
// Modules should be aligned with the moduleRequests array in order.
const modules = Array(moduleRequests.length);
const evaluationDepJobs = Array(moduleRequests.length);
let j = 0;
for (let i = 0; i < moduleRequests.length; ++i) {
const { specifier, attributes, phase } = moduleRequests[i];
const job = this.#loader.getModuleJobForRequire(specifier, this.url, attributes, phase);
specifiers[i] = specifier;
modules[i] = job.module;
if (phase === kEvaluationPhase) {
evaluationDepJobs[j++] = job;
}
}
evaluationDepJobs.length = j;
this.module.link(specifiers, modules);
this.module.link(modules);
this.linked = evaluationDepJobs;
} finally {
// Restore it - if it succeeds, we'll reset in the caller; Otherwise it's
Expand Down
32 changes: 15 additions & 17 deletions lib/internal/vm/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,23 +148,10 @@ class Module {
});
}

let registry = { __proto__: null };
if (sourceText !== undefined) {
this[kWrap] = new ModuleWrap(identifier, context, sourceText,
options.lineOffset, options.columnOffset,
options.cachedData);
registry = {
__proto__: null,
initializeImportMeta: options.initializeImportMeta,
importModuleDynamically: options.importModuleDynamically ?
importModuleDynamicallyWrap(options.importModuleDynamically) :
undefined,
};
// This will take precedence over the referrer as the object being
// passed into the callbacks.
registry.callbackReferrer = this;
const { registerModule } = require('internal/modules/esm/utils');
registerModule(this[kWrap], registry);
} else {
assert(syntheticEvaluationSteps);
this[kWrap] = new ModuleWrap(identifier, context,
Expand Down Expand Up @@ -315,6 +302,19 @@ class SourceTextModule extends Module {
importModuleDynamically,
});

const registry = {
__proto__: null,
initializeImportMeta: options.initializeImportMeta,
importModuleDynamically: options.importModuleDynamically ?
importModuleDynamicallyWrap(options.importModuleDynamically) :
undefined,
};
// This will take precedence over the referrer as the object being
// passed into the callbacks.
registry.callbackReferrer = this;
const { registerModule } = require('internal/modules/esm/utils');
registerModule(this[kWrap], registry);

this.#moduleRequests = ObjectFreeze(ArrayPrototypeMap(this[kWrap].getModuleRequests(), (request) => {
return ObjectFreeze({
__proto__: null,
Expand All @@ -329,8 +329,7 @@ class SourceTextModule extends Module {
this.#statusOverride = 'linking';

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

try {
const modules = await SafePromiseAllReturnArrayLike(modulePromises);
this[kWrap].link(specifiers, modules);
this[kWrap].link(modules);
} catch (e) {
this.#error = e;
throw e;
Expand Down
98 changes: 69 additions & 29 deletions src/module_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,51 @@ using v8::UnboundModuleScript;
using v8::Undefined;
using v8::Value;

void ModuleCacheKey::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("specifier", specifier);
tracker->TrackField("import_attributes", import_attributes);
}

template <int elements_per_attribute>
ModuleCacheKey ModuleCacheKey::From(Local<Context> context,
Local<String> specifier,
Local<FixedArray> import_attributes) {
CHECK_EQ(import_attributes->Length() % elements_per_attribute, 0);
Isolate* isolate = context->GetIsolate();
std::size_t h1 = specifier->GetIdentityHash();
size_t num_attributes = import_attributes->Length() / elements_per_attribute;
ImportAttributeVector attributes;
attributes.reserve(num_attributes);

std::size_t h2 = 0;

for (int i = 0; i < import_attributes->Length();
i += elements_per_attribute) {
Local<String> v8_key = import_attributes->Get(context, i).As<String>();
Local<String> v8_value =
import_attributes->Get(context, i + 1).As<String>();
Utf8Value key_utf8(isolate, v8_key);
Utf8Value value_utf8(isolate, v8_value);

attributes.emplace_back(key_utf8.ToString(), value_utf8.ToString());
h2 ^= v8_key->GetIdentityHash();
h2 ^= v8_value->GetIdentityHash();
}

// Combine the hashes using a simple XOR and bit shift to reduce
// collisions. Note that the hash does not guarantee uniqueness.
std::size_t hash = h1 ^ (h2 << 1);

Utf8Value utf8_specifier(isolate, specifier);
return ModuleCacheKey{utf8_specifier.ToString(), attributes, hash};
}

ModuleCacheKey ModuleCacheKey::From(Local<Context> context,
Local<ModuleRequest> v8_request) {
return From(
context, v8_request->GetSpecifier(), v8_request->GetImportAttributes());
}

ModuleWrap::ModuleWrap(Realm* realm,
Local<Object> object,
Local<Module> module,
Expand Down Expand Up @@ -509,7 +554,7 @@ void ModuleWrap::GetModuleRequests(const FunctionCallbackInfo<Value>& args) {
realm, isolate, module->GetModuleRequests()));
}

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

CHECK_EQ(args.Length(), 2);
CHECK_EQ(args.Length(), 1);

Local<Array> specifiers = args[0].As<Array>();
Local<Array> modules = args[1].As<Array>();
CHECK_EQ(specifiers->Length(), modules->Length());
Local<FixedArray> requests =
dependent->module_.Get(isolate)->GetModuleRequests();
Local<Array> modules = args[0].As<Array>();
CHECK_EQ(modules->Length(), static_cast<uint32_t>(requests->Length()));

std::vector<Global<Value>> specifiers_buffer;
if (FromV8Array(context, specifiers, &specifiers_buffer).IsNothing()) {
return;
}
std::vector<Global<Value>> modules_buffer;
if (FromV8Array(context, modules, &modules_buffer).IsNothing()) {
return;
}

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

CHECK(
realm->isolate_data()->module_wrap_constructor_template()->HasInstance(
module_object));

Utf8Value specifier(isolate, specifier_str);
dependent->resolve_cache_[specifier.ToString()].Reset(isolate,
module_object);
ModuleCacheKey module_cache_key = ModuleCacheKey::From(
context, requests->Get(context, i).As<ModuleRequest>());
dependent->resolve_cache_[module_cache_key].Reset(isolate, module_object);
}
}

Expand Down Expand Up @@ -924,27 +964,27 @@ MaybeLocal<Module> ModuleWrap::ResolveModuleCallback(
return MaybeLocal<Module>();
}

Utf8Value specifier_utf8(isolate, specifier);
std::string specifier_std(*specifier_utf8, specifier_utf8.length());
ModuleCacheKey cache_key =
ModuleCacheKey::From(context, specifier, import_attributes);

ModuleWrap* dependent = GetFromModule(env, referrer);
if (dependent == nullptr) {
THROW_ERR_VM_MODULE_LINK_FAILURE(
env, "request for '%s' is from invalid module", specifier_std);
env, "request for '%s' is from invalid module", cache_key.specifier);
return MaybeLocal<Module>();
}

if (dependent->resolve_cache_.count(specifier_std) != 1) {
if (dependent->resolve_cache_.count(cache_key) != 1) {
THROW_ERR_VM_MODULE_LINK_FAILURE(
env, "request for '%s' is not in cache", specifier_std);
env, "request for '%s' is not in cache", cache_key.specifier);
return MaybeLocal<Module>();
}

Local<Object> module_object =
dependent->resolve_cache_[specifier_std].Get(isolate);
dependent->resolve_cache_[cache_key].Get(isolate);
if (module_object.IsEmpty() || !module_object->IsObject()) {
THROW_ERR_VM_MODULE_LINK_FAILURE(
env, "request for '%s' did not return an object", specifier_std);
env, "request for '%s' did not return an object", cache_key.specifier);
return MaybeLocal<Module>();
}

Expand All @@ -965,27 +1005,27 @@ MaybeLocal<Object> ModuleWrap::ResolveSourceCallback(
return MaybeLocal<Object>();
}

Utf8Value specifier_utf8(isolate, specifier);
std::string specifier_std(*specifier_utf8, specifier_utf8.length());
ModuleCacheKey cache_key =
ModuleCacheKey::From(context, specifier, import_attributes);

ModuleWrap* dependent = GetFromModule(env, referrer);
if (dependent == nullptr) {
THROW_ERR_VM_MODULE_LINK_FAILURE(
env, "request for '%s' is from invalid module", specifier_std);
env, "request for '%s' is from invalid module", cache_key.specifier);
return MaybeLocal<Object>();
}

if (dependent->resolve_cache_.count(specifier_std) != 1) {
if (dependent->resolve_cache_.count(cache_key) != 1) {
THROW_ERR_VM_MODULE_LINK_FAILURE(
env, "request for '%s' is not in cache", specifier_std);
env, "request for '%s' is not in cache", cache_key.specifier);
return MaybeLocal<Object>();
}

Local<Object> module_object =
dependent->resolve_cache_[specifier_std].Get(isolate);
dependent->resolve_cache_[cache_key].Get(isolate);
if (module_object.IsEmpty() || !module_object->IsObject()) {
THROW_ERR_VM_MODULE_LINK_FAILURE(
env, "request for '%s' did not return an object", specifier_std);
env, "request for '%s' did not return an object", cache_key.specifier);
return MaybeLocal<Object>();
}

Expand Down
56 changes: 55 additions & 1 deletion src/module_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,57 @@ enum ModulePhase : int {
kEvaluationPhase = 2,
};

/**
* ModuleCacheKey is used to uniquely identify a module request
* in the module cache. It is a composition of the module specifier
* and the import attributes. ModuleImportPhase is not included
* in the key.
*/
struct ModuleCacheKey : public MemoryRetainer {
using ImportAttributeVector =
std::vector<std::pair<std::string, std::string>>;

std::string specifier;
ImportAttributeVector import_attributes;
// A hash of the specifier, and import attributes.
// This does not guarantee uniqueness, but is used to reduce
// the number of comparisons needed when checking for equality.
std::size_t hash;

SET_MEMORY_INFO_NAME(ModuleCacheKey)
SET_SELF_SIZE(ModuleCacheKey)
void MemoryInfo(MemoryTracker* tracker) const override;

template <int elements_per_attribute = 3>
static ModuleCacheKey From(v8::Local<v8::Context> context,
v8::Local<v8::String> specifier,
v8::Local<v8::FixedArray> import_attributes);
static ModuleCacheKey From(v8::Local<v8::Context> context,
v8::Local<v8::ModuleRequest> v8_request);

struct Hash {
std::size_t operator()(const ModuleCacheKey& request) const {
return request.hash;
}
};

// Equality operator for ModuleCacheKey.
bool operator==(const ModuleCacheKey& other) const {
// Hash does not provide uniqueness guarantee, so ignore it.
return specifier == other.specifier &&
import_attributes == other.import_attributes;
}

private:
// Use public ModuleCacheKey::From to create instances.
ModuleCacheKey(std::string specifier,
ImportAttributeVector import_attributes,
std::size_t hash)
: specifier(specifier),
import_attributes(import_attributes),
hash(hash) {}
};

class ModuleWrap : public BaseObject {
public:
enum InternalFields {
Expand Down Expand Up @@ -149,7 +200,10 @@ class ModuleWrap : public BaseObject {
static ModuleWrap* GetFromModule(node::Environment*, v8::Local<v8::Module>);

v8::Global<v8::Module> module_;
std::unordered_map<std::string, v8::Global<v8::Object>> resolve_cache_;
std::unordered_map<ModuleCacheKey,
v8::Global<v8::Object>,
ModuleCacheKey::Hash>
resolve_cache_;
contextify::ContextifyContext* contextify_context_ = nullptr;
bool synthetic_ = false;
int module_hash_;
Expand Down
Loading
Loading