| //===--- SerializeDoc.cpp - Read and write swiftdoc files -----------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "DocFormat.h" |
| #include "Serialization.h" |
| #include "SourceInfoFormat.h" |
| #include "swift/AST/ASTContext.h" |
| #include "swift/AST/ASTWalker.h" |
| #include "swift/AST/DiagnosticsCommon.h" |
| #include "swift/AST/Module.h" |
| #include "swift/AST/ParameterList.h" |
| #include "swift/AST/SourceFile.h" |
| #include "swift/AST/USRGeneration.h" |
| #include "swift/Basic/Defer.h" |
| #include "swift/Basic/SourceManager.h" |
| #include "llvm/ADT/StringSet.h" |
| #include "llvm/Support/DJB.h" |
| #include "llvm/Support/EndianStream.h" |
| #include "llvm/Support/OnDiskHashTable.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/YAMLParser.h" |
| |
| #include <vector> |
| |
| using namespace swift; |
| using namespace swift::serialization; |
| using namespace llvm::support; |
| using swift::version::Version; |
| using llvm::BCBlockRAII; |
| |
| using FileNameToGroupNameMap = llvm::StringMap<std::string>; |
| |
| namespace { |
| class YamlGroupInputParser { |
| ASTContext &Ctx; |
| StringRef RecordPath; |
| static constexpr const char * const Separator = "/"; |
| |
| bool parseRoot(FileNameToGroupNameMap &Map, llvm::yaml::Node *Root, |
| StringRef ParentName) { |
| auto *MapNode = dyn_cast<llvm::yaml::MappingNode>(Root); |
| if (!MapNode) { |
| return true; |
| } |
| for (auto &Pair : *MapNode) { |
| auto *Key = dyn_cast_or_null<llvm::yaml::ScalarNode>(Pair.getKey()); |
| auto *Value = dyn_cast_or_null<llvm::yaml::SequenceNode>(Pair.getValue()); |
| |
| if (!Key || !Value) { |
| return true; |
| } |
| llvm::SmallString<16> GroupNameStorage; |
| StringRef GroupName = Key->getValue(GroupNameStorage); |
| std::string CombinedName; |
| if (!ParentName.empty()) { |
| CombinedName = (llvm::Twine(ParentName) + Separator + GroupName).str(); |
| } else { |
| CombinedName = GroupName; |
| } |
| |
| for (llvm::yaml::Node &Entry : *Value) { |
| if (auto *FileEntry= dyn_cast<llvm::yaml::ScalarNode>(&Entry)) { |
| llvm::SmallString<16> FileNameStorage; |
| StringRef FileName = FileEntry->getValue(FileNameStorage); |
| llvm::SmallString<32> GroupNameAndFileName; |
| GroupNameAndFileName.append(CombinedName); |
| GroupNameAndFileName.append(Separator); |
| GroupNameAndFileName.append(llvm::sys::path::stem(FileName)); |
| Map[FileName] = GroupNameAndFileName.str(); |
| } else if (Entry.getType() == llvm::yaml::Node::NodeKind::NK_Mapping) { |
| if (parseRoot(Map, &Entry, CombinedName)) |
| return true; |
| } else |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| FileNameToGroupNameMap diagnoseGroupInfoFile(bool FileMissing = false) { |
| Ctx.Diags.diagnose(SourceLoc(), |
| FileMissing ? diag::cannot_find_group_info_file: |
| diag::cannot_parse_group_info_file, RecordPath); |
| return {}; |
| } |
| |
| public: |
| YamlGroupInputParser(ASTContext &Ctx, StringRef RecordPath): |
| Ctx(Ctx), RecordPath(RecordPath) {} |
| |
| /// Parse the Yaml file that contains the group information. |
| /// |
| /// If the record path is empty, returns an empty map. |
| FileNameToGroupNameMap parse() { |
| if (RecordPath.empty()) |
| return {}; |
| |
| auto Buffer = llvm::MemoryBuffer::getFile(RecordPath); |
| if (!Buffer) { |
| return diagnoseGroupInfoFile(/*Missing File*/true); |
| } |
| llvm::SourceMgr SM; |
| llvm::yaml::Stream YAMLStream(Buffer.get()->getMemBufferRef(), SM); |
| llvm::yaml::document_iterator I = YAMLStream.begin(); |
| if (I == YAMLStream.end()) { |
| // Cannot parse correctly. |
| return diagnoseGroupInfoFile(); |
| } |
| llvm::yaml::Node *Root = I->getRoot(); |
| if (!Root) { |
| // Cannot parse correctly. |
| return diagnoseGroupInfoFile(); |
| } |
| |
| // The format is a map of ("group0" : ["file1", "file2"]), meaning all |
| // symbols from file1 and file2 belong to "group0". |
| auto *Map = dyn_cast<llvm::yaml::MappingNode>(Root); |
| if (!Map) { |
| return diagnoseGroupInfoFile(); |
| } |
| FileNameToGroupNameMap Result; |
| if (parseRoot(Result, Root, "")) |
| return diagnoseGroupInfoFile(); |
| |
| // Return the parsed map. |
| return Result; |
| } |
| }; |
| |
| class DeclGroupNameContext { |
| ASTContext &Ctx; |
| FileNameToGroupNameMap FileToGroupMap; |
| llvm::MapVector<StringRef, unsigned> Map; |
| std::vector<StringRef> ViewBuffer; |
| |
| public: |
| DeclGroupNameContext(StringRef RecordPath, ASTContext &Ctx) : |
| Ctx(Ctx), FileToGroupMap(YamlGroupInputParser(Ctx, RecordPath).parse()) {} |
| |
| uint32_t getGroupSequence(const Decl *VD) { |
| if (FileToGroupMap.empty()) |
| return 0; |
| |
| // We need the file path, so there has to be a location. |
| if (VD->getLoc().isInvalid()) |
| return 0; |
| StringRef FullPath = |
| VD->getDeclContext()->getParentSourceFile()->getFilename(); |
| if (FullPath.empty()) |
| return 0; |
| StringRef FileName = llvm::sys::path::filename(FullPath); |
| auto Found = FileToGroupMap.find(FileName); |
| if (Found == FileToGroupMap.end()) { |
| Ctx.Diags.diagnose(SourceLoc(), diag::error_no_group_info, FileName); |
| return 0; |
| } |
| |
| StringRef GroupName = Found->second; |
| return Map.insert(std::make_pair(GroupName, Map.size()+1)).first->second; |
| } |
| |
| ArrayRef<StringRef> getOrderedGroupNames() { |
| ViewBuffer.clear(); |
| ViewBuffer.push_back(""); // 0 is always outside of any group. |
| for (auto It = Map.begin(); It != Map.end(); ++ It) { |
| ViewBuffer.push_back(It->first); |
| } |
| return llvm::makeArrayRef(ViewBuffer); |
| } |
| |
| bool isEnable() { |
| return !FileToGroupMap.empty(); |
| } |
| }; |
| |
| struct DeclCommentTableData { |
| StringRef Brief; |
| RawComment Raw; |
| uint32_t Group; |
| uint32_t Order; |
| }; |
| |
| class DeclCommentTableInfo { |
| public: |
| using key_type = StringRef; |
| using key_type_ref = key_type; |
| using data_type = DeclCommentTableData; |
| using data_type_ref = const data_type &; |
| using hash_value_type = uint32_t; |
| using offset_type = unsigned; |
| |
| hash_value_type ComputeHash(key_type_ref key) { |
| assert(!key.empty()); |
| return llvm::djbHash(key, SWIFTDOC_HASH_SEED_5_1); |
| } |
| |
| std::pair<unsigned, unsigned> |
| EmitKeyDataLength(raw_ostream &out, key_type_ref key, data_type_ref data) { |
| uint32_t keyLength = key.size(); |
| const unsigned numLen = 4; |
| |
| // Data consists of brief comment length and brief comment text, |
| uint32_t dataLength = numLen + data.Brief.size(); |
| // number of raw comments, |
| dataLength += numLen; |
| // for each raw comment: column number of the first line, length of each |
| // raw comment and its text. |
| for (auto C : data.Raw.Comments) |
| dataLength += numLen + numLen + C.RawText.size(); |
| |
| // Group Id. |
| dataLength += numLen; |
| |
| // Source order. |
| dataLength += numLen; |
| endian::Writer writer(out, little); |
| writer.write<uint32_t>(keyLength); |
| writer.write<uint32_t>(dataLength); |
| return { keyLength, dataLength }; |
| } |
| |
| void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { |
| out << key; |
| } |
| |
| void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, |
| unsigned len) { |
| endian::Writer writer(out, little); |
| writer.write<uint32_t>(data.Brief.size()); |
| out << data.Brief; |
| writer.write<uint32_t>(data.Raw.Comments.size()); |
| for (auto C : data.Raw.Comments) { |
| writer.write<uint32_t>(C.StartColumn); |
| writer.write<uint32_t>(C.RawText.size()); |
| out << C.RawText; |
| } |
| writer.write<uint32_t>(data.Group); |
| writer.write<uint32_t>(data.Order); |
| } |
| }; |
| |
| class DocSerializer : public SerializerBase { |
| public: |
| using SerializerBase::SerializerBase; |
| using SerializerBase::writeToStream; |
| |
| using SerializerBase::Out; |
| using SerializerBase::M; |
| using SerializerBase::SF; |
| |
| /// Writes the BLOCKINFO block for the module documentation file. |
| void writeDocBlockInfoBlock() { |
| BCBlockRAII restoreBlock(Out, llvm::bitc::BLOCKINFO_BLOCK_ID, 2); |
| |
| SmallVector<unsigned char, 64> nameBuffer; |
| #define BLOCK(X) emitBlockID(X ## _ID, #X, nameBuffer) |
| #define BLOCK_RECORD(K, X) emitRecordID(K::X, #X, nameBuffer) |
| |
| BLOCK(MODULE_DOC_BLOCK); |
| |
| BLOCK(CONTROL_BLOCK); |
| BLOCK_RECORD(control_block, METADATA); |
| BLOCK_RECORD(control_block, MODULE_NAME); |
| BLOCK_RECORD(control_block, TARGET); |
| |
| BLOCK(COMMENT_BLOCK); |
| BLOCK_RECORD(comment_block, DECL_COMMENTS); |
| BLOCK_RECORD(comment_block, GROUP_NAMES); |
| |
| #undef BLOCK |
| #undef BLOCK_RECORD |
| } |
| |
| /// Writes the Swift doc module file header and name. |
| void writeDocHeader(); |
| }; |
| |
| } // end anonymous namespace |
| |
| static void writeGroupNames(const comment_block::GroupNamesLayout &GroupNames, |
| ArrayRef<StringRef> Names) { |
| llvm::SmallString<32> Blob; |
| llvm::raw_svector_ostream BlobStream(Blob); |
| endian::Writer Writer(BlobStream, little); |
| Writer.write<uint32_t>(Names.size()); |
| for (auto N : Names) { |
| Writer.write<uint32_t>(N.size()); |
| BlobStream << N; |
| } |
| SmallVector<uint64_t, 8> Scratch; |
| GroupNames.emit(Scratch, BlobStream.str()); |
| } |
| |
| static bool hasDoubleUnderscore(Decl *D) { |
| // Exclude decls with double-underscored names, either in arguments or |
| // base names. |
| static StringRef Prefix = "__"; |
| |
| if (auto AFD = dyn_cast<AbstractFunctionDecl>(D)) { |
| // If it's a function with a parameter with leading double underscore, |
| // it's a private function. |
| if (AFD->getParameters()->hasInternalParameter(Prefix)) |
| return true; |
| } |
| |
| if (auto SubscriptD = dyn_cast<SubscriptDecl>(D)) { |
| if (SubscriptD->getIndices()->hasInternalParameter(Prefix)) |
| return true; |
| } |
| if (auto *VD = dyn_cast<ValueDecl>(D)) { |
| auto Name = VD->getBaseName(); |
| if (!Name.isSpecial() && |
| Name.getIdentifier().str().startswith(Prefix)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static bool shouldIncludeDecl(Decl *D, bool ExcludeDoubleUnderscore) { |
| if (auto *VD = dyn_cast<ValueDecl>(D)) { |
| // Skip the decl if it's not visible to clients. The use of |
| // getEffectiveAccess is unusual here; we want to take the testability |
| // state into account and emit documentation if and only if they are |
| // visible to clients (which means public ordinarily, but |
| // public+internal when testing enabled). |
| if (VD->getEffectiveAccess() < swift::AccessLevel::Public) |
| return false; |
| } |
| if (auto *ED = dyn_cast<ExtensionDecl>(D)) { |
| return shouldIncludeDecl(ED->getExtendedNominal(), ExcludeDoubleUnderscore); |
| } |
| if (ExcludeDoubleUnderscore && hasDoubleUnderscore(D)) { |
| return false; |
| } |
| return true; |
| } |
| |
| static void writeDeclCommentTable( |
| const comment_block::DeclCommentListLayout &DeclCommentList, |
| const SourceFile *SF, const ModuleDecl *M, |
| DeclGroupNameContext &GroupContext) { |
| |
| struct DeclCommentTableWriter : public ASTWalker { |
| llvm::BumpPtrAllocator Arena; |
| llvm::SmallString<512> USRBuffer; |
| llvm::OnDiskChainedHashTableGenerator<DeclCommentTableInfo> generator; |
| DeclGroupNameContext &GroupContext; |
| unsigned SourceOrder; |
| |
| DeclCommentTableWriter(DeclGroupNameContext &GroupContext): |
| GroupContext(GroupContext) {} |
| |
| void resetSourceOrder() { |
| SourceOrder = 0; |
| } |
| |
| StringRef copyString(StringRef String) { |
| char *Mem = static_cast<char *>(Arena.Allocate(String.size(), 1)); |
| std::copy(String.begin(), String.end(), Mem); |
| return StringRef(Mem, String.size()); |
| } |
| |
| bool shouldSerializeDoc(Decl *D) { |
| // When building the stdlib we intend to serialize unusual comments. |
| // This situation is represented by GroupContext.isEnable(). In that |
| // case, we perform more serialization to keep track of source order. |
| if (GroupContext.isEnable()) |
| return true; |
| |
| // Skip the decl if it cannot have a comment. |
| if (!D->canHaveComment()) |
| return false; |
| |
| // Skip the decl if it does not have a comment. |
| if (D->getRawComment().Comments.empty()) |
| return false; |
| return true; |
| } |
| |
| void writeDocForExtensionDecl(ExtensionDecl *ED) { |
| // Compute USR. |
| { |
| USRBuffer.clear(); |
| llvm::raw_svector_ostream OS(USRBuffer); |
| if (ide::printExtensionUSR(ED, OS)) |
| return; |
| } |
| generator.insert(copyString(USRBuffer.str()), |
| { ED->getBriefComment(), ED->getRawComment(), |
| GroupContext.getGroupSequence(ED), |
| SourceOrder++ }); |
| } |
| |
| bool walkToDeclPre(Decl *D) override { |
| if (!shouldIncludeDecl(D, /*ExcludeDoubleUnderscore*/true)) |
| return false; |
| if (!shouldSerializeDoc(D)) |
| return true; |
| if (auto *ED = dyn_cast<ExtensionDecl>(D)) { |
| writeDocForExtensionDecl(ED); |
| return true; |
| } |
| |
| auto *VD = dyn_cast<ValueDecl>(D); |
| if (!VD) |
| return true; |
| |
| // Compute USR. |
| { |
| USRBuffer.clear(); |
| llvm::raw_svector_ostream OS(USRBuffer); |
| if (ide::printValueDeclUSR(VD, OS)) |
| return true; |
| } |
| |
| generator.insert(copyString(USRBuffer.str()), |
| { VD->getBriefComment(), D->getRawComment(), |
| GroupContext.getGroupSequence(VD), |
| SourceOrder++ }); |
| return true; |
| } |
| |
| std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override { |
| return { false, S }; |
| } |
| |
| std::pair<bool, Expr *> walkToExprPre(Expr *E) override { |
| return { false, E }; |
| } |
| |
| bool walkToTypeLocPre(TypeLoc &TL) override { return false; } |
| bool walkToTypeReprPre(TypeRepr *T) override { return false; } |
| bool walkToParameterListPre(ParameterList *PL) override { return false; } |
| }; |
| |
| DeclCommentTableWriter Writer(GroupContext); |
| |
| ArrayRef<const FileUnit *> files; |
| SmallVector<const FileUnit *, 1> Scratch; |
| if (SF) { |
| Scratch.push_back(SF); |
| files = llvm::makeArrayRef(Scratch); |
| } else { |
| files = M->getFiles(); |
| } |
| for (auto nextFile : files) { |
| Writer.resetSourceOrder(); |
| const_cast<FileUnit *>(nextFile)->walk(Writer); |
| } |
| SmallVector<uint64_t, 8> scratch; |
| llvm::SmallString<32> hashTableBlob; |
| uint32_t tableOffset; |
| { |
| llvm::raw_svector_ostream blobStream(hashTableBlob); |
| // Make sure that no bucket is at offset 0 |
| endian::write<uint32_t>(blobStream, 0, little); |
| tableOffset = Writer.generator.Emit(blobStream); |
| } |
| |
| DeclCommentList.emit(scratch, tableOffset, hashTableBlob); |
| } |
| |
| void DocSerializer::writeDocHeader() { |
| { |
| BCBlockRAII restoreBlock(Out, CONTROL_BLOCK_ID, 3); |
| control_block::ModuleNameLayout ModuleName(Out); |
| control_block::MetadataLayout Metadata(Out); |
| control_block::TargetLayout Target(Out); |
| |
| auto& LangOpts = M->getASTContext().LangOpts; |
| Metadata.emit(ScratchRecord, SWIFTDOC_VERSION_MAJOR, SWIFTDOC_VERSION_MINOR, |
| /*short version string length*/0, /*compatibility length*/0, |
| version::getSwiftFullVersion( |
| LangOpts.EffectiveLanguageVersion)); |
| |
| ModuleName.emit(ScratchRecord, M->getName().str()); |
| Target.emit(ScratchRecord, LangOpts.Target.str()); |
| } |
| } |
| |
| void serialization::writeDocToStream(raw_ostream &os, ModuleOrSourceFile DC, |
| StringRef GroupInfoPath) { |
| DocSerializer S{SWIFTDOC_SIGNATURE, DC}; |
| // FIXME: This is only really needed for debugging. We don't actually use it. |
| S.writeDocBlockInfoBlock(); |
| |
| { |
| BCBlockRAII moduleBlock(S.Out, MODULE_DOC_BLOCK_ID, 2); |
| S.writeDocHeader(); |
| { |
| BCBlockRAII restoreBlock(S.Out, COMMENT_BLOCK_ID, 4); |
| DeclGroupNameContext GroupContext(GroupInfoPath, S.M->getASTContext()); |
| comment_block::DeclCommentListLayout DeclCommentList(S.Out); |
| writeDeclCommentTable(DeclCommentList, S.SF, S.M, GroupContext); |
| comment_block::GroupNamesLayout GroupNames(S.Out); |
| |
| // FIXME: Multi-file compilation may cause group id collision. |
| writeGroupNames(GroupNames, GroupContext.getOrderedGroupNames()); |
| } |
| } |
| |
| S.writeToStream(os); |
| } |
| namespace { |
| struct DeclLocationsTableData { |
| uint32_t SourceFileOffset; |
| LineColumn Loc; |
| LineColumn StartLoc; |
| LineColumn EndLoc; |
| }; |
| |
| class USRTableInfo { |
| public: |
| using key_type = StringRef; |
| using key_type_ref = key_type; |
| using data_type = uint32_t; |
| using data_type_ref = const data_type &; |
| using hash_value_type = uint32_t; |
| using offset_type = unsigned; |
| |
| hash_value_type ComputeHash(key_type_ref key) { |
| assert(!key.empty()); |
| return llvm::djbHash(key, SWIFTSOURCEINFO_HASH_SEED); |
| } |
| |
| std::pair<unsigned, unsigned> |
| EmitKeyDataLength(raw_ostream &out, key_type_ref key, data_type_ref data) { |
| const unsigned numLen = 4; |
| uint32_t keyLength = key.size(); |
| uint32_t dataLength = numLen; |
| endian::Writer writer(out, little); |
| writer.write<uint32_t>(keyLength); |
| return { keyLength, dataLength }; |
| } |
| |
| void EmitKey(raw_ostream &out, key_type_ref key, unsigned len) { |
| out << key; |
| } |
| |
| void EmitData(raw_ostream &out, key_type_ref key, data_type_ref data, |
| unsigned len) { |
| endian::Writer writer(out, little); |
| writer.write<uint32_t>(data); |
| } |
| }; |
| |
| class DeclUSRsTableWriter { |
| llvm::StringSet<> USRs; |
| llvm::OnDiskChainedHashTableGenerator<USRTableInfo> generator; |
| public: |
| uint32_t peekNextId() const { return USRs.size(); } |
| Optional<uint32_t> getNewUSRId(StringRef USR) { |
| // Attempt to insert the USR into the StringSet. |
| auto It = USRs.insert(USR); |
| // If the USR exists in the StringSet, return None. |
| if (!It.second) |
| return None; |
| auto Id = USRs.size() - 1; |
| // We have to insert the USR from the StringSet because it's where the |
| // memory is owned. |
| generator.insert(It.first->getKey(), Id); |
| return Id; |
| } |
| void emitUSRsRecord(llvm::BitstreamWriter &out) { |
| decl_locs_block::DeclUSRSLayout USRsList(out); |
| SmallVector<uint64_t, 8> scratch; |
| llvm::SmallString<32> hashTableBlob; |
| uint32_t tableOffset; |
| { |
| llvm::raw_svector_ostream blobStream(hashTableBlob); |
| // Make sure that no bucket is at offset 0 |
| endian::write<uint32_t>(blobStream, 0, little); |
| tableOffset = generator.Emit(blobStream); |
| } |
| USRsList.emit(scratch, tableOffset, hashTableBlob); |
| } |
| }; |
| |
| class StringWriter { |
| llvm::StringMap<uint32_t> IndexMap; |
| llvm::SmallString<1024> Buffer; |
| public: |
| uint32_t getTextOffset(StringRef Text) { |
| if (IndexMap.find(Text) == IndexMap.end()) { |
| IndexMap.insert({Text, Buffer.size()}); |
| Buffer.append(Text); |
| Buffer.push_back('\0'); |
| } |
| return IndexMap[Text]; |
| } |
| |
| void emitSourceFilesRecord(llvm::BitstreamWriter &Out) { |
| decl_locs_block::TextDataLayout TextBlob(Out); |
| SmallVector<uint64_t, 8> scratch; |
| TextBlob.emit(scratch, Buffer); |
| } |
| }; |
| |
| struct BasicDeclLocsTableWriter : public ASTWalker { |
| llvm::SmallString<1024> Buffer; |
| DeclUSRsTableWriter &USRWriter; |
| StringWriter &FWriter; |
| BasicDeclLocsTableWriter(DeclUSRsTableWriter &USRWriter, |
| StringWriter &FWriter): USRWriter(USRWriter), |
| FWriter(FWriter) {} |
| |
| std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override { return { false, S };} |
| std::pair<bool, Expr *> walkToExprPre(Expr *E) override { return { false, E };} |
| bool walkToTypeLocPre(TypeLoc &TL) override { return false; } |
| bool walkToTypeReprPre(TypeRepr *T) override { return false; } |
| bool walkToParameterListPre(ParameterList *PL) override { return false; } |
| |
| void appendToBuffer(DeclLocationsTableData data) { |
| llvm::raw_svector_ostream out(Buffer); |
| endian::Writer writer(out, little); |
| writer.write<uint32_t>(data.SourceFileOffset); |
| #define WRITE_LINE_COLUMN(X) \ |
| writer.write<uint32_t>(data.X.Line); \ |
| writer.write<uint32_t>(data.X.Column); |
| WRITE_LINE_COLUMN(Loc) |
| WRITE_LINE_COLUMN(StartLoc); |
| WRITE_LINE_COLUMN(EndLoc); |
| #undef WRITE_LINE_COLUMN |
| } |
| |
| Optional<uint32_t> calculateNewUSRId(Decl *D) { |
| llvm::SmallString<512> Buffer; |
| llvm::raw_svector_ostream OS(Buffer); |
| if (ide::printDeclUSR(D, OS)) |
| return None; |
| return USRWriter.getNewUSRId(OS.str()); |
| } |
| |
| LineColumn getLineColumn(SourceManager &SM, SourceLoc Loc) { |
| LineColumn Result; |
| if (Loc.isValid()) { |
| auto LC = SM.getLineAndColumn(Loc); |
| Result.Line = LC.first; |
| Result.Column = LC.second; |
| } |
| return Result; |
| } |
| |
| Optional<DeclLocationsTableData> getLocData(Decl *D) { |
| auto *File = D->getDeclContext()->getModuleScopeContext(); |
| auto Locs = cast<FileUnit>(File)->getBasicLocsForDecl(D); |
| if (!Locs.hasValue()) |
| return None; |
| DeclLocationsTableData Result; |
| llvm::SmallString<128> AbsolutePath = Locs->SourceFilePath; |
| llvm::sys::fs::make_absolute(AbsolutePath); |
| Result.SourceFileOffset = FWriter.getTextOffset(AbsolutePath.str()); |
| #define COPY_LINE_COLUMN(X) \ |
| Result.X.Line = Locs->X.Line; \ |
| Result.X.Column = Locs->X.Column; |
| COPY_LINE_COLUMN(Loc) |
| COPY_LINE_COLUMN(StartLoc) |
| COPY_LINE_COLUMN(EndLoc) |
| #undef COPY_LINE_COLUMN |
| return Result; |
| } |
| |
| bool shouldSerializeSourceLoc(Decl *D) { |
| if (D->isImplicit()) |
| return false; |
| return true; |
| } |
| |
| bool walkToDeclPre(Decl *D) override { |
| SWIFT_DEFER { |
| assert(USRWriter.peekNextId() * sizeof(DeclLocationsTableData) |
| == Buffer.size() && |
| "USR Id has a one-to-one mapping with DeclLocationsTableData"); |
| }; |
| // .swiftdoc doesn't include comments for double underscored symbols, but |
| // for .swiftsourceinfo, having the source location for these symbols isn't |
| // a concern becuase these symbols are in .swiftinterface anyway. |
| if (!shouldIncludeDecl(D, /*ExcludeDoubleUnderscore*/false)) |
| return false; |
| if (!shouldSerializeSourceLoc(D)) |
| return true; |
| // If we cannot get loc data for D, don't proceed. |
| auto LocData = getLocData(D); |
| if (!LocData.hasValue()) |
| return true; |
| // If we have handled this USR before, don't proceed. |
| auto USR = calculateNewUSRId(D); |
| if (!USR.hasValue()) |
| return true; |
| appendToBuffer(*LocData); |
| return true; |
| } |
| }; |
| |
| static void emitBasicLocsRecord(llvm::BitstreamWriter &Out, |
| ModuleOrSourceFile MSF, |
| DeclUSRsTableWriter &USRWriter, |
| StringWriter &FWriter) { |
| assert(MSF); |
| const decl_locs_block::BasicDeclLocsLayout DeclLocsList(Out); |
| BasicDeclLocsTableWriter Writer(USRWriter, FWriter); |
| if (auto *SF = MSF.dyn_cast<SourceFile*>()) { |
| SF->walk(Writer); |
| } else { |
| MSF.get<ModuleDecl*>()->walk(Writer); |
| } |
| |
| SmallVector<uint64_t, 8> scratch; |
| DeclLocsList.emit(scratch, Writer.Buffer); |
| } |
| |
| class SourceInfoSerializer : public SerializerBase { |
| public: |
| using SerializerBase::SerializerBase; |
| using SerializerBase::writeToStream; |
| |
| using SerializerBase::Out; |
| using SerializerBase::M; |
| using SerializerBase::SF; |
| /// Writes the BLOCKINFO block for the module sourceinfo file. |
| void writeSourceInfoBlockInfoBlock() { |
| BCBlockRAII restoreBlock(Out, llvm::bitc::BLOCKINFO_BLOCK_ID, 2); |
| |
| SmallVector<unsigned char, 64> nameBuffer; |
| #define BLOCK(X) emitBlockID(X ## _ID, #X, nameBuffer) |
| #define BLOCK_RECORD(K, X) emitRecordID(K::X, #X, nameBuffer) |
| |
| BLOCK(MODULE_SOURCEINFO_BLOCK); |
| |
| BLOCK(CONTROL_BLOCK); |
| BLOCK_RECORD(control_block, METADATA); |
| BLOCK_RECORD(control_block, MODULE_NAME); |
| BLOCK_RECORD(control_block, TARGET); |
| |
| BLOCK(DECL_LOCS_BLOCK); |
| BLOCK_RECORD(decl_locs_block, BASIC_DECL_LOCS); |
| BLOCK_RECORD(decl_locs_block, DECL_USRS); |
| BLOCK_RECORD(decl_locs_block, TEXT_DATA); |
| |
| #undef BLOCK |
| #undef BLOCK_RECORD |
| } |
| /// Writes the Swift sourceinfo file header and name. |
| void writeSourceInfoHeader() { |
| { |
| BCBlockRAII restoreBlock(Out, CONTROL_BLOCK_ID, 3); |
| control_block::ModuleNameLayout ModuleName(Out); |
| control_block::MetadataLayout Metadata(Out); |
| control_block::TargetLayout Target(Out); |
| |
| auto& LangOpts = M->getASTContext().LangOpts; |
| Metadata.emit(ScratchRecord, SWIFTSOURCEINFO_VERSION_MAJOR, |
| SWIFTSOURCEINFO_VERSION_MINOR, |
| /*short version string length*/0, /*compatibility length*/0, |
| version::getSwiftFullVersion(LangOpts.EffectiveLanguageVersion)); |
| |
| ModuleName.emit(ScratchRecord, M->getName().str()); |
| Target.emit(ScratchRecord, LangOpts.Target.str()); |
| } |
| } |
| }; |
| } |
| void serialization::writeSourceInfoToStream(raw_ostream &os, |
| ModuleOrSourceFile DC) { |
| assert(DC); |
| SourceInfoSerializer S{SWIFTSOURCEINFO_SIGNATURE, DC}; |
| // FIXME: This is only really needed for debugging. We don't actually use it. |
| S.writeSourceInfoBlockInfoBlock(); |
| { |
| BCBlockRAII moduleBlock(S.Out, MODULE_SOURCEINFO_BLOCK_ID, 2); |
| S.writeSourceInfoHeader(); |
| { |
| BCBlockRAII restoreBlock(S.Out, DECL_LOCS_BLOCK_ID, 4); |
| DeclUSRsTableWriter USRWriter; |
| StringWriter FPWriter; |
| emitBasicLocsRecord(S.Out, DC, USRWriter, FPWriter); |
| // Emit USR table mapping from a USR to USR Id. |
| // The basic locs record uses USR Id instead of actual USR, so that we |
| // don't need to repeat USR texts for newly added records. |
| USRWriter.emitUSRsRecord(S.Out); |
| // A blob of 0 terminated strings referenced by the location records, |
| // e.g. file paths. |
| FPWriter.emitSourceFilesRecord(S.Out); |
| } |
| } |
| |
| S.writeToStream(os); |
| } |