//===--- 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 using namespace swift; using namespace swift::serialization; using namespace llvm::support; using swift::version::Version; using llvm::BCBlockRAII; using FileNameToGroupNameMap = llvm::StringMap; 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(Root); if (!MapNode) { return true; } for (auto &Pair : *MapNode) { auto *Key = dyn_cast_or_null(Pair.getKey()); auto *Value = dyn_cast_or_null(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.str(); } for (llvm::yaml::Node &Entry : *Value) { if (auto *FileEntry= dyn_cast(&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] = std::string(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(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 Map; std::vector 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; // If the decl being serialized isn't actually from a source file, don't // put it in a group. // FIXME: How do we preserve group information through partial module // merging for multi-frontend builds, then? SourceFile *SF = VD->getDeclContext()->getParentSourceFile(); if (!SF) return 0; StringRef FullPath = SF->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 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 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(keyLength); writer.write(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(data.Brief.size()); out << data.Brief; writer.write(data.Raw.Comments.size()); for (auto C : data.Raw.Comments) { writer.write(C.ColumnIndent); writer.write(C.RawText.size()); out << C.RawText; } writer.write(data.Group); writer.write(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 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 Names) { llvm::SmallString<32> Blob; llvm::raw_svector_ostream BlobStream(Blob); endian::Writer Writer(BlobStream, little); Writer.write(Names.size()); for (auto N : Names) { Writer.write(N.size()); BlobStream << N; } SmallVector 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 it's a function or subscript with a parameter with leading // double underscore, it's a private function or subscript. if (isa(D) || isa(D)) { if (getParameterList(cast(D))->hasInternalParameter(Prefix)) return true; } if (auto *VD = dyn_cast(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(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; } // Skip SPI decls, unless we're generating a symbol graph with SPI information. if (D->isSPI() && !D->getASTContext().SymbolGraphOpts.IncludeSPISymbols) return false; if (auto *ED = dyn_cast(D)) { auto *extended = ED->getExtendedNominal(); if (!extended) return false; return shouldIncludeDecl(extended, 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 generator; DeclGroupNameContext &GroupContext; unsigned SourceOrder; DeclCommentTableWriter(DeclGroupNameContext &GroupContext): GroupContext(GroupContext) {} void resetSourceOrder() { SourceOrder = 0; } StringRef copyString(StringRef String) { char *Mem = static_cast(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(D)) { writeDocForExtensionDecl(ED); return true; } auto *VD = dyn_cast(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 walkToStmtPre(Stmt *S) override { return { false, S }; } std::pair walkToExprPre(Expr *E) override { return { false, E }; } bool walkToTypeReprPre(TypeRepr *T) override { return false; } bool walkToParameterListPre(ParameterList *PL) override { return false; } }; DeclCommentTableWriter Writer(GroupContext); ArrayRef files; SmallVector Scratch; if (SF) { Scratch.push_back(SF); files = llvm::makeArrayRef(Scratch); } else { files = M->getFiles(); } for (auto nextFile : files) { Writer.resetSourceOrder(); const_cast(nextFile)->walk(Writer); } SmallVector 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(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; auto verText = version::getSwiftFullVersion(LangOpts.EffectiveLanguageVersion); Metadata.emit(ScratchRecord, SWIFTDOC_VERSION_MAJOR, SWIFTDOC_VERSION_MINOR, /*short version string length*/0, /*compatibility length*/0, /*user module version major*/0, /*user module version minor*/0, /*user module version subminor*/0, /*user module version build*/0, verText); 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 { 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 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(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(data); } }; class DeclUSRsTableWriter { llvm::StringSet<> USRs; llvm::OnDiskChainedHashTableGenerator generator; public: uint32_t peekNextId() const { return USRs.size(); } Optional 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 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(blobStream, 0, little); tableOffset = generator.Emit(blobStream); } USRsList.emit(scratch, tableOffset, hashTableBlob); } }; class StringWriter { llvm::StringMap IndexMap; llvm::SmallString<1024> Buffer; public: uint32_t getTextOffset(StringRef Text) { auto IterAndIsNew = IndexMap.insert({Text, Buffer.size()}); if (IterAndIsNew.second) { Buffer.append(Text); Buffer.push_back('\0'); } return IterAndIsNew.first->getValue(); } void emitSourceFilesRecord(llvm::BitstreamWriter &Out) { decl_locs_block::TextDataLayout TextBlob(Out); SmallVector scratch; TextBlob.emit(scratch, Buffer); } }; static void writeRawLoc(const ExternalSourceLocs::RawLoc &Loc, endian::Writer &Writer, StringWriter &Strings) { Writer.write(Loc.Offset); Writer.write(Loc.Line); Writer.write(Loc.Column); Writer.write(Loc.Directive.Offset); Writer.write(Loc.Directive.LineOffset); Writer.write(Loc.Directive.Length); Writer.write(Strings.getTextOffset(Loc.Directive.Name)); } /** Records the locations of `SingleRawComment` pieces for a declaration and emits them into a blob for the DOC_RANGES record. See: \c decl_locs_block::DocRangesLayout */ class DocRangeWriter { StringWriter &Strings; llvm::DenseMap DeclOffsetMap; llvm::SmallString<1024> Buffer; public: DocRangeWriter(StringWriter &Strings) : Strings(Strings) { /** Offset 0 is reserved to mean "no offset", meaning that a declaration didn't have a doc comment. */ Buffer.push_back(0); } /** \returns the offset into the doc ranges buffer for a declaration. Calling this twice on the same declaration will not duplicate data but return the original offset. */ uint32_t getDocRangesOffset( const Decl *D, ArrayRef> DocRanges) { if (DocRanges.empty()) { return 0; } const auto EntryAndAlreadyFound = DeclOffsetMap.insert({ D, Buffer.size() }); const auto StartOffset = EntryAndAlreadyFound.first->getSecond(); const auto AlreadyInMap = !EntryAndAlreadyFound.second; if (AlreadyInMap) { return StartOffset; } llvm::raw_svector_ostream OS(Buffer); endian::Writer Writer(OS, little); Writer.write(DocRanges.size()); for (const auto &DocRange : DocRanges) { writeRawLoc(DocRange.first, Writer, Strings); Writer.write(DocRange.second); } return StartOffset; } void emitDocRangesRecord(llvm::BitstreamWriter &Out) { decl_locs_block::DocRangesLayout DocRangesBlob(Out); SmallVector Scratch; DocRangesBlob.emit(Scratch, Buffer); } }; struct BasicDeclLocsTableWriter : public ASTWalker { llvm::SmallString<1024> Buffer; DeclUSRsTableWriter &USRWriter; StringWriter &FWriter; DocRangeWriter &DocWriter; BasicDeclLocsTableWriter(DeclUSRsTableWriter &USRWriter, StringWriter &FWriter, DocRangeWriter &DocWriter): USRWriter(USRWriter), FWriter(FWriter), DocWriter(DocWriter) {} std::pair walkToStmtPre(Stmt *S) override { return { false, S };} std::pair walkToExprPre(Expr *E) override { return { false, E };} bool walkToTypeReprPre(TypeRepr *T) override { return false; } bool walkToParameterListPre(ParameterList *PL) override { return false; } Optional 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()); } bool shouldSerializeSourceLoc(Decl *D) { if (D->isImplicit()) return false; return true; } bool walkToDeclPre(Decl *D) override { // .swiftdoc doesn't include comments for double underscored symbols, but // for .swiftsourceinfo, having the source location for these symbols isn't // a concern because these symbols are in .swiftinterface anyway. if (!shouldIncludeDecl(D, /*ExcludeDoubleUnderscore*/false)) return false; if (!shouldSerializeSourceLoc(D)) return true; auto *File = D->getDeclContext()->getModuleScopeContext(); auto RawLocs = cast(File)->getExternalRawLocsForDecl(D); if (!RawLocs.hasValue()) return true; // If we have handled this USR before, don't proceed auto USR = calculateNewUSRId(D); if (!USR.hasValue()) return true; llvm::SmallString<128> AbsolutePath = RawLocs->SourceFilePath; llvm::sys::fs::make_absolute(AbsolutePath); llvm::raw_svector_ostream Out(Buffer); endian::Writer Writer(Out, little); Writer.write(FWriter.getTextOffset(AbsolutePath.str())); Writer.write(DocWriter.getDocRangesOffset( D, llvm::makeArrayRef(RawLocs->DocRanges))); writeRawLoc(RawLocs->Loc, Writer, FWriter); writeRawLoc(RawLocs->StartLoc, Writer, FWriter); writeRawLoc(RawLocs->EndLoc, Writer, FWriter); return true; } }; static void emitBasicLocsRecord(llvm::BitstreamWriter &Out, ModuleOrSourceFile MSF, DeclUSRsTableWriter &USRWriter, StringWriter &FWriter, DocRangeWriter &DocWriter) { assert(MSF); const decl_locs_block::BasicDeclLocsLayout DeclLocsList(Out); BasicDeclLocsTableWriter Writer(USRWriter, FWriter, DocWriter); if (auto *SF = MSF.dyn_cast()) { SF->walk(Writer); } else { MSF.get()->walk(Writer); } SmallVector scratch; DeclLocsList.emit(scratch, Writer.Buffer); } static void emitFileListRecord(llvm::BitstreamWriter &Out, ModuleOrSourceFile MSF, StringWriter &FWriter) { assert(MSF); struct SourceFileListWriter { StringWriter &FWriter; llvm::SmallString<0> Buffer; llvm::StringSet<> seenFilenames; void emitSourceFileInfo(const BasicSourceFileInfo &info) { if (info.getFilePath().empty()) return; // Make 'FilePath' absolute for serialization; SmallString<128> absolutePath = info.getFilePath(); llvm::sys::fs::make_absolute(absolutePath); // Don't emit duplicated files. if (!seenFilenames.insert(absolutePath).second) return; auto fileID = FWriter.getTextOffset(absolutePath); auto fingerprintStrIncludingTypeMembers = info.getInterfaceHashIncludingTypeMembers().getRawValue(); auto fingerprintStrExcludingTypeMembers = info.getInterfaceHashExcludingTypeMembers().getRawValue(); auto timestamp = std::chrono::duration_cast( info.getLastModified().time_since_epoch()) .count(); llvm::raw_svector_ostream out(Buffer); endian::Writer writer(out, little); // FilePath. writer.write(fileID); // InterfaceHashIncludingTypeMembers (fixed length string). assert(fingerprintStrIncludingTypeMembers.size() == Fingerprint::DIGEST_LENGTH); out << fingerprintStrIncludingTypeMembers; // InterfaceHashExcludingTypeMembers (fixed length string). assert(fingerprintStrExcludingTypeMembers.size() == Fingerprint::DIGEST_LENGTH); out << fingerprintStrExcludingTypeMembers; // LastModified (nanoseconds since epoch). writer.write(timestamp); // FileSize (num of bytes). writer.write(info.getFileSize()); } SourceFileListWriter(StringWriter &FWriter) : FWriter(FWriter) { Buffer.reserve(1024); } } writer(FWriter); if (SourceFile *SF = MSF.dyn_cast()) { writer.emitSourceFileInfo(BasicSourceFileInfo(SF)); } else { auto *M = MSF.get(); M->collectBasicSourceFileInfo([&](const BasicSourceFileInfo &info) { writer.emitSourceFileInfo(info); }); } const decl_locs_block::SourceFileListLayout FileList(Out); SmallVector scratch; FileList.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 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, SOURCE_FILE_LIST); BLOCK_RECORD(decl_locs_block, BASIC_DECL_LOCS); BLOCK_RECORD(decl_locs_block, DECL_USRS); BLOCK_RECORD(decl_locs_block, TEXT_DATA); BLOCK_RECORD(decl_locs_block, DOC_RANGES); #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; auto verText = version::getSwiftFullVersion(LangOpts.EffectiveLanguageVersion); Metadata.emit(ScratchRecord, SWIFTSOURCEINFO_VERSION_MAJOR, SWIFTSOURCEINFO_VERSION_MINOR, /*short version string length*/0, /*compatibility length*/0, /*user module version major*/0, /*user module version minor*/0, /*user module version sub-minor*/0, /*user module version build*/0, verText); 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; DocRangeWriter DocWriter(FPWriter); emitFileListRecord(S.Out, DC, FPWriter); emitBasicLocsRecord(S.Out, DC, USRWriter, FPWriter, DocWriter); // 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); // A blob of fixed-size location records of `SingleRawComment` pieces. DocWriter.emitDocRangesRecord(S.Out); } } S.writeToStream(os); }