[dsymutil][DWARFLinker][NFC] Refactor usages of UniquingStringPool.

That refactoring is helpful since it reduces data inter-dependencies.
Which is good for current implementation and even more good for
fully multi-thread implementation. The idea of the refactoring
is to delete UniquingStringPool from the global DWARFLinker level.
It is used to unique type names while ODR deduplication is done.
Thus we move UniquingStringPool into the DeclContextTree which
matched to UniquingStringPool usage scope.

golden-dsymutil/dsymutil 23787992
clang MD5: 7d9873ff94f0246b6ab1ec3e8d0f3f06

build-Release/bin/dsymutil 23921272
clang MD5: 7d9873ff94f0246b6ab1ec3e8d0f3f06

Differential Revision: https://reviews.llvm.org/D93460
This commit is contained in:
Alexey Lapshin 2020-12-16 16:34:12 +03:00
parent fe9976c02c
commit f5f7ff8d0f
5 changed files with 88 additions and 103 deletions

View File

@ -467,7 +467,6 @@ private:
bool registerModuleReference(DWARFDie CUDie, const DWARFUnit &Unit, bool registerModuleReference(DWARFDie CUDie, const DWARFUnit &Unit,
const DWARFFile &File, const DWARFFile &File,
OffsetsStringPool &OffsetsStringPool, OffsetsStringPool &OffsetsStringPool,
UniquingStringPool &UniquingStringPoolStringPool,
DeclContextTree &ODRContexts, DeclContextTree &ODRContexts,
uint64_t ModulesEndOffset, unsigned &UnitID, uint64_t ModulesEndOffset, unsigned &UnitID,
bool IsLittleEndian, unsigned Indent = 0, bool IsLittleEndian, unsigned Indent = 0,
@ -480,7 +479,6 @@ private:
StringRef ModuleName, uint64_t DwoId, StringRef ModuleName, uint64_t DwoId,
const DWARFFile &File, const DWARFFile &File,
OffsetsStringPool &OffsetsStringPool, OffsetsStringPool &OffsetsStringPool,
UniquingStringPool &UniquingStringPool,
DeclContextTree &ODRContexts, uint64_t ModulesEndOffset, DeclContextTree &ODRContexts, uint64_t ModulesEndOffset,
unsigned &UnitID, bool IsLittleEndian, unsigned &UnitID, bool IsLittleEndian,
unsigned Indent = 0, bool Quiet = false); unsigned Indent = 0, bool Quiet = false);

View File

@ -237,21 +237,6 @@ public:
const std::vector<AccelInfo> &getNamespaces() const { return Namespaces; } const std::vector<AccelInfo> &getNamespaces() const { return Namespaces; }
const std::vector<AccelInfo> &getObjC() const { return ObjC; } const std::vector<AccelInfo> &getObjC() const { return ObjC; }
/// Get the full path for file \a FileNum in the line table
StringRef getResolvedPath(unsigned FileNum) {
if (FileNum >= ResolvedPaths.size())
return StringRef();
return ResolvedPaths[FileNum];
}
/// Set the fully resolved path for the line-table's file \a FileNum
/// to \a Path.
void setResolvedPath(unsigned FileNum, StringRef Path) {
if (ResolvedPaths.size() <= FileNum)
ResolvedPaths.resize(FileNum + 1);
ResolvedPaths[FileNum] = Path;
}
MCSymbol *getLabelBegin() { return LabelBegin; } MCSymbol *getLabelBegin() { return LabelBegin; }
void setLabelBegin(MCSymbol *S) { LabelBegin = S; } void setLabelBegin(MCSymbol *S) { LabelBegin = S; }
@ -310,12 +295,6 @@ private:
std::vector<AccelInfo> ObjC; std::vector<AccelInfo> ObjC;
/// @} /// @}
/// Cached resolved paths from the line table.
/// Note, the StringRefs here point in to the intern (uniquing) string pool.
/// This means that a StringRef returned here doesn't need to then be uniqued
/// for the purposes of getting a unique address for each string.
std::vector<StringRef> ResolvedPaths;
/// Is this unit subject to the ODR rule? /// Is this unit subject to the ODR rule?
bool HasODR; bool HasODR;

View File

@ -15,6 +15,7 @@
#include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringRef.h"
#include "llvm/CodeGen/NonRelocatableStringpool.h" #include "llvm/CodeGen/NonRelocatableStringpool.h"
#include "llvm/DWARFLinker/DWARFLinkerCompileUnit.h" #include "llvm/DWARFLinker/DWARFLinkerCompileUnit.h"
#include "llvm/DebugInfo/DWARF/DWARFDebugLine.h"
#include "llvm/DebugInfo/DWARF/DWARFDie.h" #include "llvm/DebugInfo/DWARF/DWARFDie.h"
#include "llvm/Support/FileSystem.h" #include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h" #include "llvm/Support/Path.h"
@ -31,16 +32,18 @@ class CachedPathResolver {
public: public:
/// Resolve a path by calling realpath and cache its result. The returned /// Resolve a path by calling realpath and cache its result. The returned
/// StringRef is interned in the given \p StringPool. /// StringRef is interned in the given \p StringPool.
StringRef resolve(std::string Path, NonRelocatableStringpool &StringPool) { StringRef resolve(const std::string &Path,
NonRelocatableStringpool &StringPool) {
StringRef FileName = sys::path::filename(Path); StringRef FileName = sys::path::filename(Path);
SmallString<256> ParentPath = sys::path::parent_path(Path); StringRef ParentPath = sys::path::parent_path(Path);
// If the ParentPath has not yet been resolved, resolve and cache it for // If the ParentPath has not yet been resolved, resolve and cache it for
// future look-ups. // future look-ups.
if (!ResolvedPaths.count(ParentPath)) { if (!ResolvedPaths.count(ParentPath)) {
SmallString<256> RealPath; SmallString<256> RealPath;
sys::fs::real_path(ParentPath, RealPath); sys::fs::real_path(ParentPath, RealPath);
ResolvedPaths.insert({ParentPath, StringRef(RealPath).str()}); ResolvedPaths.insert(
{ParentPath, std::string(RealPath.c_str(), RealPath.size())});
} }
// Join the file name again with the resolved path. // Join the file name again with the resolved path.
@ -95,7 +98,6 @@ public:
void setDefinedInClangModule(bool Val) { DefinedInClangModule = Val; } void setDefinedInClangModule(bool Val) { DefinedInClangModule = Val; }
uint16_t getTag() const { return Tag; } uint16_t getTag() const { return Tag; }
StringRef getName() const { return Name; }
private: private:
friend DeclMapInfo; friend DeclMapInfo;
@ -129,10 +131,10 @@ public:
/// ///
/// FIXME: The invalid bit along the return value is to emulate some /// FIXME: The invalid bit along the return value is to emulate some
/// dsymutil-classic functionality. /// dsymutil-classic functionality.
PointerIntPair<DeclContext *, 1> PointerIntPair<DeclContext *, 1> getChildDeclContext(DeclContext &Context,
getChildDeclContext(DeclContext &Context, const DWARFDie &DIE, const DWARFDie &DIE,
CompileUnit &Unit, UniquingStringPool &StringPool, CompileUnit &Unit,
bool InClangModule); bool InClangModule);
DeclContext &getRoot() { return Root; } DeclContext &getRoot() { return Root; }
@ -141,8 +143,19 @@ private:
DeclContext Root; DeclContext Root;
DeclContext::Map Contexts; DeclContext::Map Contexts;
/// Cache resolved paths from the line table. /// Cached resolved paths from the line table.
/// The key is <UniqueUnitID, FileIdx>.
using ResolvedPathsMap = DenseMap<std::pair<unsigned, unsigned>, StringRef>;
ResolvedPathsMap ResolvedPaths;
/// Helper that resolves and caches fragments of file paths.
CachedPathResolver PathResolver; CachedPathResolver PathResolver;
/// String pool keeping real path bodies.
NonRelocatableStringpool StringPool;
StringRef getResolvedPath(CompileUnit &CU, unsigned FileNum,
const DWARFDebugLine::LineTable &LineTable);
}; };
/// Info type for the DenseMap storing the DeclContext pointers. /// Info type for the DenseMap storing the DeclContext pointers.

View File

@ -313,9 +313,8 @@ static void updateChildPruning(const DWARFDie &Die, CompileUnit &CU,
/// (i.e., forward declarations that are children of a DW_TAG_module). /// (i.e., forward declarations that are children of a DW_TAG_module).
static bool analyzeContextInfo( static bool analyzeContextInfo(
const DWARFDie &DIE, unsigned ParentIdx, CompileUnit &CU, const DWARFDie &DIE, unsigned ParentIdx, CompileUnit &CU,
DeclContext *CurrentDeclContext, UniquingStringPool &StringPool, DeclContext *CurrentDeclContext, DeclContextTree &Contexts,
DeclContextTree &Contexts, uint64_t ModulesEndOffset, uint64_t ModulesEndOffset, swiftInterfacesMap *ParseableSwiftInterfaces,
swiftInterfacesMap *ParseableSwiftInterfaces,
std::function<void(const Twine &, const DWARFDie &)> ReportWarning, std::function<void(const Twine &, const DWARFDie &)> ReportWarning,
bool InImportedModule = false) { bool InImportedModule = false) {
// LIFO work list. // LIFO work list.
@ -366,7 +365,7 @@ static bool analyzeContextInfo(
if (CU.hasODR() || InClangModule) { if (CU.hasODR() || InClangModule) {
if (Current.Context) { if (Current.Context) {
auto PtrInvalidPair = Contexts.getChildDeclContext( auto PtrInvalidPair = Contexts.getChildDeclContext(
*Current.Context, Current.Die, CU, StringPool, InClangModule); *Current.Context, Current.Die, CU, InClangModule);
Current.Context = PtrInvalidPair.getPointer(); Current.Context = PtrInvalidPair.getPointer();
Info.Ctxt = Info.Ctxt =
PtrInvalidPair.getInt() ? nullptr : PtrInvalidPair.getPointer(); PtrInvalidPair.getInt() ? nullptr : PtrInvalidPair.getPointer();
@ -1977,11 +1976,13 @@ static std::string remapPath(StringRef Path,
return p.str().str(); return p.str().str();
} }
bool DWARFLinker::registerModuleReference( bool DWARFLinker::registerModuleReference(DWARFDie CUDie, const DWARFUnit &Unit,
DWARFDie CUDie, const DWARFUnit &Unit, const DWARFFile &File, const DWARFFile &File,
OffsetsStringPool &StringPool, UniquingStringPool &UniquingStringPool, OffsetsStringPool &StringPool,
DeclContextTree &ODRContexts, uint64_t ModulesEndOffset, unsigned &UnitID, DeclContextTree &ODRContexts,
bool IsLittleEndian, unsigned Indent, bool Quiet) { uint64_t ModulesEndOffset,
unsigned &UnitID, bool IsLittleEndian,
unsigned Indent, bool Quiet) {
std::string PCMfile = dwarf::toString( std::string PCMfile = dwarf::toString(
CUDie.find({dwarf::DW_AT_dwo_name, dwarf::DW_AT_GNU_dwo_name}), ""); CUDie.find({dwarf::DW_AT_dwo_name, dwarf::DW_AT_GNU_dwo_name}), "");
if (PCMfile.empty()) if (PCMfile.empty())
@ -2025,10 +2026,9 @@ bool DWARFLinker::registerModuleReference(
// shouldn't run into an infinite loop, so mark it as processed now. // shouldn't run into an infinite loop, so mark it as processed now.
ClangModules.insert({PCMfile, DwoId}); ClangModules.insert({PCMfile, DwoId});
if (Error E = if (Error E = loadClangModule(CUDie, PCMfile, Name, DwoId, File, StringPool,
loadClangModule(CUDie, PCMfile, Name, DwoId, File, StringPool, ODRContexts, ModulesEndOffset, UnitID,
UniquingStringPool, ODRContexts, ModulesEndOffset, IsLittleEndian, Indent + 2, Quiet)) {
UnitID, IsLittleEndian, Indent + 2, Quiet)) {
consumeError(std::move(E)); consumeError(std::move(E));
return false; return false;
} }
@ -2038,9 +2038,8 @@ bool DWARFLinker::registerModuleReference(
Error DWARFLinker::loadClangModule( Error DWARFLinker::loadClangModule(
DWARFDie CUDie, StringRef Filename, StringRef ModuleName, uint64_t DwoId, DWARFDie CUDie, StringRef Filename, StringRef ModuleName, uint64_t DwoId,
const DWARFFile &File, OffsetsStringPool &StringPool, const DWARFFile &File, OffsetsStringPool &StringPool,
UniquingStringPool &UniquingStringPool, DeclContextTree &ODRContexts, DeclContextTree &ODRContexts, uint64_t ModulesEndOffset, unsigned &UnitID,
uint64_t ModulesEndOffset, unsigned &UnitID, bool IsLittleEndian, bool IsLittleEndian, unsigned Indent, bool Quiet) {
unsigned Indent, bool Quiet) {
/// Using a SmallString<0> because loadClangModule() is recursive. /// Using a SmallString<0> because loadClangModule() is recursive.
SmallString<0> Path(Options.PrependPath); SmallString<0> Path(Options.PrependPath);
if (sys::path::is_relative(Filename)) if (sys::path::is_relative(Filename))
@ -2064,9 +2063,9 @@ Error DWARFLinker::loadClangModule(
auto CUDie = CU->getUnitDIE(false); auto CUDie = CU->getUnitDIE(false);
if (!CUDie) if (!CUDie)
continue; continue;
if (!registerModuleReference( if (!registerModuleReference(CUDie, *CU, File, StringPool, ODRContexts,
CUDie, *CU, File, StringPool, UniquingStringPool, ODRContexts, ModulesEndOffset, UnitID, IsLittleEndian,
ModulesEndOffset, UnitID, IsLittleEndian, Indent, Quiet)) { Indent, Quiet)) {
if (Unit) { if (Unit) {
std::string Err = std::string Err =
(Filename + (Filename +
@ -2094,9 +2093,8 @@ Error DWARFLinker::loadClangModule(
Unit = std::make_unique<CompileUnit>(*CU, UnitID++, !Options.NoODR, Unit = std::make_unique<CompileUnit>(*CU, UnitID++, !Options.NoODR,
ModuleName); ModuleName);
Unit->setHasInterestingContent(); Unit->setHasInterestingContent();
analyzeContextInfo(CUDie, 0, *Unit, &ODRContexts.getRoot(), analyzeContextInfo(CUDie, 0, *Unit, &ODRContexts.getRoot(), ODRContexts,
UniquingStringPool, ODRContexts, ModulesEndOffset, ModulesEndOffset, Options.ParseableSwiftInterfaces,
Options.ParseableSwiftInterfaces,
[&](const Twine &Warning, const DWARFDie &DIE) { [&](const Twine &Warning, const DWARFDie &DIE) {
reportWarning(Warning, File, &DIE); reportWarning(Warning, File, &DIE);
}); });
@ -2310,10 +2308,6 @@ bool DWARFLinker::link() {
// parallel loop. // parallel loop.
unsigned NumObjects = ObjectContexts.size(); unsigned NumObjects = ObjectContexts.size();
// This Dwarf string pool which is only used for uniquing. This one should
// never be used for offsets as its not thread-safe or predictable.
UniquingStringPool UniquingStringPool(nullptr, true);
// This Dwarf string pool which is used for emission. It must be used // This Dwarf string pool which is used for emission. It must be used
// serially as the order of calling getStringOffset matters for // serially as the order of calling getStringOffset matters for
// reproducibility. // reproducibility.
@ -2383,7 +2377,7 @@ bool DWARFLinker::link() {
} }
if (CUDie && !LLVM_UNLIKELY(Options.Update)) if (CUDie && !LLVM_UNLIKELY(Options.Update))
registerModuleReference(CUDie, *CU, OptContext.File, OffsetsStringPool, registerModuleReference(CUDie, *CU, OptContext.File, OffsetsStringPool,
UniquingStringPool, ODRContexts, 0, UnitID, ODRContexts, 0, UnitID,
OptContext.File.Dwarf->isLittleEndian()); OptContext.File.Dwarf->isLittleEndian());
} }
} }
@ -2425,8 +2419,8 @@ bool DWARFLinker::link() {
auto CUDie = CU->getUnitDIE(false); auto CUDie = CU->getUnitDIE(false);
if (!CUDie || LLVM_UNLIKELY(Options.Update) || if (!CUDie || LLVM_UNLIKELY(Options.Update) ||
!registerModuleReference(CUDie, *CU, Context.File, OffsetsStringPool, !registerModuleReference(CUDie, *CU, Context.File, OffsetsStringPool,
UniquingStringPool, ODRContexts, ODRContexts, ModulesEndOffset, UnitID,
ModulesEndOffset, UnitID, Quiet)) { Quiet)) {
Context.CompileUnits.push_back(std::make_unique<CompileUnit>( Context.CompileUnits.push_back(std::make_unique<CompileUnit>(
*CU, UnitID++, !Options.NoODR && !Options.Update, "")); *CU, UnitID++, !Options.NoODR && !Options.Update, ""));
} }
@ -2438,9 +2432,8 @@ bool DWARFLinker::link() {
if (!CUDie) if (!CUDie)
continue; continue;
analyzeContextInfo(CurrentUnit->getOrigUnit().getUnitDIE(), 0, analyzeContextInfo(CurrentUnit->getOrigUnit().getUnitDIE(), 0,
*CurrentUnit, &ODRContexts.getRoot(), *CurrentUnit, &ODRContexts.getRoot(), ODRContexts,
UniquingStringPool, ODRContexts, ModulesEndOffset, ModulesEndOffset, Options.ParseableSwiftInterfaces,
Options.ParseableSwiftInterfaces,
[&](const Twine &Warning, const DWARFDie &DIE) { [&](const Twine &Warning, const DWARFDie &DIE) {
reportWarning(Warning, Context.File, &DIE); reportWarning(Warning, Context.File, &DIE);
}); });

View File

@ -40,9 +40,9 @@ bool DeclContext::setLastSeenDIE(CompileUnit &U, const DWARFDie &Die) {
return true; return true;
} }
PointerIntPair<DeclContext *, 1> DeclContextTree::getChildDeclContext( PointerIntPair<DeclContext *, 1>
DeclContext &Context, const DWARFDie &DIE, CompileUnit &U, DeclContextTree::getChildDeclContext(DeclContext &Context, const DWARFDie &DIE,
UniquingStringPool &StringPool, bool InClangModule) { CompileUnit &U, bool InClangModule) {
unsigned Tag = DIE.getTag(); unsigned Tag = DIE.getTag();
// FIXME: dsymutil-classic compat: We should bail out here if we // FIXME: dsymutil-classic compat: We should bail out here if we
@ -80,27 +80,20 @@ PointerIntPair<DeclContext *, 1> DeclContextTree::getChildDeclContext(
break; break;
} }
const char *Name = DIE.getLinkageName();
const char *ShortName = DIE.getShortName();
if (!Name)
Name = ShortName;
StringRef NameRef; StringRef NameRef;
StringRef ShortNameRef;
StringRef FileRef; StringRef FileRef;
if (Name) if (const char *LinkageName = DIE.getLinkageName())
NameRef = StringPool.internString(Name); NameRef = StringPool.internString(LinkageName);
else if (Tag == dwarf::DW_TAG_namespace) else if (const char *ShortName = DIE.getShortName())
NameRef = StringPool.internString(ShortName);
bool IsAnonymousNamespace = NameRef.empty() && Tag == dwarf::DW_TAG_namespace;
if (IsAnonymousNamespace) {
// FIXME: For dsymutil-classic compatibility. I think uniquing within // FIXME: For dsymutil-classic compatibility. I think uniquing within
// anonymous namespaces is wrong. There is no ODR guarantee there. // anonymous namespaces is wrong. There is no ODR guarantee there.
NameRef = StringPool.internString("(anonymous namespace)"); NameRef = "(anonymous namespace)";
}
if (ShortName && ShortName != Name)
ShortNameRef = StringPool.internString(ShortName);
else
ShortNameRef = NameRef;
if (Tag != dwarf::DW_TAG_class_type && Tag != dwarf::DW_TAG_structure_type && if (Tag != dwarf::DW_TAG_class_type && Tag != dwarf::DW_TAG_structure_type &&
Tag != dwarf::DW_TAG_union_type && Tag != dwarf::DW_TAG_union_type &&
@ -121,7 +114,7 @@ PointerIntPair<DeclContext *, 1> DeclContextTree::getChildDeclContext(
// module-defined types do not have a file and line. // module-defined types do not have a file and line.
ByteSize = dwarf::toUnsigned(DIE.find(dwarf::DW_AT_byte_size), ByteSize = dwarf::toUnsigned(DIE.find(dwarf::DW_AT_byte_size),
std::numeric_limits<uint64_t>::max()); std::numeric_limits<uint64_t>::max());
if (Tag != dwarf::DW_TAG_namespace || !Name) { if (Tag != dwarf::DW_TAG_namespace || IsAnonymousNamespace) {
if (unsigned FileNum = if (unsigned FileNum =
dwarf::toUnsigned(DIE.find(dwarf::DW_AT_decl_file), 0)) { dwarf::toUnsigned(DIE.find(dwarf::DW_AT_decl_file), 0)) {
if (const auto *LT = U.getOrigUnit().getContext().getLineTableForUnit( if (const auto *LT = U.getOrigUnit().getContext().getLineTableForUnit(
@ -129,29 +122,14 @@ PointerIntPair<DeclContext *, 1> DeclContextTree::getChildDeclContext(
// FIXME: dsymutil-classic compatibility. I'd rather not // FIXME: dsymutil-classic compatibility. I'd rather not
// unique anything in anonymous namespaces, but if we do, then // unique anything in anonymous namespaces, but if we do, then
// verify that the file and line correspond. // verify that the file and line correspond.
if (!Name && Tag == dwarf::DW_TAG_namespace) if (IsAnonymousNamespace)
FileNum = 1; FileNum = 1;
if (LT->hasFileAtIndex(FileNum)) { if (LT->hasFileAtIndex(FileNum)) {
Line = dwarf::toUnsigned(DIE.find(dwarf::DW_AT_decl_line), 0); Line = dwarf::toUnsigned(DIE.find(dwarf::DW_AT_decl_line), 0);
// Cache the resolved paths based on the index in the line table, // Cache the resolved paths based on the index in the line table,
// because calling realpath is expansive. // because calling realpath is expensive.
StringRef ResolvedPath = U.getResolvedPath(FileNum); FileRef = getResolvedPath(U, FileNum, *LT);
if (!ResolvedPath.empty()) {
FileRef = ResolvedPath;
} else {
std::string File;
bool FoundFileName = LT->getFileNameByIndex(
FileNum, U.getOrigUnit().getCompilationDir(),
DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath,
File);
(void)FoundFileName;
assert(FoundFileName && "Must get file name from line table");
// Second level of caching, this time based on the file's parent
// path.
FileRef = PathResolver.resolve(File, StringPool);
U.setResolvedPath(FileNum, FileRef);
}
} }
} }
} }
@ -175,7 +153,7 @@ PointerIntPair<DeclContext *, 1> DeclContextTree::getChildDeclContext(
// FIXME: dsymutil-classic compatibility: when we don't have a name, // FIXME: dsymutil-classic compatibility: when we don't have a name,
// use the filename. // use the filename.
if (Tag == dwarf::DW_TAG_namespace && NameRef == "(anonymous namespace)") if (IsAnonymousNamespace)
Hash = hash_combine(Hash, FileRef); Hash = hash_combine(Hash, FileRef);
// Now look if this context already exists. // Now look if this context already exists.
@ -210,4 +188,28 @@ PointerIntPair<DeclContext *, 1> DeclContextTree::getChildDeclContext(
return PointerIntPair<DeclContext *, 1>(*ContextIter); return PointerIntPair<DeclContext *, 1>(*ContextIter);
} }
StringRef
DeclContextTree::getResolvedPath(CompileUnit &CU, unsigned FileNum,
const DWARFDebugLine::LineTable &LineTable) {
std::pair<unsigned, unsigned> Key = {CU.getUniqueID(), FileNum};
ResolvedPathsMap::const_iterator It = ResolvedPaths.find(Key);
if (It == ResolvedPaths.end()) {
std::string FileName;
bool FoundFileName = LineTable.getFileNameByIndex(
FileNum, CU.getOrigUnit().getCompilationDir(),
DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath, FileName);
(void)FoundFileName;
assert(FoundFileName && "Must get file name from line table");
// Second level of caching, this time based on the file's parent
// path.
StringRef ResolvedPath = PathResolver.resolve(FileName, StringPool);
It = ResolvedPaths.insert(std::make_pair(Key, ResolvedPath)).first;
}
return It->second;
}
} // namespace llvm } // namespace llvm