Index: lld/COFF/InputFiles.h =================================================================== --- lld/COFF/InputFiles.h +++ lld/COFF/InputFiles.h @@ -14,6 +14,7 @@ #include "lld/Common/LLVM.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseSet.h" +#include "llvm/DebugInfo/CodeView/TypeRecord.h" #include "llvm/LTO/LTO.h" #include "llvm/Object/Archive.h" #include "llvm/Object/COFF.h" @@ -124,6 +125,9 @@ // Returns the underying COFF file. COFFObjectFile *getCOFFObj() { return COFFObj.get(); } + // Whether the .obj was already merged into the final PDB or not + bool wasProcessedForPDB() const { return !!ModuleDBI; } + static std::vector Instances; // Flags in the absolute @feat.00 symbol if it is present. These usually @@ -144,6 +148,10 @@ // if we are not producing a PDB. llvm::pdb::DbiModuleDescriptorBuilder *ModuleDBI = nullptr; + // If the .obj represents a precompiled types type map, we need the signature + // through the following record + llvm::Optional EndPrecomp; + private: void initializeChunks(); void initializeSymbols(); Index: lld/COFF/PDB.cpp =================================================================== --- lld/COFF/PDB.cpp +++ lld/COFF/PDB.cpp @@ -48,6 +48,7 @@ #include "llvm/Object/CVDebugRecord.h" #include "llvm/Support/BinaryByteStream.h" #include "llvm/Support/Endian.h" +#include "llvm/Support/Errc.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/JamCRC.h" #include "llvm/Support/Path.h" @@ -79,6 +80,7 @@ SmallVector TPIMap; SmallVector IPIMap; bool IsTypeServerMap = false; + bool IsPrecompiledTypeMap = false; }; class PDBLinker { @@ -102,7 +104,7 @@ void addObjectsToPDB(); /// Link CodeView from a single object file into the PDB. - void addObjFile(ObjFile *File); + void addObjFile(ObjFile *File, CVIndexMap *CV = nullptr); /// Produce a mapping from the type and item indices used in the object /// file to those in the destination PDB. @@ -121,6 +123,8 @@ Expected maybeMergeTypeServerPDB(ObjFile *File, TypeServer2Record &TS); + const CVIndexMap &aquirePrecompObj(ObjFile *File, PrecompRecord &Precomp); + /// Add the section map and section contributions to the PDB. void addSections(ArrayRef OutputSections, ArrayRef SectionTable); @@ -166,6 +170,9 @@ /// Type index mappings of type server PDBs that we've loaded so far. std::map TypeServerIndexMappings; + /// Type index mappings of precompiled type type map that we've loaded so far. + std::map PrecompTypeIndexMappings; + /// List of TypeServer PDBs which cannot be loaded. /// Cached to prevent repeated load attempts. std::set MissingTypeServerPDBs; @@ -245,18 +252,23 @@ }); } -static Optional -maybeReadTypeServerRecord(CVTypeArray &Types) { +template +static Optional +maybeReadRecord(CVTypeArray &Types, const uint32_t RecordTypeValue, + const bool drop = false) { auto I = Types.begin(); if (I == Types.end()) return None; const CVType &Type = *I; - if (Type.kind() != LF_TYPESERVER2) + if (Type.kind() != RecordTypeValue) return None; - TypeServer2Record TS; - if (auto EC = TypeDeserializer::deserializeAs(const_cast(Type), TS)) - fatal("error reading type server record: " + toString(std::move(EC))); - return std::move(TS); + RecordType R; + if (auto EC = TypeDeserializer::deserializeAs(const_cast(Type), R)) + fatal("error reading record: " + toString(std::move(EC))); + if (drop) + Types.setUnderlyingStream( + Types.getUnderlyingStream().drop_front(Type.length())); + return std::move(R); } Expected PDBLinker::mergeDebugT(ObjFile *File, @@ -264,7 +276,10 @@ ScopedTimer T(TypeMergingTimer); ArrayRef Data = getDebugSection(File, ".debug$T"); + // Try again, precompiled headers use .debug$P instead of .debug$T if (Data.empty()) + Data = getDebugSection(File, ".debug$P"); + if (Data.empty()) return ObjectIndexMap; BinaryByteStream Stream(Data, support::little); @@ -275,11 +290,30 @@ // Look through type servers. If we've already seen this type server, don't // merge any type information. - if (Optional TS = maybeReadTypeServerRecord(Types)) - return maybeMergeTypeServerPDB(File, *TS); + if (Optional TS = + maybeReadRecord(Types, LF_TYPESERVER2)) + return maybeMergeTypeServerPDB(File, *TS); - // This is a /Z7 object. Fill in the temporary, caller-provided - // ObjectIndexMap. + // This is a /Z7 object. + // If the current .obj was compiled with /Yu, then process the corresponding + // precompiled .obj (/Yc) first. Some type indices in the current .obj are + // referencing data in the precompiled .obj, so we need both to be loaded. + if (Optional Precomp = + maybeReadRecord(Types, LF_PRECOMP, true)) { + const CVIndexMap &PrecompIndexMap = aquirePrecompObj(File, *Precomp); + assert(PrecompIndexMap.IsPrecompiledTypeMap); + + if (PrecompIndexMap.TPIMap.empty()) + return PrecompIndexMap; + + assert(Precomp->getStartTypeIndex() == TypeIndex::FirstNonSimpleIndex); + assert(Precomp->getTypesCount() <= PrecompIndexMap.TPIMap.size()); + // Use the precomp's previously remapped index map + ObjectIndexMap.TPIMap.append(PrecompIndexMap.TPIMap.begin(), + PrecompIndexMap.TPIMap.begin() + Precomp->getTypesCount()); + } + + // Fill in the temporary, caller-provided ObjectIndexMap. if (Config->DebugGHashes) { ArrayRef Hashes; std::vector OwnedHashes; @@ -291,15 +325,18 @@ } if (auto Err = mergeTypeAndIdRecords(GlobalIDTable, GlobalTypeTable, - ObjectIndexMap.TPIMap, Types, Hashes)) + ObjectIndexMap.TPIMap, Types, Hashes, + File->EndPrecomp)) fatal("codeview::mergeTypeAndIdRecords failed: " + toString(std::move(Err))); } else { if (auto Err = mergeTypeAndIdRecords(IDTable, TypeTable, - ObjectIndexMap.TPIMap, Types)) + ObjectIndexMap.TPIMap, Types, + File->EndPrecomp)) fatal("codeview::mergeTypeAndIdRecords failed: " + toString(std::move(Err))); } + return ObjectIndexMap; } @@ -397,9 +434,11 @@ auto IpiHashes = GloballyHashedType::hashIds(ExpectedIpi->typeArray(), TpiHashes); + Optional EndPrecomp; // Merge TPI first, because the IPI stream will reference type indices. if (auto Err = mergeTypeRecords(GlobalTypeTable, IndexMap.TPIMap, - ExpectedTpi->typeArray(), TpiHashes)) + ExpectedTpi->typeArray(), TpiHashes, + EndPrecomp)) fatal("codeview::mergeTypeRecords failed: " + toString(std::move(Err))); // Merge IPI. @@ -422,6 +461,89 @@ return IndexMap; } +const CVIndexMap &PDBLinker::aquirePrecompObj(ObjFile *File, + PrecompRecord &Precomp) { + + // First, check if we already loaded the precompiled types .obj with this + // signature. Return the type index mapping if we have it. + auto Insertion = PrecompTypeIndexMappings.insert( + {Precomp.getSignature(), CVIndexMap()}); + CVIndexMap &IndexMap = Insertion.first->second; + if (!Insertion.second) + return IndexMap; + + // Mark this map as a precompiled types map. + IndexMap.IsPrecompiledTypeMap = true; + + SmallString<128> PrecompPath = Precomp.getPrecompFilePath(); + sys::fs::make_absolute(PrecompPath); + sys::path::native(PrecompPath, sys::path::Style::windows); + + ObjFile *CurrentFile = nullptr; + SmallString<128> CurrentPath; + + // link.exe requires that a precompiled object must always be provided + // on the command-line + // First, try comparing the whole path if possible + for (ObjFile *f : ObjFile::Instances) { + CurrentPath = f->getName(); + sys::fs::make_absolute(CurrentPath); + sys::path::native(CurrentPath, sys::path::Style::windows); + + if (CurrentPath.equals_lower(PrecompPath)) { + CurrentFile = f; + break; + } + CurrentPath.clear(); + } + + if (CurrentPath.empty()) + { + // If the precomp object was on a different path, we can still use it, + // although only if its unique on the command-line + StringRef PrecompName = sys::path::filename(PrecompPath); + CurrentPath.clear(); + for (ObjFile *f : ObjFile::Instances) { + SmallString<128> path = f->getName(); + sys::fs::make_absolute(path); + sys::path::native(path, sys::path::Style::windows); + StringRef filename = sys::path::filename(path); + + if (!filename.equals_lower(PrecompName)) + continue; + + if (!CurrentPath.empty()) + fatal("the precompiled object name must appear once on the " + "command-line: " + PrecompName); + + CurrentPath = filename; + CurrentFile = f; + } + } + + if (!CurrentPath.empty()) + { + addObjFile(CurrentFile, &IndexMap); + + if (!CurrentFile->EndPrecomp) + fatal(CurrentPath + " is not a precompiled .obj"); + + if (Precomp.getSignature() != CurrentFile->EndPrecomp->getSignature()) + warn("the signature of the precompiled file " + CurrentPath + " does not " + "match; the file(s) might be out of date. Linking as if no debug " + "info."); + + return IndexMap; + } + + warn("dependent precompiled object not found: " + PrecompPath + + ", linking as if no debug info (" + File->getName() + ")"); + + static CVIndexMap emptyMap; + emptyMap.IsPrecompiledTypeMap = true; + return emptyMap; +} + static bool remapTypeIndex(TypeIndex &TI, ArrayRef TypeIndexMap) { if (TI.isSimple()) return true; @@ -780,7 +902,10 @@ ".debug$S"); } -void PDBLinker::addObjFile(ObjFile *File) { +void PDBLinker::addObjFile(ObjFile *File, CVIndexMap *CV) { + if (File->wasProcessedForPDB()) + return; + // Add a module descriptor for every object file. We need to put an absolute // path to the object into the PDB. If this is a plain object, we make its // path absolute. If it's an object in an archive, we make the archive path @@ -799,7 +924,7 @@ // the PDB first, so that we can get the map from object file type and item // indices to PDB type and item indices. CVIndexMap ObjectIndexMap; - auto IndexMapResult = mergeDebugT(File, ObjectIndexMap); + auto IndexMapResult = mergeDebugT(File, CV ? *CV : ObjectIndexMap); // If the .debug$T sections fail to merge, assume there is no debug info. if (!IndexMapResult) { Index: lld/test/COFF/precomp-link.test =================================================================== --- lld/test/COFF/precomp-link.test +++ lld/test/COFF/precomp-link.test @@ -0,0 +1,32 @@ + +RUN: lld-link %S/Inputs/precomp-a.obj %S/Inputs/precomp-b.obj \ +RUN: %S/Inputs/precomp.obj /nodefaultlib /entry:main \ +RUN: /debug /pdb:%t.pdb /out:%t.exe /opt:ref /opt:icf +RUN: llvm-pdbutil dump -types %t.pdb | FileCheck %s + +CHECK: Types (TPI Stream) +CHECK-NOT: LF_PRECOMP +CHECK-NOT: LF_ENDPRECOMP + +# // precomp.h +# #pragma once +# int Function(char A); + +# // precomp.cpp +# #include "precomp.h" + +# // a.cpp +# #include "precomp.h" +# int main(void) { +# Function('a'); +# return 0; +# } + +# // b.cpp +# #include "precomp.h" +# int Function(char a) { +# return (int)a; +# } + +# // cl.exe precomp.cpp /Z7 /Ycprecomp.h /c +# // cl.exe a.cpp b.cpp /Z7 /Yuprecomp.h /c Index: llvm/include/llvm/DebugInfo/CodeView/TypeStreamMerger.h =================================================================== --- llvm/include/llvm/DebugInfo/CodeView/TypeStreamMerger.h +++ llvm/include/llvm/DebugInfo/CodeView/TypeStreamMerger.h @@ -83,18 +83,21 @@ Error mergeTypeAndIdRecords(MergingTypeTableBuilder &DestIds, MergingTypeTableBuilder &DestTypes, SmallVectorImpl &SourceToDest, - const CVTypeArray &IdsAndTypes); + const CVTypeArray &IdsAndTypes, + Optional &EndPrecomp); Error mergeTypeAndIdRecords(GlobalTypeTableBuilder &DestIds, GlobalTypeTableBuilder &DestTypes, SmallVectorImpl &SourceToDest, const CVTypeArray &IdsAndTypes, - ArrayRef Hashes); + ArrayRef Hashes, + Optional &EndPrecomp); Error mergeTypeRecords(GlobalTypeTableBuilder &Dest, SmallVectorImpl &SourceToDest, const CVTypeArray &Types, - ArrayRef Hashes); + ArrayRef Hashes, + Optional &EndPrecomp); Error mergeIdRecords(GlobalTypeTableBuilder &Dest, ArrayRef Types, SmallVectorImpl &SourceToDest, Index: llvm/lib/DebugInfo/CodeView/TypeStreamMerger.cpp =================================================================== --- llvm/lib/DebugInfo/CodeView/TypeStreamMerger.cpp +++ llvm/lib/DebugInfo/CodeView/TypeStreamMerger.cpp @@ -12,6 +12,7 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/DebugInfo/CodeView/GlobalTypeTableBuilder.h" #include "llvm/DebugInfo/CodeView/MergingTypeTableBuilder.h" +#include "llvm/DebugInfo/CodeView/TypeDeserializer.h" #include "llvm/DebugInfo/CodeView/TypeIndex.h" #include "llvm/DebugInfo/CodeView/TypeIndexDiscovery.h" #include "llvm/DebugInfo/CodeView/TypeRecord.h" @@ -63,7 +64,12 @@ public: explicit TypeStreamMerger(SmallVectorImpl &SourceToDest) : IndexMap(SourceToDest) { - SourceToDest.clear(); + // A precompiled obj might be referenced here: + // All data in SourceToDest is assumed to belong to the precompiled obj, + // and is already remapped to the target PDB. + // Every type that will be merged in needs to back-reference this data. + // We also don't want to resolve twice the types in the precompiled obj. + CurIndex += SourceToDest.size(); } static const TypeIndex Untranslated; @@ -71,7 +77,8 @@ // Local hashing entry points Error mergeTypesAndIds(MergingTypeTableBuilder &DestIds, MergingTypeTableBuilder &DestTypes, - const CVTypeArray &IdsAndTypes); + const CVTypeArray &IdsAndTypes, + Optional &EP); Error mergeIdRecords(MergingTypeTableBuilder &Dest, ArrayRef TypeSourceToDest, const CVTypeArray &Ids); @@ -82,13 +89,16 @@ Error mergeTypesAndIds(GlobalTypeTableBuilder &DestIds, GlobalTypeTableBuilder &DestTypes, const CVTypeArray &IdsAndTypes, - ArrayRef Hashes); + ArrayRef Hashes, + Optional &EP); Error mergeIdRecords(GlobalTypeTableBuilder &Dest, ArrayRef TypeSourceToDest, const CVTypeArray &Ids, ArrayRef Hashes); - Error mergeTypeRecords(GlobalTypeTableBuilder &Dest, const CVTypeArray &Types, - ArrayRef Hashes); + Error mergeTypeRecords(GlobalTypeTableBuilder &Dest, + const CVTypeArray &Types, + ArrayRef Hashes, + Optional &EP); private: Error doit(const CVTypeArray &Types); @@ -156,6 +166,8 @@ return llvm::make_error(cv_error_code::corrupt_record); } + Expected shouldRemapType(const CVType &Type); + Optional LastError; bool UseGlobalHashes = false; @@ -185,6 +197,8 @@ /// Temporary storage that we use to copy a record's data while re-writing /// its type indices. SmallVector RemapStorage; + + Optional EndPrecomp; }; } // end anonymous namespace @@ -226,7 +240,10 @@ if (IsSecondPass && MapPos >= Map.size()) { // FIXME: Print a more useful error. We can give the current record and the // index that we think its pointing to. - LastError = joinErrors(std::move(*LastError), errorCorruptRecord()); + if (LastError) + LastError = joinErrors(std::move(*LastError), errorCorruptRecord()); + else + LastError = errorCorruptRecord(); } ++NumBadIndices; @@ -258,22 +275,31 @@ Error TypeStreamMerger::mergeTypesAndIds(MergingTypeTableBuilder &DestIds, MergingTypeTableBuilder &DestTypes, - const CVTypeArray &IdsAndTypes) { + const CVTypeArray &IdsAndTypes, + Optional &EP) { DestIdStream = &DestIds; DestTypeStream = &DestTypes; UseGlobalHashes = false; - return doit(IdsAndTypes); + + auto e = doit(IdsAndTypes); + + EP = EndPrecomp; + return e; } // Global hashing entry points Error TypeStreamMerger::mergeTypeRecords(GlobalTypeTableBuilder &Dest, const CVTypeArray &Types, - ArrayRef Hashes) { + ArrayRef Hashes, + Optional &EP) { DestGlobalTypeStream = &Dest; UseGlobalHashes = true; GlobalHashes = Hashes; - return doit(Types); + auto e = doit(Types); + + EP = EndPrecomp; + return e; } Error TypeStreamMerger::mergeIdRecords(GlobalTypeTableBuilder &Dest, @@ -291,12 +317,17 @@ Error TypeStreamMerger::mergeTypesAndIds(GlobalTypeTableBuilder &DestIds, GlobalTypeTableBuilder &DestTypes, const CVTypeArray &IdsAndTypes, - ArrayRef Hashes) { + ArrayRef Hashes, + Optional &EP) { DestGlobalIdStream = &DestIds; DestGlobalTypeStream = &DestTypes; UseGlobalHashes = true; GlobalHashes = Hashes; - return doit(IdsAndTypes); + + auto e = doit(IdsAndTypes); + + EP = EndPrecomp; + return e; } Error TypeStreamMerger::doit(const CVTypeArray &Types) { @@ -342,25 +373,30 @@ } Error TypeStreamMerger::remapType(const CVType &Type) { - auto DoSerialize = - [this, Type](MutableArrayRef Storage) -> ArrayRef { - return remapIndices(Type, Storage); - }; + auto R = shouldRemapType(Type); + if (!R) + return R.takeError(); TypeIndex DestIdx = Untranslated; - if (LLVM_LIKELY(UseGlobalHashes)) { - GlobalTypeTableBuilder &Dest = - isIdRecord(Type.kind()) ? *DestGlobalIdStream : *DestGlobalTypeStream; - GloballyHashedType H = GlobalHashes[CurIndex.toArrayIndex()]; - DestIdx = Dest.insertRecordAs(H, Type.RecordData.size(), DoSerialize); - } else { - MergingTypeTableBuilder &Dest = - isIdRecord(Type.kind()) ? *DestIdStream : *DestTypeStream; + if (*R) { + auto DoSerialize = + [this, Type](MutableArrayRef Storage) -> ArrayRef { + return remapIndices(Type, Storage); + }; + if (LLVM_LIKELY(UseGlobalHashes)) { + GlobalTypeTableBuilder &Dest = + isIdRecord(Type.kind()) ? *DestGlobalIdStream : *DestGlobalTypeStream; + GloballyHashedType H = GlobalHashes[CurIndex.toArrayIndex()]; + DestIdx = Dest.insertRecordAs(H, Type.RecordData.size(), DoSerialize); + } else { + MergingTypeTableBuilder &Dest = + isIdRecord(Type.kind()) ? *DestIdStream : *DestTypeStream; - RemapStorage.resize(Type.RecordData.size()); - ArrayRef Result = DoSerialize(RemapStorage); - if (!Result.empty()) - DestIdx = Dest.insertRecordBytes(Result); + RemapStorage.resize(Type.RecordData.size()); + ArrayRef Result = DoSerialize(RemapStorage); + if (!Result.empty()) + DestIdx = Dest.insertRecordBytes(Result); + } } addMapping(DestIdx); @@ -415,25 +451,29 @@ Error llvm::codeview::mergeTypeAndIdRecords( MergingTypeTableBuilder &DestIds, MergingTypeTableBuilder &DestTypes, - SmallVectorImpl &SourceToDest, const CVTypeArray &IdsAndTypes) { + SmallVectorImpl &SourceToDest, const CVTypeArray &IdsAndTypes, + Optional &EndPrecomp) { TypeStreamMerger M(SourceToDest); - return M.mergeTypesAndIds(DestIds, DestTypes, IdsAndTypes); + return M.mergeTypesAndIds(DestIds, DestTypes, IdsAndTypes, EndPrecomp); } Error llvm::codeview::mergeTypeAndIdRecords( GlobalTypeTableBuilder &DestIds, GlobalTypeTableBuilder &DestTypes, SmallVectorImpl &SourceToDest, const CVTypeArray &IdsAndTypes, - ArrayRef Hashes) { + ArrayRef Hashes, + Optional &EndPrecomp) { TypeStreamMerger M(SourceToDest); - return M.mergeTypesAndIds(DestIds, DestTypes, IdsAndTypes, Hashes); + return M.mergeTypesAndIds(DestIds, DestTypes, IdsAndTypes, Hashes, + EndPrecomp); } Error llvm::codeview::mergeTypeRecords(GlobalTypeTableBuilder &Dest, SmallVectorImpl &SourceToDest, const CVTypeArray &Types, - ArrayRef Hashes) { + ArrayRef Hashes, + Optional &EndPrecomp) { TypeStreamMerger M(SourceToDest); - return M.mergeTypeRecords(Dest, Types, Hashes); + return M.mergeTypeRecords(Dest, Types, Hashes, EndPrecomp); } Error llvm::codeview::mergeIdRecords(GlobalTypeTableBuilder &Dest, @@ -444,3 +484,19 @@ TypeStreamMerger M(SourceToDest); return M.mergeIdRecords(Dest, Types, Ids, Hashes); } + +Expected TypeStreamMerger::shouldRemapType(const CVType &Type) { + // For .obj files containing precompiled types, we need to extract the + // signature, through EndPrecompRecord. This is done here for performance + // reasons, to avoid re-parsing the Types stream; in most cases, this .obj + // will not be a precompiled types file. + if (Type.kind() == LF_ENDPRECOMP) { + assert(!EndPrecomp); + EndPrecomp.emplace(); + if (auto EC = TypeDeserializer::deserializeAs(const_cast(Type), + EndPrecomp.getValue())) + return joinErrors(std::move(EC), errorCorruptRecord()); + return false; + } + return true; +} Index: llvm/tools/llvm-readobj/COFFDumper.cpp =================================================================== --- llvm/tools/llvm-readobj/COFFDumper.cpp +++ llvm/tools/llvm-readobj/COFFDumper.cpp @@ -1224,7 +1224,9 @@ error(object_error::parse_failed); } SmallVector SourceToDest; - if (auto EC = mergeTypeAndIdRecords(CVIDs, CVTypes, SourceToDest, Types)) + Optional EndPrecomp; + if (auto EC = mergeTypeAndIdRecords(CVIDs, CVTypes, SourceToDest, Types, + EndPrecomp)) return error(std::move(EC)); } }