Index: clang-doc/CMakeLists.txt =================================================================== --- clang-doc/CMakeLists.txt +++ clang-doc/CMakeLists.txt @@ -6,6 +6,8 @@ ClangDoc.cpp ClangDocMapper.cpp ClangDocBinary.cpp + ClangDocReducer.cpp + ClangDocRepresentation.cpp LINK_LIBS clangAnalysis Index: clang-doc/ClangDocBinary.h =================================================================== --- clang-doc/ClangDocBinary.h +++ clang-doc/ClangDocBinary.h @@ -42,6 +42,7 @@ template void writeBitstream(const T &I, BitstreamWriter &Stream, bool writeBlockInfo = false); + void writeAllBitstream(InfoSet &I, BitstreamWriter &Stream); private: void emitBlockInfoBlock(BitstreamWriter &Stream); @@ -65,6 +66,36 @@ AbbreviationMap Abbrevs; }; +// Class to read bitstream into an InfoSet collection +class ClangDocBinaryReader { + public: + ClangDocBinaryReader() {} + using RecordData = SmallVector; + + // Does not merge infos + std::unique_ptr readBitstream(SmallString<2048> Bits); + bool readBitstreamToInfoSet(SmallString<2048> Bits, std::unique_ptr &IS); + + private: + enum class Cursor { BadBlock = 1, Record, BlockEnd, BlockBegin }; + bool validateStream(llvm::BitstreamCursor &Stream); + bool readBlockInfoBlock(llvm::BitstreamCursor &Stream); + template + CommentInfo &getCommentInfo(T &I); + template + bool addNamedType(T &I, NamedType &NT); + template + bool readRecord(llvm::BitstreamCursor &Stream, unsigned ID, T &I); + template + bool readBlock(llvm::BitstreamCursor &Stream, unsigned ID, T &I); + Cursor skipUntilRecordOrBlock(llvm::BitstreamCursor &Stream, + unsigned &BlockOrRecordID); + + SmallVector Record; + Optional BlockInfo; + std::map RecordNames; +}; + } // namespace doc } // namespace clang Index: clang-doc/ClangDocBinary.cpp =================================================================== --- clang-doc/ClangDocBinary.cpp +++ clang-doc/ClangDocBinary.cpp @@ -406,5 +406,486 @@ #undef EMITINFO +void ClangDocBinaryWriter::writeAllBitstream(InfoSet &ISet, + BitstreamWriter &Stream) { + emitBlockInfoBlock(Stream); + for (const auto &I : ISet.getNamespaceInfos()) writeBitstream(I, Stream); + for (const auto &I : ISet.getFunctionInfos()) writeBitstream(I, Stream); + for (const auto &I : ISet.getRecordInfos()) writeBitstream(I, Stream); + for (const auto &I : ISet.getEnumInfos()) writeBitstream(I, Stream); + for (const auto &I : ISet.getNonDefInfos()) writeBitstream(I.second, Stream); +} + +// Reader + +template <> +bool ClangDocBinaryReader::readRecord(llvm::BitstreamCursor &Stream, + unsigned ID, NamespaceInfo &I) { + // Read the record. + Record.clear(); + StringRef Blob; + unsigned RecID = Stream.readRecord(ID, Record, &Blob); + + switch ((DataTypes)RecID) { + // Locations + case NAMESPACE_FULLY_QUALIFIED_NAME: + I.FullyQualifiedName = Blob; + return true; + case NAMESPACE_NAME: + I.SimpleName = Blob; + return true; + case NAMESPACE_NAMESPACE: + I.Namespace = Blob; + return true; + + default: + errs() << "Invalid record field in block.\n"; + return false; + } +} + +template <> +bool ClangDocBinaryReader::readRecord(llvm::BitstreamCursor &Stream, + unsigned ID, RecordInfo &I) { + // Read the record. + Record.clear(); + StringRef Blob; + unsigned RecID = Stream.readRecord(ID, Record, &Blob); + + switch ((DataTypes)RecID) { + case RECORD_FULLY_QUALIFIED_NAME: + I.FullyQualifiedName = Blob; + return true; + case RECORD_NAME: + I.SimpleName = Blob; + return true; + case RECORD_NAMESPACE: + I.Namespace = Blob; + return true; + case RECORD_PARENT: + I.Parents.push_back(Blob); + return true; + case RECORD_VPARENT: + I.VirtualParents.push_back(Blob); + return true; + case RECORD_DEFLOCATION: + I.DefLoc = Location{(int)Record[0], Blob}; + return true; + case RECORD_LOCATION: + I.Loc.emplace_back(Location{(int)Record[0], Blob}); + return true; + case RECORD_TAG_TYPE: + I.TagType = (TagTypeKind)Record[0]; + return true; + + default: + errs() << "Invalid record field in block.\n"; + return false; + } +} + +template <> +bool ClangDocBinaryReader::readRecord(llvm::BitstreamCursor &Stream, + unsigned ID, EnumInfo &I) { + // Read the record. + Record.clear(); + StringRef Blob; + unsigned RecID = Stream.readRecord(ID, Record, &Blob); + + switch ((DataTypes)RecID) { + case ENUM_FULLY_QUALIFIED_NAME: + I.FullyQualifiedName = Blob; + return true; + case ENUM_NAME: + I.SimpleName = Blob; + return true; + case ENUM_NAMESPACE: + I.Namespace = Blob; + return true; + case ENUM_DEFLOCATION: + I.DefLoc = Location{(int)Record[0], Blob}; + return true; + case ENUM_LOCATION: + I.Loc.emplace_back(Location{(int)Record[0], Blob}); + return true; + case ENUM_SCOPED: + I.Scoped = Record[0]; + return true; + + default: + errs() << "Invalid record field in block.\n"; + return false; + } +} + +template <> +bool ClangDocBinaryReader::readRecord(llvm::BitstreamCursor &Stream, + unsigned ID, FunctionInfo &I) { + // Read the record. + Record.clear(); + StringRef Blob; + unsigned RecID = Stream.readRecord(ID, Record, &Blob); + + switch ((DataTypes)RecID) { + case FUNCTION_FULLY_QUALIFIED_NAME: + I.FullyQualifiedName = Blob; + return true; + case FUNCTION_NAME: + I.SimpleName = Blob; + return true; + case FUNCTION_NAMESPACE: + I.Namespace = Blob; + return true; + case FUNCTION_PARENT: + I.Parent = Blob; + return true; + case FUNCTION_MANGLED_NAME: + I.MangledName = Blob; + return true; + case FUNCTION_DEFLOCATION: + I.DefLoc = Location{(int)Record[0], Blob}; + return true; + case FUNCTION_LOCATION: + I.Loc.emplace_back(Location{(int)Record[0], Blob}); + return true; + case FUNCTION_ACCESS: + I.Access = (AccessSpecifier)Record[0]; + return true; + + default: + errs() << "Invalid record field in block.\n"; + return false; + } +} + +template <> +bool ClangDocBinaryReader::readRecord(llvm::BitstreamCursor &Stream, + unsigned ID, NonDefInfo &I) { + // Read the record. + Record.clear(); + StringRef Blob; + unsigned RecID = Stream.readRecord(ID, Record, &Blob); + + switch ((DataTypes)RecID) { + case NONDEF_FULLY_QUALIFIED_NAME: + I.FullyQualifiedName = Blob; + return true; + case NONDEF_NAME: + I.SimpleName = Blob; + return true; + case NONDEF_NAMESPACE: + I.Namespace = Blob; + return true; + case NONDEF_TYPE: + I.Type = (NonDefInfo::InfoType)Record[0]; + return true; + case NONDEF_LOCATION: + I.Loc.emplace_back(Location{(int)Record[0], Blob}); + return true; + + default: + errs() << "Invalid record field in block.\n"; + return false; + } +} + +template <> +bool ClangDocBinaryReader::readRecord(llvm::BitstreamCursor &Stream, + unsigned ID, NamedType &I) { + // Read the record. + Record.clear(); + StringRef Blob; + unsigned RecID = Stream.readRecord(ID, Record, &Blob); + + switch ((DataTypes)RecID) { + case NAMED_TYPE_ID: + I.Field = (NamedType::FieldName) Record[0]; + return true; + case NAMED_TYPE_TYPE: + I.Type = Blob; + return true; + case NAMED_TYPE_NAME: + I.Name = Blob; + return true; + case NAMED_TYPE_ACCESS: + I.Access = (AccessSpecifier)Record[0]; + return true; + + default: + errs() << "Invalid record field in block.\n"; + return false; + } +} + +template <> +bool ClangDocBinaryReader::readRecord(llvm::BitstreamCursor &Stream, + unsigned ID, CommentInfo &I) { + // Read the record. + Record.clear(); + StringRef Blob; + unsigned RecID = Stream.readRecord(ID, Record, &Blob); + + switch ((DataTypes)RecID) { + case COMMENT_KIND: + I.Kind = Blob; + return true; + case COMMENT_TEXT: + I.Text = Blob; + return true; + case COMMENT_NAME: + I.Name = Blob; + return true; + case COMMENT_DIRECTION: + I.Direction = Blob; + return true; + case COMMENT_PARAMNAME: + I.ParamName = Blob; + return true; + case COMMENT_CLOSENAME: + I.CloseName = Blob; + return true; + case COMMENT_ATTRKEY: + I.AttrKeys.push_back(Blob); + return true; + case COMMENT_ATTRVAL: + I.AttrValues.push_back(Blob); + return true; + case COMMENT_ARG: + I.Args.push_back(Blob); + return true; + case COMMENT_POSITION: + I.Position.push_back(Blob); + return true; + case COMMENT_SELFCLOSING: + I.SelfClosing = Record[0]; + return true; + case COMMENT_EXPLICIT: + I.Explicit = Record[0]; + return true; + + default: + errs() << "Invalid record field in block.\n"; + return false; + } +} + +template +CommentInfo &ClangDocBinaryReader::getCommentInfo(T &I) { + I.Description.emplace_back(CommentInfo{}); + return I.Description.back(); +} + +template <> +CommentInfo &ClangDocBinaryReader::getCommentInfo(CommentInfo &I) { + I.Children.emplace_back(std::make_shared()); + return *I.Children.back(); +} + +template +bool ClangDocBinaryReader::addNamedType(T &I, NamedType &NT) { + errs() << "Should not have a named type subblock.\n"; + return false; +} + +template <> +bool ClangDocBinaryReader::addNamedType(RecordInfo &I, NamedType &NT) { + if (NT.Field == NamedType::MEMBER) { + I.Members.emplace_back(NT); + return true; + } + errs() << "Unknown field.\n"; + return false; +} + +template <> +bool ClangDocBinaryReader::addNamedType(EnumInfo &I, NamedType &NT) { + if (NT.Field == NamedType::MEMBER) { + I.Members.emplace_back(NT); + return true; + } + errs() << "Unknown field.\n"; + return false; +} + +template <> +bool ClangDocBinaryReader::addNamedType(FunctionInfo &I, NamedType &NT) { + switch (NT.Field) { + case (NamedType::PARAM): + I.Params.emplace_back(NT); + return true; + case (NamedType::RETTYPE): + // TODO: get rid of copy here + I.ReturnType = NT; + return true; + default: + errs() << "Unknown field.\n"; + return false; + } +} + +template +bool ClangDocBinaryReader::readBlock(llvm::BitstreamCursor &Stream, unsigned ID, + T &I) { + if (Stream.EnterSubBlock(ID)) return false; + + while (true) { + unsigned BlockOrCode = 0; + Cursor Res = skipUntilRecordOrBlock(Stream, BlockOrCode); + + switch (Res) { + case Cursor::BadBlock: + return false; + case Cursor::BlockEnd: + return true; + case Cursor::BlockBegin: + switch (BlockOrCode) { + // Blocks can only have Comment or NamedType subblocks + case COMMENT_BLOCK_ID: + if (readBlock(Stream, COMMENT_BLOCK_ID, getCommentInfo(I))) + continue; + return false; + case NAMED_TYPE_BLOCK_ID: { + NamedType N; + if (readBlock(Stream, BlockOrCode, N)) { + if (addNamedType(I, N)) continue; + } + return false; + } + default: + errs() << "Invalid subblock type\n"; + return false; + } + if (!Stream.SkipBlock()) return false; + continue; + case Cursor::Record: + break; + } + if (!readRecord(Stream, BlockOrCode, I)) return false; + } +} + +ClangDocBinaryReader::Cursor ClangDocBinaryReader::skipUntilRecordOrBlock( + llvm::BitstreamCursor &Stream, unsigned &BlockOrRecordID) { + BlockOrRecordID = 0; + + while (!Stream.AtEndOfStream()) { + unsigned Code = Stream.ReadCode(); + + switch ((llvm::bitc::FixedAbbrevIDs)Code) { + case llvm::bitc::ENTER_SUBBLOCK: + BlockOrRecordID = Stream.ReadSubBlockID(); + return Cursor::BlockBegin; + case llvm::bitc::END_BLOCK: + if (Stream.ReadBlockEnd()) return Cursor::BadBlock; + return Cursor::BlockEnd; + case llvm::bitc::DEFINE_ABBREV: + Stream.ReadAbbrevRecord(); + continue; + case llvm::bitc::UNABBREV_RECORD: + return Cursor::BadBlock; + default: + // We found a record. + BlockOrRecordID = Code; + return Cursor::Record; + } + } + llvm_unreachable("Premature stream end."); +} + +bool ClangDocBinaryReader::validateStream(llvm::BitstreamCursor &Stream) { + if (Stream.AtEndOfStream()) return false; + + // Sniff for the signature. + if (Stream.Read(8) != 'D' || Stream.Read(8) != 'O' || Stream.Read(8) != 'C' || + Stream.Read(8) != 'S') + return false; + return true; +} + +bool ClangDocBinaryReader::readBlockInfoBlock(llvm::BitstreamCursor &Stream) { + BlockInfo = Stream.ReadBlockInfoBlock(/*ReadBlockInfoNames=*/true); + if (!BlockInfo) return false; + Stream.setBlockInfo(&*BlockInfo); + // Extract the record names associated with each field + for (unsigned i = BI_FIRST; i <= BI_LAST; ++i) { + for (const auto &N : (*BlockInfo).getBlockInfo(i)->RecordNames) + RecordNames[N.first] = N.second; + } + return true; +} + +bool ClangDocBinaryReader::readBitstreamToInfoSet(SmallString<2048> Bits, std::unique_ptr &IS) { + BitstreamCursor Stream(Bits); + if (!validateStream(Stream)) return false; + + // Read the top level blocks. + while (!Stream.AtEndOfStream()) { + unsigned Code = Stream.ReadCode(); + if (Code != bitc::ENTER_SUBBLOCK) return false; + + switch (auto ID = Stream.ReadSubBlockID()) { + case llvm::bitc::BLOCKINFO_BLOCK_ID: + if (readBlockInfoBlock(Stream)) continue; + return false; + case NAMESPACE_BLOCK_ID: { + NamespaceInfo N; + if (readBlock(Stream, ID, N)) { + IS->insert(N.FullyQualifiedName, N); + continue; + } + return false; + } + case NONDEF_BLOCK_ID: { + NonDefInfo N; + if (readBlock(Stream, ID, N)) { + IS->insert(N.FullyQualifiedName, N); + continue; + } + return false; + } + case RECORD_BLOCK_ID: { + RecordInfo N; + if (readBlock(Stream, ID, N)) { + IS->insert(N.FullyQualifiedName, N); + continue; + } + return false; + } + case ENUM_BLOCK_ID: { + EnumInfo N; + if (readBlock(Stream, ID, N)) { + IS->insert(N.FullyQualifiedName, N); + continue; + } + return false; + } + case FUNCTION_BLOCK_ID: { + FunctionInfo N; + if (readBlock(Stream, ID, N)) { + IS->insert(N.MangledName, N); + continue; + } + return false; + } + // NamedType and Comment blocks should not appear at the top level + case NAMED_TYPE_BLOCK_ID: + case COMMENT_BLOCK_ID: + errs() << "Invalid top level block.\n"; + return false; + default: + if (!Stream.SkipBlock()) return false; + continue; + } + } + return true; +} + +std::unique_ptr ClangDocBinaryReader::readBitstream(SmallString<2048> Bits) { + std::unique_ptr IS = llvm::make_unique(); + if (readBitstreamToInfoSet(Bits, IS)) + return std::move(IS); + return nullptr; +} + } // namespace doc } // namespace clang Index: clang-doc/ClangDocReducer.h =================================================================== --- /dev/null +++ clang-doc/ClangDocReducer.h @@ -0,0 +1,26 @@ +///===-- ClangDocReducer.h - ClangDocReducer -------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_REDUCER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_REDUCER_H + +#include "ClangDocRepresentation.h" +#include "clang/Tooling/Execution.h" +#include "clang/Tooling/Tooling.h" + +namespace clang { +namespace doc { + +// Combine occurrences of the same info across translation units. +std::unique_ptr mergeInfos(tooling::ToolResults *Results); + +} // namespace doc +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_CLANG_DOC_REDUCER_H Index: clang-doc/ClangDocReducer.cpp =================================================================== --- /dev/null +++ clang-doc/ClangDocReducer.cpp @@ -0,0 +1,32 @@ +///===-- ClangDocReducer.h - ClangDocReducer -------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangDocBinary.h" +#include "ClangDocReducer.h" +#include "ClangDocRepresentation.h" + +namespace clang { +namespace doc { + +std::unique_ptr mergeInfos(tooling::ToolResults *Results) { + std::unique_ptr UniqueInfos = llvm::make_unique(); + doc::ClangDocBinaryReader Reader; + bool Err = false; + Results->forEachResult([&](StringRef Key, StringRef Value) { + if (!Reader.readBitstreamToInfoSet(Value, UniqueInfos)) { + Err = true; + return; + } + }); + if (Err) return nullptr; + return std::move(UniqueInfos); +} + +} // namespace doc +} // namespace clang Index: clang-doc/ClangDocRepresentation.h =================================================================== --- clang-doc/ClangDocRepresentation.h +++ clang-doc/ClangDocRepresentation.h @@ -106,6 +106,46 @@ // TODO: Add functionality to include separate markdown pages. +class InfoSet { +public: + InfoSet() {} + + template + void insert(StringRef Key, T &I); + + // Returns the info with a Key, if it exists. Valid until next insert(). + template + T* find(StringRef Key); + + void removeNonDef(StringRef Key); + + const std::vector& getNamespaceInfos() const { return NamespaceInfos; } + const std::vector& getFunctionInfos() const { return FunctionInfos; } + const std::vector& getRecordInfos() const { return RecordInfos; } + const std::vector& getEnumInfos() const { return EnumInfos; } + const llvm::StringMap& getNonDefInfos() const { return NonDefInfos; } + +private: + // Merge symbols L and R, preferring data from L in case of conflict. + // The two symbols must have the same ID. + template + void mergeInfo(L *Left, R *Right); + + void mergeInfoBase(Info *Left, Info *Right); + void mergeSymbolInfoBase(SymbolInfo *Left, SymbolInfo *Right); + void mergeNamedType(NamedType *Left, NamedType *Right); + + std::vector NamespaceInfos; + std::vector FunctionInfos; + std::vector RecordInfos; + std::vector EnumInfos; + llvm::DenseMap InfoIndex; + llvm::StringMap NonDefInfos; +}; + + + + } // namespace doc } // namespace clang Index: clang-doc/ClangDocRepresentation.cpp =================================================================== --- /dev/null +++ clang-doc/ClangDocRepresentation.cpp @@ -0,0 +1,178 @@ +///===-- ClangDocRepresentation.cpp - ClangDocRepresenation -----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ClangDocRepresentation.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; + +namespace clang { +namespace doc { + +void InfoSet::mergeInfoBase(Info *Left, Info *Right) { + if (Left->FullyQualifiedName.empty()) + Left->FullyQualifiedName = Right->FullyQualifiedName; + if (Left->SimpleName.empty()) Left->SimpleName = Right->SimpleName; + if (Left->Namespace.empty()) Left->Namespace = Right->Namespace; + for (const auto &CI : Right->Description) Left->Description.emplace_back(CI); +} + +void InfoSet::mergeSymbolInfoBase(SymbolInfo *Left, SymbolInfo *Right) { + mergeInfoBase(Left, Right); + if (!Left->DefLoc.LineNumber) + Left->DefLoc.LineNumber = Right->DefLoc.LineNumber; + if (Left->DefLoc.Filename.empty()) + Left->DefLoc.Filename = Right->DefLoc.Filename; + for (const auto &L : Right->Loc) Left->Loc.emplace_back(L); +} + +void InfoSet::mergeNamedType(NamedType *Left, NamedType *Right) { + if (Left->Type.empty()) Left->Type = Right->Type; + if (Left->Name.empty()) Left->Name = Right->Name; + if (!Left->Access) Left->Access = Right->Access; + for (const auto &CI : Right->Description) Left->Description.emplace_back(CI); +} + +template <> +void InfoSet::mergeInfo(NamespaceInfo *Left, NamespaceInfo *Right) { + mergeInfoBase(Left, Right); +} + +template <> +void InfoSet::mergeInfo(RecordInfo *Left, RecordInfo *Right) { + mergeSymbolInfoBase(Left, Right); + if (!Left->TagType) Left->TagType = Right->TagType; + if (Left->Members.empty()) + Left->Members = Right->Members; + else if (Left->Members.size() == Right->Members.size()) { + for (unsigned i = 0; i < Left->Members.size(); ++i) + mergeNamedType(&Left->Members[i], &Right->Members[i]); + } + if (Left->Parents.empty()) Left->Parents = Right->Parents; + if (Left->VirtualParents.empty()) Left->VirtualParents = Right->VirtualParents; +} + +template <> +void InfoSet::mergeInfo(EnumInfo *Left, EnumInfo *Right) { + mergeSymbolInfoBase(Left, Right); + if (!Left->Scoped) Left->Scoped = Right->Scoped; + if (Left->Members.empty()) + Left->Members = Right->Members; + else if (Left->Members.size() == Right->Members.size()) { + for (unsigned i = 0; i < Left->Members.size(); ++i) + mergeNamedType(&Left->Members[i], &Right->Members[i]); + } +} + +template <> +void InfoSet::mergeInfo(FunctionInfo *Left, FunctionInfo *Right) { + mergeSymbolInfoBase(Left, Right); + if (Left->MangledName.empty()) Left->MangledName = Right->MangledName; + if (Left->Parent.empty()) Left->Parent = Right->Parent; + if (!Left->Access) Left->Access = Right->Access; + mergeNamedType(&Left->ReturnType, &Right->ReturnType); + if (Left->Params.empty()) + Left->Params = Right->Params; + else if (Left->Params.size() == Right->Params.size()) { + for (unsigned i = 0; i < Left->Params.size(); ++i) + mergeNamedType(&Left->Params[i], &Right->Params[i]); + } +} + +#define FIND_FUNC(X) \ + template <> \ + X* InfoSet::find(StringRef Key) { \ + auto I = InfoIndex.find(Key); \ + return I == InfoIndex.end() ? nullptr : &X##s[I->second]; \ + } + +FIND_FUNC(NamespaceInfo) +FIND_FUNC(RecordInfo) +FIND_FUNC(EnumInfo) +FIND_FUNC(FunctionInfo) + +#undef FIND_FUNC + +template <> +NonDefInfo* InfoSet::find(StringRef Key) { + auto I = NonDefInfos.find(Key); + return I == NonDefInfos.end() ? nullptr : &I->second; +} + +#define INSERT_FUNC(X) \ + template <> \ + void InfoSet::insert(StringRef Key, X &I) { \ + auto R = InfoIndex.try_emplace(Key, X##s.size()); \ + if (auto *NonDef = find(Key)) { \ + mergeSymbolInfoBase(&I, NonDef); \ + removeNonDef(Key); \ + } \ + if (R.second) \ + X##s.push_back(std::move(I)); \ + else { \ + X *E = &X##s[R.first->second]; \ + mergeInfo(E, &I); \ + } \ + } + +INSERT_FUNC(RecordInfo) +INSERT_FUNC(EnumInfo) +INSERT_FUNC(FunctionInfo) + +#undef INSERT_FUNC + +template <> +void InfoSet::insert(StringRef Key, NamespaceInfo &I) { + auto R = InfoIndex.try_emplace(Key, NamespaceInfos.size()); \ + if (R.second) + NamespaceInfos.push_back(std::move(I)); + else { + NamespaceInfo *E = &NamespaceInfos[R.first->second]; + mergeInfo(E, &I); + } +} + +template <> +void InfoSet::insert(StringRef Key, NonDefInfo &I) { + switch (I.Type) { + case NonDefInfo::NAMESPACE: + if (auto *E = find(Key)) { + mergeInfoBase(E, &I); + return; + } + case NonDefInfo::RECORD: + if (auto *E = find(Key)) { + mergeSymbolInfoBase(E, &I); + return; + } + case NonDefInfo::ENUM: + if (auto *E = find(Key)) { + mergeSymbolInfoBase(E, &I); + return; + } + case NonDefInfo::FUNCTION: + if (auto *E = find(Key)) { + mergeSymbolInfoBase(E, &I); + return; + } + } + if (auto *E = find(Key)) { + mergeSymbolInfoBase(E, &I); + return; + } + NonDefInfos[Key] = std::move(I); +} + +void InfoSet::removeNonDef(StringRef Key) { + NonDefInfos.erase(Key); +} + +} // namespace doc +} // namespace clang Index: clang-doc/tool/ClangDocMain.cpp =================================================================== --- clang-doc/tool/ClangDocMain.cpp +++ clang-doc/tool/ClangDocMain.cpp @@ -10,6 +10,7 @@ #include #include "ClangDoc.h" #include "ClangDocBinary.h" +#include "ClangDocReducer.h" #include "clang/AST/AST.h" #include "clang/AST/Decl.h" #include "clang/ASTMatchers/ASTMatchFinder.h" @@ -99,26 +100,31 @@ Exec->get()->execute(newFrontendActionFactory(&Finder), ArgAdjuster); if (Err) errs() << toString(std::move(Err)) << "\n"; + // Reducing phase + errs() << "Reducing infos...\n"; + auto Infos = doc::mergeInfos(Exec->get()->getToolResults()); + if (DumpResult) { - Exec->get()->getToolResults()->forEachResult([&](StringRef Key, - StringRef Value) { - SmallString<128> IRRootPath; - sys::path::native(OutDirectory, IRRootPath); - std::error_code DirectoryStatus = sys::fs::create_directories(IRRootPath); - if (DirectoryStatus != OK) { - errs() << "Unable to create documentation directories.\n"; - return; - } - sys::path::append(IRRootPath, Key + ".bc"); - std::error_code OutErrorInfo; - raw_fd_ostream OS(IRRootPath, OutErrorInfo, sys::fs::F_None); - if (OutErrorInfo != OK) { - errs() << "Error opening documentation file.\n"; - return; - } - OS << Value; - OS.close(); - }); + SmallString<2048> Buffer; + llvm::BitstreamWriter Stream(Buffer); + Writer.writeAllBitstream(*Infos, Stream); + SmallString<128> IRRootPath; + sys::path::native(OutDirectory, IRRootPath); + sys::path::append(IRRootPath, "bc"); + std::error_code DirectoryStatus = sys::fs::create_directories(IRRootPath); + if (DirectoryStatus != OK) { + errs() << "Unable to create documentation directories.\n"; + return 1; + } + sys::path::append(IRRootPath, "docs.bc"); + std::error_code OutErrorInfo; + raw_fd_ostream OS(IRRootPath, OutErrorInfo, sys::fs::F_None); + if (OutErrorInfo != OK) { + errs() << "Error opening documentation file.\n"; + return 1; + } + OS << Buffer; + OS.close(); } return 0; Index: test/clang-doc/mapper-class.cpp =================================================================== --- test/clang-doc/mapper-class.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// RUN: rm -rf %t -// RUN: mkdir %t -// RUN: echo "" > %t/compile_flags.txt -// RUN: cp "%s" "%t/test.cpp" -// RUN: clang-doc --dump --omit-filenames -doxygen -p %t %t/test.cpp -docs=%t/docs -// RUN: llvm-bcanalyzer %t/docs/E.bc --dump | FileCheck %s - -class E {}; -// CHECK: -// CHECK: - // CHECK: blob data = 'E' - // CHECK: blob data = 'E' - // CHECK: -// CHECK: Index: test/clang-doc/mapper-enum.cpp =================================================================== --- test/clang-doc/mapper-enum.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// RUN: rm -rf %t -// RUN: mkdir %t -// RUN: echo "" > %t/compile_flags.txt -// RUN: cp "%s" "%t/test.cpp" -// RUN: clang-doc --dump --omit-filenames -doxygen -p %t %t/test.cpp -docs=%t/docs -// RUN: llvm-bcanalyzer %t/docs/B.bc --dump | FileCheck %s - -enum B { X, Y }; -// CHECK: -// CHECK: - // CHECK: blob data = 'B' - // CHECK: blob data = 'B' - // CHECK: - // CHECK: - // CHECK: blob data = 'X' - // CHECK: - // CHECK: - // CHECK: - // CHECK: - // CHECK: blob data = 'Y' - // CHECK: - // CHECK: -// CHECK: Index: test/clang-doc/mapper-namespace.cpp =================================================================== --- test/clang-doc/mapper-namespace.cpp +++ test/clang-doc/mapper-namespace.cpp @@ -3,11 +3,73 @@ // RUN: echo "" > %t/compile_flags.txt // RUN: cp "%s" "%t/test.cpp" // RUN: clang-doc --dump --omit-filenames -doxygen -p %t %t/test.cpp -docs=%t/docs -// RUN: llvm-bcanalyzer %t/docs/A.bc --dump | FileCheck %s +// RUN llvm-bcanalyzer %t/docs/docs.bc --dump | FileCheck %s + +namespace A { + +void f(); +void f() {}; + +} // A + +namespace A { +namespace B { + +enum E { X }; + +E func(int i) { + return X; +} + +} +} -namespace A {} // CHECK: // CHECK: // CHECK: blob data = 'A' // CHECK: blob data = 'A' // CHECK: +// CHECK: + // CHECK: blob data = 'A::B' + // CHECK: blob data = 'B' + // CHECK: blob data = 'A' +// CHECK: +// CHECK: + // CHECK: blob data = 'A::f' + // CHECK: blob data = 'f' + // CHECK: blob data = 'A' + // CHECK: blob data = '_ZN1A1fEv' + // CHECK: + // CHECK: + // CHECK: blob data = 'void' + // CHECK: + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'A::B::func' + // CHECK: blob data = 'func' + // CHECK: blob data = 'A::B' + // CHECK: blob data = '_ZN1A1B4funcEi' + // CHECK: + // CHECK: + // CHECK: blob data = 'enum A::B::E' + // CHECK: + // CHECK: + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: blob data = 'i' + // CHECK: + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'A::B::E' + // CHECK: blob data = 'E' + // CHECK: blob data = 'A::B' + // CHECK: + // CHECK: + // CHECK: + // CHECK: blob data = 'A::B::X' + // CHECK: + // CHECK: +// CHECK: Index: test/clang-doc/mapper-record.cpp =================================================================== --- /dev/null +++ test/clang-doc/mapper-record.cpp @@ -0,0 +1,155 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: echo "" > %t/compile_flags.txt +// RUN: cp "%s" "%t/test.cpp" +// RUN: clang-doc --dump --omit-filenames -doxygen -p %t %t/test.cpp -docs=%t/docs +// RUN llvm-bcanalyzer %t/docs/docs.bc --dump | FileCheck %s + +union A { int X; int Y; }; + +enum B { X, Y }; + +struct C { int i; }; + +class D {}; + +class E { +public: + E() {} + ~E() {} + +protected: + void ProtectedMethod(); +}; + +void E::ProtectedMethod() {} + +class F : virtual private D, public E {}; + +// CHECK: +// CHECK: + // CHECK: blob data = 'E::E' + // CHECK: blob data = 'E' + // CHECK: blob data = 'E' + // CHECK: blob data = '_ZN1EC1Ev' + // CHECK: blob data = 'E' + // CHECK: + // CHECK: + // CHECK: blob data = 'void' + // CHECK: + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'E::~E' + // CHECK: blob data = '~E' + // CHECK: blob data = 'E' + // CHECK: blob data = '_ZN1ED1Ev' + // CHECK: blob data = 'E' + // CHECK: + // CHECK: + // CHECK: blob data = 'void' + // CHECK: + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'E::ProtectedMethod' + // CHECK: blob data = 'ProtectedMethod' + // CHECK: blob data = 'E' + // CHECK: blob data = '_ZN1E15ProtectedMethodEv' + // CHECK: blob data = 'E' + // CHECK: + // CHECK: + // CHECK: blob data = 'void' + // CHECK: + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'A' + // CHECK: blob data = 'A' + // CHECK: + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: blob data = 'A::X' + // CHECK: + // CHECK: + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: blob data = 'A::Y' + // CHECK: + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'C' + // CHECK: blob data = 'C' + // CHECK: + // CHECK: + // CHECK: + // CHECK: blob data = 'int' + // CHECK: blob data = 'C::i' + // CHECK: + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'D' + // CHECK: blob data = 'D' + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'E' + // CHECK: blob data = 'E' + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'F' + // CHECK: blob data = 'F' + // CHECK: + // CHECK: blob data = 'class E' + // CHECK: blob data = 'class D' +// CHECK: +// CHECK: + // CHECK: blob data = 'B' + // CHECK: blob data = 'B' + // CHECK: + // CHECK: + // CHECK: + // CHECK: blob data = 'X' + // CHECK: + // CHECK: + // CHECK: + // CHECK: + // CHECK: blob data = 'Y' + // CHECK: + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'F::F' + // CHECK: blob data = 'F' + // CHECK: blob data = 'F' + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'A::A' + // CHECK: blob data = 'A' + // CHECK: blob data = 'A' + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'C::C' + // CHECK: blob data = 'C' + // CHECK: blob data = 'C' + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'D::D' + // CHECK: blob data = 'D' + // CHECK: blob data = 'D' + // CHECK: +// CHECK: +// CHECK: + // CHECK: blob data = 'E::E' + // CHECK: blob data = 'E' + // CHECK: blob data = 'E' + // CHECK: +// CHECK: Index: test/clang-doc/mapper-struct.cpp =================================================================== --- test/clang-doc/mapper-struct.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// RUN: rm -rf %t -// RUN: mkdir %t -// RUN: echo "" > %t/compile_flags.txt -// RUN: cp "%s" "%t/test.cpp" -// RUN: clang-doc --dump --omit-filenames -doxygen -p %t %t/test.cpp -docs=%t/docs -// RUN: llvm-bcanalyzer %t/docs/C.bc --dump | FileCheck %s - -struct C { int i; }; -// CHECK: -// CHECK: - // CHECK: blob data = 'C' - // CHECK: blob data = 'C' - // CHECK: - // CHECK: - // CHECK: blob data = 'int' - // CHECK: blob data = 'C::i' - // CHECK: - // CHECK: -// CHECK: Index: test/clang-doc/mapper-union.cpp =================================================================== --- test/clang-doc/mapper-union.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// RUN: rm -rf %t -// RUN: mkdir %t -// RUN: echo "" > %t/compile_flags.txt -// RUN: cp "%s" "%t/test.cpp" -// RUN: clang-doc --dump --omit-filenames -doxygen -p %t %t/test.cpp -docs=%t/docs -// RUN: llvm-bcanalyzer %t/docs/A.bc --dump | FileCheck %s - -union A { int X; int Y; }; -// CHECK: -// CHECK: - // CHECK: blob data = 'A' - // CHECK: blob data = 'A' - // CHECK: - // CHECK: - // CHECK: - // CHECK: blob data = 'int' - // CHECK: blob data = 'A::X' - // CHECK: - // CHECK: - // CHECK: - // CHECK: - // CHECK: blob data = 'int' - // CHECK: blob data = 'A::Y' - // CHECK: - // CHECK: -// CHECK: