summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp')
-rw-r--r--Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp769
1 files changed, 769 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp
new file mode 100644
index 000000000..f8854e2c2
--- /dev/null
+++ b/Source/JavaScriptCore/runtime/AbstractModuleRecord.cpp
@@ -0,0 +1,769 @@
+/*
+ * Copyright (C) 2015-2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "AbstractModuleRecord.h"
+
+#include "Error.h"
+#include "Interpreter.h"
+#include "JSCInlines.h"
+#include "JSMap.h"
+#include "JSModuleEnvironment.h"
+#include "JSModuleNamespaceObject.h"
+#include "UnlinkedModuleProgramCodeBlock.h"
+
+namespace JSC {
+
+const ClassInfo AbstractModuleRecord::s_info = { "AbstractModuleRecord", &Base::s_info, 0, CREATE_METHOD_TABLE(AbstractModuleRecord) };
+
+AbstractModuleRecord::AbstractModuleRecord(VM& vm, Structure* structure, const Identifier& moduleKey)
+ : Base(vm, structure)
+ , m_moduleKey(moduleKey)
+{
+}
+
+void AbstractModuleRecord::destroy(JSCell* cell)
+{
+ AbstractModuleRecord* thisObject = static_cast<AbstractModuleRecord*>(cell);
+ thisObject->AbstractModuleRecord::~AbstractModuleRecord();
+}
+
+void AbstractModuleRecord::finishCreation(ExecState* exec, VM& vm)
+{
+ Base::finishCreation(vm);
+ ASSERT(inherits(vm, info()));
+ putDirect(vm, Identifier::fromString(&vm, ASCIILiteral("registryEntry")), jsUndefined());
+ putDirect(vm, Identifier::fromString(&vm, ASCIILiteral("evaluated")), jsBoolean(false));
+
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ JSMap* map = JSMap::create(exec, vm, globalObject()->mapStructure());
+ RELEASE_ASSERT(!scope.exception());
+ m_dependenciesMap.set(vm, this, map);
+ putDirect(vm, Identifier::fromString(&vm, ASCIILiteral("dependenciesMap")), m_dependenciesMap.get());
+}
+
+void AbstractModuleRecord::visitChildren(JSCell* cell, SlotVisitor& visitor)
+{
+ AbstractModuleRecord* thisObject = jsCast<AbstractModuleRecord*>(cell);
+ Base::visitChildren(thisObject, visitor);
+ visitor.append(thisObject->m_moduleEnvironment);
+ visitor.append(thisObject->m_moduleNamespaceObject);
+ visitor.append(thisObject->m_dependenciesMap);
+}
+
+void AbstractModuleRecord::appendRequestedModule(const Identifier& moduleName)
+{
+ m_requestedModules.add(moduleName.impl());
+}
+
+void AbstractModuleRecord::addStarExportEntry(const Identifier& moduleName)
+{
+ m_starExportEntries.add(moduleName.impl());
+}
+
+void AbstractModuleRecord::addImportEntry(const ImportEntry& entry)
+{
+ bool isNewEntry = m_importEntries.add(entry.localName.impl(), entry).isNewEntry;
+ ASSERT_UNUSED(isNewEntry, isNewEntry); // This is guaranteed by the parser.
+}
+
+void AbstractModuleRecord::addExportEntry(const ExportEntry& entry)
+{
+ bool isNewEntry = m_exportEntries.add(entry.exportName.impl(), entry).isNewEntry;
+ ASSERT_UNUSED(isNewEntry, isNewEntry); // This is guaranteed by the parser.
+}
+
+auto AbstractModuleRecord::tryGetImportEntry(UniquedStringImpl* localName) -> std::optional<ImportEntry>
+{
+ const auto iterator = m_importEntries.find(localName);
+ if (iterator == m_importEntries.end())
+ return std::nullopt;
+ return std::optional<ImportEntry>(iterator->value);
+}
+
+auto AbstractModuleRecord::tryGetExportEntry(UniquedStringImpl* exportName) -> std::optional<ExportEntry>
+{
+ const auto iterator = m_exportEntries.find(exportName);
+ if (iterator == m_exportEntries.end())
+ return std::nullopt;
+ return std::optional<ExportEntry>(iterator->value);
+}
+
+auto AbstractModuleRecord::ExportEntry::createLocal(const Identifier& exportName, const Identifier& localName) -> ExportEntry
+{
+ return ExportEntry { Type::Local, exportName, Identifier(), Identifier(), localName };
+}
+
+auto AbstractModuleRecord::ExportEntry::createIndirect(const Identifier& exportName, const Identifier& importName, const Identifier& moduleName) -> ExportEntry
+{
+ return ExportEntry { Type::Indirect, exportName, moduleName, importName, Identifier() };
+}
+
+auto AbstractModuleRecord::Resolution::notFound() -> Resolution
+{
+ return Resolution { Type::NotFound, nullptr, Identifier() };
+}
+
+auto AbstractModuleRecord::Resolution::error() -> Resolution
+{
+ return Resolution { Type::Error, nullptr, Identifier() };
+}
+
+auto AbstractModuleRecord::Resolution::ambiguous() -> Resolution
+{
+ return Resolution { Type::Ambiguous, nullptr, Identifier() };
+}
+
+static JSValue identifierToJSValue(ExecState* exec, const Identifier& identifier)
+{
+ if (identifier.isSymbol())
+ return Symbol::create(exec->vm(), static_cast<SymbolImpl&>(*identifier.impl()));
+ return jsString(&exec->vm(), identifier.impl());
+}
+
+AbstractModuleRecord* AbstractModuleRecord::hostResolveImportedModule(ExecState* exec, const Identifier& moduleName)
+{
+ JSValue moduleNameValue = identifierToJSValue(exec, moduleName);
+ JSValue pair = m_dependenciesMap->JSMap::get(exec, moduleNameValue);
+ return jsCast<AbstractModuleRecord*>(pair.get(exec, Identifier::fromString(exec, "value")));
+}
+
+auto AbstractModuleRecord::resolveImport(ExecState* exec, const Identifier& localName) -> Resolution
+{
+ std::optional<ImportEntry> optionalImportEntry = tryGetImportEntry(localName.impl());
+ if (!optionalImportEntry)
+ return Resolution::notFound();
+
+ const ImportEntry& importEntry = *optionalImportEntry;
+ if (importEntry.isNamespace(exec->vm()))
+ return Resolution::notFound();
+
+ AbstractModuleRecord* importedModule = hostResolveImportedModule(exec, importEntry.moduleRequest);
+ return importedModule->resolveExport(exec, importEntry.importName);
+}
+
+struct AbstractModuleRecord::ResolveQuery {
+ struct Hash {
+ static unsigned hash(const ResolveQuery&);
+ static bool equal(const ResolveQuery&, const ResolveQuery&);
+ static const bool safeToCompareToEmptyOrDeleted = true;
+ };
+
+ ResolveQuery(AbstractModuleRecord* moduleRecord, UniquedStringImpl* exportName)
+ : moduleRecord(moduleRecord)
+ , exportName(exportName)
+ {
+ }
+
+ ResolveQuery(AbstractModuleRecord* moduleRecord, const Identifier& exportName)
+ : ResolveQuery(moduleRecord, exportName.impl())
+ {
+ }
+
+ enum EmptyValueTag { EmptyValue };
+ ResolveQuery(EmptyValueTag)
+ {
+ }
+
+ enum DeletedValueTag { DeletedValue };
+ ResolveQuery(DeletedValueTag)
+ : moduleRecord(nullptr)
+ , exportName(WTF::HashTableDeletedValue)
+ {
+ }
+
+ bool isEmptyValue() const
+ {
+ return !exportName;
+ }
+
+ bool isDeletedValue() const
+ {
+ return exportName.isHashTableDeletedValue();
+ }
+
+ // The module record is not marked from the GC. But these records are reachable from the JSGlobalObject.
+ // So we don't care the reachability to this record.
+ AbstractModuleRecord* moduleRecord;
+ RefPtr<UniquedStringImpl> exportName;
+};
+
+inline unsigned AbstractModuleRecord::ResolveQuery::Hash::hash(const ResolveQuery& query)
+{
+ return WTF::PtrHash<AbstractModuleRecord*>::hash(query.moduleRecord) + IdentifierRepHash::hash(query.exportName);
+}
+
+inline bool AbstractModuleRecord::ResolveQuery::Hash::equal(const ResolveQuery& lhs, const ResolveQuery& rhs)
+{
+ return lhs.moduleRecord == rhs.moduleRecord && lhs.exportName == rhs.exportName;
+}
+
+auto AbstractModuleRecord::tryGetCachedResolution(UniquedStringImpl* exportName) -> std::optional<Resolution>
+{
+ const auto iterator = m_resolutionCache.find(exportName);
+ if (iterator == m_resolutionCache.end())
+ return std::nullopt;
+ return std::optional<Resolution>(iterator->value);
+}
+
+void AbstractModuleRecord::cacheResolution(UniquedStringImpl* exportName, const Resolution& resolution)
+{
+ m_resolutionCache.add(exportName, resolution);
+}
+
+auto AbstractModuleRecord::resolveExportImpl(ExecState* exec, const ResolveQuery& root) -> Resolution
+{
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-resolveexport
+
+ // How to avoid C++ recursion in this function:
+ // This function avoids C++ recursion of the naive ResolveExport implementation.
+ // Flatten the recursion to the loop with the task queue and frames.
+ //
+ // 1. pendingTasks
+ // We enqueue the recursive resolveExport call to this queue to avoid recursive calls in C++.
+ // The task has 3 types. (1) Query, (2) IndirectFallback and (3) GatherStars.
+ // (1) Query
+ // Querying the resolution to the current module.
+ // (2) IndirectFallback
+ // Examine the result of the indirect export resolution. Only when the indirect export resolution fails,
+ // we look into the star exports. (step 5-a-vi).
+ // (3) GatherStars
+ // Examine the result of the star export resolutions.
+ //
+ // 2. frames
+ // When the spec calls the resolveExport recursively, instead we append the frame
+ // (that holds the result resolution) to the frames and enqueue the task to the pendingTasks.
+ // The entry in the frames means the *local* resolution result of the specific recursive resolveExport.
+ //
+ // We should maintain the local resolution result instead of holding the global resolution result only.
+ // For example,
+ //
+ // star
+ // (1) ---> (2) "Resolve"
+ // |
+ // |
+ // +-> (3) "NotFound"
+ // |
+ // | star
+ // +-> (4) ---> (5) "Resolve" [here]
+ // |
+ // |
+ // +-> (6) "Error"
+ //
+ // Consider the above graph. The numbers represents the modules. Now we are [here].
+ // If we only hold the global resolution result during the resolveExport operation, [here],
+ // we decide the entire result of resolveExport is "Ambiguous", because there are multiple
+ // "Resolve" (in module (2) and (5)). However, this should become "Error" because (6) will
+ // propagate "Error" state to the (4), (4) will become "Error" and then, (1) will become
+ // "Error". We should aggregate the results at the star exports point ((4) and (1)).
+ //
+ // Usually, both "Error" and "Ambiguous" states will throw the syntax error. So except for the content of the
+ // error message, there are no difference. (And if we fix the (6) that raises "Error", next, it will produce
+ // the "Ambiguous" error due to (5). Anyway, user need to fix the both. So which error should be raised at first
+ // doesn't matter so much.
+ //
+ // However, this may become the problem under the module namespace creation.
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-getmodulenamespace
+ // section 15.2.1.18, step 3-d-ii
+ // Here, we distinguish "Ambiguous" and "Error". When "Error" state is produced, we need to throw the propagated error.
+ // But if "Ambiguous" state comes, we just ignore the result.
+ // To follow the requirement strictly, in this implementation, we keep the local resolution result to produce the
+ // correct result under the above complex cases.
+
+ // Caching strategy:
+ // The resolveExport operation is frequently called. So caching results is important.
+ // We observe the following aspects and based on them construct the caching strategy.
+ // Here, we attempt to cache the resolution by constructing the map in module records.
+ // That means Module -> ExportName -> Maybe<Resolution>.
+ // Technically, all the AbstractModuleRecords have the Map<ExportName, Resolution> for caching.
+ //
+ // The important observations are that,
+ //
+ // - *cacheable* means that traversing to this node from a path will produce the same results as starting from this node.
+ //
+ // Here, we define the resovling route. We represent [?] as the module that has the local binding.
+ // And (?) as the module without the local binding.
+ //
+ // @ -> (A) -> (B) -> [C]
+ //
+ // We list the resolving route for each node.
+ //
+ // (A): (A) -> (B) -> [C]
+ // (B): (B) -> [C]
+ // [C]: [C]
+ //
+ // In this case, if we start the tracing from (B), the resolving route becomes (B) -> [C].
+ // So this is the same. At that time, we can say (B) is cacheable in the first tracing.
+ //
+ // - The cache ability of a node depends on the resolving route from this node.
+ //
+ // 1. The starting point is always cacheable.
+ //
+ // 2. A module that has resolved a local binding is always cacheable.
+ //
+ // @ -> (A) -> [B]
+ //
+ // In the above case, we can see the [B] as cacheable.
+ // This is because when starting from [B] node, we immediately resolve with the local binding.
+ // So the resolving route from [B] does not depend on the starting point.
+ //
+ // 3. If we don't follow any star links during the resolution, we can see all the traced nodes are cacheable.
+ //
+ // If there are non star links, it means that there is *no branch* in the module dependency graph.
+ // This *no branch* feature makes all the modules cachable.
+ //
+ // I.e, if we traverse one star link (even if we successfully resolve that star link),
+ // we must still traverse all other star links. I would also explain we don't run into
+ // this when resolving a local/indirect link. When resolving a local/indirect link,
+ // we won't traverse any star links.
+ // And since the module can hold only one local/indirect link for the specific export name (if there
+ // are multiple local/indirect links that has the same export name, it should be syntax error in the
+ // parsing phase.), there is no multiple outgoing links from a module.
+ //
+ // @ -> (A) --> (B) -> [C] -> (D) -> (E) -+
+ // ^ |
+ // | |
+ // +------------------------+
+ //
+ // When starting from @, [C] will be found as the module resolving the given binding.
+ // In this case, (B) can cache this resolution. Since the resolving route is the same to the one when
+ // starting from (B). After caching the above result, we attempt to resolve the same binding from (D).
+ //
+ // @
+ // |
+ // v
+ // @ -> (A) --> (B) -> [C] -> (D) -> (E) -+
+ // ^ |
+ // | |
+ // +------------------------+
+ //
+ // In this case, we can use the (B)'s cached result. And (E) can be cached.
+ //
+ // (E): The resolving route is now (E) -> (B) -> [C]. That is the same when starting from (E).
+ //
+ // No branching makes that the problematic *once-visited* node cannot be seen.
+ // The *once-visited* node makes the resolving route changed since when we see the *once-visited* node,
+ // we stop tracing this.
+ //
+ // If there is no star links and if we look *once-visited* node under no branching graph, *once-visited*
+ // node cannot resolve the requested binding. If the *once-visited* node can resolve the binding, we
+ // should have already finished the resolution before reaching this *once-visited* node.
+ //
+ // 4. Once we follow star links, we should not retrieve the result from the cache and should not cache.
+ //
+ // Star links are only the way to introduce branch.
+ // Once we follow the star links during the resolution, we cannot cache naively.
+ // This is because the cacheability depends on the resolving route. And branching produces the problematic *once-visited*
+ // nodes. Since we don't follow the *once-visited* node, the resolving route from the node becomes different from
+ // the resolving route when starting from this node.
+ //
+ // The following example explains when we should not retrieve the cache and cache the result.
+ //
+ // +----> (D) ------+
+ // | |
+ // | v
+ // (A) *----+----> (B) ---> [C]
+ // ^
+ // |
+ // @
+ //
+ // When starting from (B), we find [C]. In this resolving route, we don't find any star link.
+ // And by definition, (B) and [C] are cachable. (B) is the starting point. And [C] has the local binding.
+ //
+ // +----> (D) ------+
+ // | |
+ // | v
+ // @-> (A) *----+----> (B) ---> [C]
+ //
+ // But when starting from (A), we should not get the value from the cache. Because,
+ //
+ // 1. When looking (D), we reach [C] and make both resolved.
+ // 2. When looking (B), if we retrieved the last cache from (B), (B) becomes resolved.
+ // 3. But actually, (B) is not-found in this trial because (C) is already *once-visited*.
+ // 4. If we accidentally make (B) resolved, (A) becomes ambiguous. But the correct answer is resolved.
+ //
+ // Why is this problem caused? This is because the *once-visited* node makes the result not-found.
+ // In the second trial, (B) -> [C] result is changed from resolved to not-found.
+ //
+ // When does this become a problem? If the status of the *once-visited* node group is resolved,
+ // changing the result to not-found makes the result changed.
+ //
+ // This problem does not happen when we don't see any star link yet. Now, consider the minimum case.
+ //
+ // @-> (A) -> [ some graph ]
+ // ^ |
+ // | |
+ // +------------+
+ //
+ // In (A), we don't see any star link yet. So we can say that all the visited nodes does not have any local
+ // resolution. Because if they had a local/indirect resolution, we should have already finished the tracing.
+ //
+ // And even if the some graph will see the *once-visited* node (in this case, (A)), that does not affect the
+ // result of the resolution. Because even if we follow the link to (A) or not follow the link to (A), the status
+ // of the link is always not-found since (A) does not have any local resolution.
+ // In the above case, we can use the result of the [some graph].
+ //
+ // 5. Once we see star links, even if we have not yet traversed that star link path, we should disable caching.
+ //
+ // Here is the reason why:
+ //
+ // +-------------+
+ // | |
+ // v |
+ // (A) -> (B) -> (C) *-> [E]
+ // * ^
+ // | |
+ // v @
+ // [D]
+ //
+ // In the above case, (C) will be resolved with [D].
+ // (C) will see (A) and (A) gives up in (A) -> (B) -> (C) route. So, (A) will fallback to [D].
+ //
+ // +-------------+
+ // | |
+ // v |
+ // @-> (A) -> (B) -> (C) *-> [E]
+ // *
+ // |
+ // v
+ // [D]
+ //
+ // But in this case, (A) will be resolved with [E] (not [D]).
+ // (C) will attempt to follow the link to (A), but it fails.
+ // So (C) will fallback to the star link and found [E]. In this senario,
+ // (C) is now resolved with [E]'s result.
+ //
+ // The cause of this problem is also the same to 4.
+ // In the latter case, when looking (C), we cannot use the cached result in (C).
+ // Because the cached result of (C) depends on the *once-visited* node (A) and
+ // (A) has the fallback system with the star link.
+ // In the latter trial, we now assume that (A)'s status is not-found.
+ // But, actually, in the former trial, (A)'s status becomes resolved due to the fallback to the [D].
+ //
+ // To summarize the observations.
+ //
+ // 1. The starting point is always cacheable.
+ // 2. A module that has resolved a local binding is always cacheable.
+ // 3. If we don't follow any star links during the resolution, we can see all the traced nodes are cacheable.
+ // 4. Once we follow star links, we should not retrieve the result from the cache and should not cache the result.
+ // 5. Once we see star links, even if we have not yet traversed that star link path, we should disable caching.
+
+ typedef WTF::HashSet<ResolveQuery, ResolveQuery::Hash, WTF::CustomHashTraits<ResolveQuery>> ResolveSet;
+ enum class Type { Query, IndirectFallback, GatherStars };
+ struct Task {
+ ResolveQuery query;
+ Type type;
+ };
+
+ Vector<Task, 8> pendingTasks;
+ ResolveSet resolveSet;
+ HashSet<AbstractModuleRecord*> starSet;
+
+ Vector<Resolution, 8> frames;
+
+ bool foundStarLinks = false;
+
+ frames.append(Resolution::notFound());
+
+ // Call when the query is not resolved in the current module.
+ // It will enqueue the star resolution requests. Return "false" if the error occurs.
+ auto resolveNonLocal = [&](const ResolveQuery& query) -> bool {
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-resolveexport
+ // section 15.2.1.16.3, step 6
+ // If the "default" name is not resolved in the current module, we need to throw an error and stop resolution immediately,
+ // Rationale to this error: A default export cannot be provided by an export *.
+ if (query.exportName == exec->propertyNames().defaultKeyword.impl())
+ return false;
+
+ // step 7, If exportStarSet contains module, then return null.
+ if (!starSet.add(query.moduleRecord).isNewEntry)
+ return true;
+
+ // Enqueue the task to gather the results of the stars.
+ // And append the new Resolution frame to gather the local result of the stars.
+ pendingTasks.append(Task { query, Type::GatherStars });
+ foundStarLinks = true;
+ frames.append(Resolution::notFound());
+
+
+ // Enqueue the tasks in reverse order.
+ for (auto iterator = query.moduleRecord->starExportEntries().rbegin(), end = query.moduleRecord->starExportEntries().rend(); iterator != end; ++iterator) {
+ const RefPtr<UniquedStringImpl>& starModuleName = *iterator;
+ AbstractModuleRecord* importedModuleRecord = query.moduleRecord->hostResolveImportedModule(exec, Identifier::fromUid(exec, starModuleName.get()));
+ pendingTasks.append(Task { ResolveQuery(importedModuleRecord, query.exportName.get()), Type::Query });
+ }
+ return true;
+ };
+
+ // Return the current resolution value of the top frame.
+ auto currentTop = [&] () -> Resolution& {
+ ASSERT(!frames.isEmpty());
+ return frames.last();
+ };
+
+ // Merge the given resolution to the current resolution value of the top frame.
+ // If there is ambiguity, return "false". When the "false" is returned, we should make the result "ambiguous".
+ auto mergeToCurrentTop = [&] (const Resolution& resolution) -> bool {
+ if (resolution.type == Resolution::Type::NotFound)
+ return true;
+
+ if (currentTop().type == Resolution::Type::NotFound) {
+ currentTop() = resolution;
+ return true;
+ }
+
+ if (currentTop().moduleRecord != resolution.moduleRecord || currentTop().localName != resolution.localName)
+ return false;
+
+ return true;
+ };
+
+ auto cacheResolutionForQuery = [] (const ResolveQuery& query, const Resolution& resolution) {
+ ASSERT(resolution.type == Resolution::Type::Resolved);
+ query.moduleRecord->cacheResolution(query.exportName.get(), resolution);
+ };
+
+ pendingTasks.append(Task { root, Type::Query });
+ while (!pendingTasks.isEmpty()) {
+ const Task task = pendingTasks.takeLast();
+ const ResolveQuery& query = task.query;
+
+ switch (task.type) {
+ case Type::Query: {
+ AbstractModuleRecord* moduleRecord = query.moduleRecord;
+
+ if (!resolveSet.add(task.query).isNewEntry)
+ continue;
+
+ // 5. Once we see star links, even if we have not yet traversed that star link path, we should disable caching.
+ if (!moduleRecord->starExportEntries().isEmpty())
+ foundStarLinks = true;
+
+ // 4. Once we follow star links, we should not retrieve the result from the cache and should not cache the result.
+ if (!foundStarLinks) {
+ if (std::optional<Resolution> cachedResolution = moduleRecord->tryGetCachedResolution(query.exportName.get())) {
+ if (!mergeToCurrentTop(*cachedResolution))
+ return Resolution::ambiguous();
+ continue;
+ }
+ }
+
+ const std::optional<ExportEntry> optionalExportEntry = moduleRecord->tryGetExportEntry(query.exportName.get());
+ if (!optionalExportEntry) {
+ // If there is no matched exported binding in the current module,
+ // we need to look into the stars.
+ if (!resolveNonLocal(task.query))
+ return Resolution::error();
+ continue;
+ }
+
+ const ExportEntry& exportEntry = *optionalExportEntry;
+ switch (exportEntry.type) {
+ case ExportEntry::Type::Local: {
+ ASSERT(!exportEntry.localName.isNull());
+ Resolution resolution { Resolution::Type::Resolved, moduleRecord, exportEntry.localName };
+ // 2. A module that has resolved a local binding is always cacheable.
+ cacheResolutionForQuery(query, resolution);
+ if (!mergeToCurrentTop(resolution))
+ return Resolution::ambiguous();
+ continue;
+ }
+
+ case ExportEntry::Type::Indirect: {
+ AbstractModuleRecord* importedModuleRecord = moduleRecord->hostResolveImportedModule(exec, exportEntry.moduleName);
+
+ // When the imported module does not produce any resolved binding, we need to look into the stars in the *current*
+ // module. To do this, we append the `IndirectFallback` task to the task queue.
+ pendingTasks.append(Task { query, Type::IndirectFallback });
+ // And append the new Resolution frame to check the indirect export will be resolved or not.
+ frames.append(Resolution::notFound());
+ pendingTasks.append(Task { ResolveQuery(importedModuleRecord, exportEntry.importName), Type::Query });
+ continue;
+ }
+ }
+ break;
+ }
+
+ case Type::IndirectFallback: {
+ Resolution resolution = frames.takeLast();
+
+ if (resolution.type == Resolution::Type::NotFound) {
+ // Indirect export entry does not produce any resolved binding.
+ // So we will investigate the stars.
+ if (!resolveNonLocal(task.query))
+ return Resolution::error();
+ continue;
+ }
+
+ ASSERT_WITH_MESSAGE(resolution.type == Resolution::Type::Resolved, "When we see Error and Ambiguous, we immediately return from this loop. So here, only Resolved comes.");
+
+ // 3. If we don't follow any star links during the resolution, we can see all the traced nodes are cacheable.
+ // 4. Once we follow star links, we should not retrieve the result from the cache and should not cache the result.
+ if (!foundStarLinks)
+ cacheResolutionForQuery(query, resolution);
+
+ // If indirect export entry produces Resolved, we should merge it to the upper frame.
+ // And do not investigate the stars of the current module.
+ if (!mergeToCurrentTop(resolution))
+ return Resolution::ambiguous();
+ break;
+ }
+
+ case Type::GatherStars: {
+ Resolution resolution = frames.takeLast();
+ ASSERT_WITH_MESSAGE(resolution.type == Resolution::Type::Resolved || resolution.type == Resolution::Type::NotFound, "When we see Error and Ambiguous, we immediately return from this loop. So here, only Resolved and NotFound comes.");
+
+ // Merge the star resolution to the upper frame.
+ if (!mergeToCurrentTop(resolution))
+ return Resolution::ambiguous();
+ break;
+ }
+ }
+ }
+
+ ASSERT(frames.size() == 1);
+ // 1. The starting point is always cacheable.
+ if (frames[0].type == Resolution::Type::Resolved)
+ cacheResolutionForQuery(root, frames[0]);
+ return frames[0];
+}
+
+auto AbstractModuleRecord::resolveExport(ExecState* exec, const Identifier& exportName) -> Resolution
+{
+ // Look up the cached resolution first before entering the resolving loop, since the loop setup takes some cost.
+ if (std::optional<Resolution> cachedResolution = tryGetCachedResolution(exportName.impl()))
+ return *cachedResolution;
+ return resolveExportImpl(exec, ResolveQuery(this, exportName.impl()));
+}
+
+static void getExportedNames(ExecState* exec, AbstractModuleRecord* root, IdentifierSet& exportedNames)
+{
+ HashSet<AbstractModuleRecord*> exportStarSet;
+ Vector<AbstractModuleRecord*, 8> pendingModules;
+
+ pendingModules.append(root);
+
+ while (!pendingModules.isEmpty()) {
+ AbstractModuleRecord* moduleRecord = pendingModules.takeLast();
+ if (exportStarSet.contains(moduleRecord))
+ continue;
+ exportStarSet.add(moduleRecord);
+
+ for (const auto& pair : moduleRecord->exportEntries()) {
+ const AbstractModuleRecord::ExportEntry& exportEntry = pair.value;
+ if (moduleRecord == root || exec->propertyNames().defaultKeyword != exportEntry.exportName)
+ exportedNames.add(exportEntry.exportName.impl());
+ }
+
+ for (const auto& starModuleName : moduleRecord->starExportEntries()) {
+ AbstractModuleRecord* requestedModuleRecord = moduleRecord->hostResolveImportedModule(exec, Identifier::fromUid(exec, starModuleName.get()));
+ pendingModules.append(requestedModuleRecord);
+ }
+ }
+}
+
+JSModuleNamespaceObject* AbstractModuleRecord::getModuleNamespace(ExecState* exec)
+{
+ VM& vm = exec->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-getmodulenamespace
+ if (m_moduleNamespaceObject)
+ return m_moduleNamespaceObject.get();
+
+ JSGlobalObject* globalObject = exec->lexicalGlobalObject();
+ IdentifierSet exportedNames;
+ getExportedNames(exec, this, exportedNames);
+
+ Vector<std::pair<Identifier, Resolution>> resolutions;
+ for (auto& name : exportedNames) {
+ Identifier ident = Identifier::fromUid(exec, name.get());
+ const Resolution resolution = resolveExport(exec, ident);
+ switch (resolution.type) {
+ case Resolution::Type::NotFound:
+ throwSyntaxError(exec, scope, makeString("Exported binding name '", String(name.get()), "' is not found."));
+ return nullptr;
+
+ case Resolution::Type::Error:
+ throwSyntaxError(exec, scope, makeString("Exported binding name 'default' cannot be resolved by star export entries."));
+ return nullptr;
+
+ case Resolution::Type::Ambiguous:
+ break;
+
+ case Resolution::Type::Resolved:
+ resolutions.append({ WTFMove(ident), resolution });
+ break;
+ }
+ }
+
+ m_moduleNamespaceObject.set(vm, this, JSModuleNamespaceObject::create(exec, globalObject, globalObject->moduleNamespaceObjectStructure(), this, WTFMove(resolutions)));
+ return m_moduleNamespaceObject.get();
+}
+
+static String printableName(const RefPtr<UniquedStringImpl>& uid)
+{
+ if (uid->isSymbol())
+ return uid.get();
+ return WTF::makeString("'", String(uid.get()), "'");
+}
+
+static String printableName(const Identifier& ident)
+{
+ return printableName(ident.impl());
+}
+
+void AbstractModuleRecord::dump()
+{
+ dataLog("\nAnalyzing ModuleRecord key(", printableName(m_moduleKey), ")\n");
+
+ dataLog(" Dependencies: ", m_requestedModules.size(), " modules\n");
+ for (const auto& moduleName : m_requestedModules)
+ dataLog(" module(", printableName(moduleName), ")\n");
+
+ dataLog(" Import: ", m_importEntries.size(), " entries\n");
+ for (const auto& pair : m_importEntries) {
+ const ImportEntry& importEntry = pair.value;
+ dataLog(" import(", printableName(importEntry.importName), "), local(", printableName(importEntry.localName), "), module(", printableName(importEntry.moduleRequest), ")\n");
+ }
+
+ dataLog(" Export: ", m_exportEntries.size(), " entries\n");
+ for (const auto& pair : m_exportEntries) {
+ const ExportEntry& exportEntry = pair.value;
+ switch (exportEntry.type) {
+ case ExportEntry::Type::Local:
+ dataLog(" [Local] ", "export(", printableName(exportEntry.exportName), "), local(", printableName(exportEntry.localName), ")\n");
+ break;
+
+ case ExportEntry::Type::Indirect:
+ dataLog(" [Indirect] ", "export(", printableName(exportEntry.exportName), "), import(", printableName(exportEntry.importName), "), module(", printableName(exportEntry.moduleName), ")\n");
+ break;
+ }
+ }
+ for (const auto& moduleName : m_starExportEntries)
+ dataLog(" [Star] module(", printableName(moduleName.get()), ")\n");
+}
+
+} // namespace JSC