diff --git a/clang/include/clang/APINotes/APINotesWriter.h b/clang/include/clang/APINotes/APINotesWriter.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/APINotes/APINotesWriter.h @@ -0,0 +1,37 @@ +//===-- APINotesWriter.h - API Notes Writer ---------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_APINOTES_WRITER_H +#define LLVM_CLANG_APINOTES_WRITER_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" + +#include + +namespace clang { +class FileEntry; + +namespace api_notes { +class APINotesWriter { + class Implementation; + std::unique_ptr Implementation; + +public: + APINotesWriter(llvm::StringRef ModuleName, const FileEntry *SF); + ~APINotesWriter(); + + APINotesWriter(const APINotesWriter &) = delete; + APINotesWriter &operator=(const APINotesWriter &) = delete; + + void writeToStream(llvm::raw_ostream &OS); +}; +} // namespace api_notes +} // namespace clang + +#endif diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -252,4 +252,36 @@ } // namespace api_notes } // namespace clang +namespace llvm { +template <> struct DenseMapInfo { + typedef DenseMapInfo UnsignedInfo; + + static inline clang::api_notes::StoredObjCSelector getEmptyKey() { + return clang::api_notes::StoredObjCSelector{UnsignedInfo::getEmptyKey(), + {}}; + } + + static inline clang::api_notes::StoredObjCSelector getTombstoneKey() { + return clang::api_notes::StoredObjCSelector{UnsignedInfo::getTombstoneKey(), + {}}; + } + + static unsigned + getHashValue(const clang::api_notes::StoredObjCSelector &Selector) { + auto hash = llvm::hash_value(Selector.NumPieces); + hash = hash_combine(hash, Selector.Identifiers.size()); + for (auto piece : Selector.Identifiers) + hash = hash_combine(hash, static_cast(piece)); + // FIXME: Mix upper/lower 32-bit values together to produce + // unsigned rather than truncating. + return hash; + } + + static bool isEqual(const clang::api_notes::StoredObjCSelector &LHS, + const clang::api_notes::StoredObjCSelector &RHS) { + return LHS.NumPieces == RHS.NumPieces && LHS.Identifiers == RHS.Identifiers; + } +}; +} // namespace llvm + #endif diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -0,0 +1,1165 @@ +//===-- APINotesWriter.cpp - API Notes Writer -------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/APINotes/APINotesWriter.h" +#include "APINotesFormat.h" +#include "clang/APINotes/Types.h" +#include "clang/Basic/FileManager.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Bitstream/BitstreamWriter.h" +#include "llvm/Support/DJB.h" +#include "llvm/Support/OnDiskHashTable.h" +#include "llvm/Support/VersionTuple.h" + +namespace clang { +namespace api_notes { +class APINotesWriter::Implementation { + template + using VersionedSmallVector = + llvm::SmallVector, 1>; + + std::string ModuleName; + const FileEntry *SourceFile; + + /// Scratch space for bitstream writing. + llvm::SmallVector Scratch; + +#if defined(__APPLE__) && defined(SWIFT_DOWNSTREAM) + bool SwiftImportAsMember = false; +#endif + + /// Mapping from strings to identifier IDs. + llvm::StringMap IdentifierIDs; + + /// Information about Objective-C contexts (classes or protocols). + /// + /// Indexed by the identifier ID and a bit indication whether we're looking + /// for a class (0) or protocol (1) and provides both the context ID and + /// information describing the context within that module. + llvm::DenseMap, + std::pair>> + ObjCContexts; + + /// Information about Objective-C properties. + /// + /// Indexed by the context ID, property name, and whether this is an + /// instance property. + llvm::DenseMap< + std::tuple, + llvm::SmallVector, 1>> + ObjCProperties; + + /// Information about Objective-C methods. + /// + /// Indexed by the context ID, selector ID, and Boolean (stored as a char) + /// indicating whether this is a class or instance method. + llvm::DenseMap, + llvm::SmallVector, 1>> + ObjCMethods; + + /// Mapping from selectors to selector ID. + llvm::DenseMap SelectorIDs; + + /// Information about global variables. + /// + /// Indexed by the identifier ID. + llvm::DenseMap, 1>> + GlobalVariables; + + /// Information about global functions. + /// + /// Indexed by the identifier ID. + llvm::DenseMap, 1>> + GlobalFunctions; + + /// Information about enumerators. + /// + /// Indexed by the identifier ID. + llvm::DenseMap< + unsigned, llvm::SmallVector, 1>> + EnumConstants; + + /// Information about tags. + /// + /// Indexed by the identifier ID. + llvm::DenseMap, 1>> + Tags; + + /// Information about typedefs. + /// + /// Indexed by the identifier ID. + llvm::DenseMap, 1>> + Typedefs; + + void writeBlockInfoBlock(llvm::BitstreamWriter &Stream); + void writeControlBlock(llvm::BitstreamWriter &Stream); + void writeIdentifierBlock(llvm::BitstreamWriter &Stream); + void writeObjCContextBlock(llvm::BitstreamWriter &Stream); + void writeObjCPropertyBlock(llvm::BitstreamWriter &Stream); + void writeObjCMethodBlock(llvm::BitstreamWriter &Stream); + void writeObjCSelectorBlock(llvm::BitstreamWriter &Stream); + void writeGlobalVariableBlock(llvm::BitstreamWriter &Stream); + void writeGlobalFunctionBlock(llvm::BitstreamWriter &Stream); + void writeEnumConstantBlock(llvm::BitstreamWriter &Stream); + void writeTagBlock(llvm::BitstreamWriter &Stream); + void writeTypedefBlock(llvm::BitstreamWriter &Stream); + +public: + Implementation(llvm::StringRef ModuleName, const FileEntry *SF) + : ModuleName(std::string(ModuleName)), SourceFile(SF) {} + + void writeToStream(llvm::raw_ostream &OS); +}; + +void APINotesWriter::Implementation::writeToStream(llvm::raw_ostream &OS) { + llvm::SmallVector Buffer; + + { + llvm::BitstreamWriter Stream(Buffer); + + // Emit the signature. + for (unsigned char Byte : API_NOTES_SIGNATURE) + Stream.Emit(Byte, 8); + + // Emit the blocks. + writeBlockInfoBlock(Stream); + writeControlBlock(Stream); + writeIdentifierBlock(Stream); + writeObjCContextBlock(Stream); + writeObjCPropertyBlock(Stream); + writeObjCMethodBlock(Stream); + writeObjCSelectorBlock(Stream); + writeGlobalVariableBlock(Stream); + writeGlobalFunctionBlock(Stream); + writeEnumConstantBlock(Stream); + writeTagBlock(Stream); + writeTypedefBlock(Stream); + } + + OS.write(Buffer.data(), Buffer.size()); + OS.flush(); +} + +namespace { +/// Record the name of a block. +void emitBlockID(llvm::BitstreamWriter &Stream, unsigned ID, + llvm::StringRef Name) { + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETBID, + llvm::ArrayRef{ID}); + + // Emit the block name if present. + if (Name.empty()) + return; + Stream.EmitRecord( + llvm::bitc::BLOCKINFO_CODE_BLOCKNAME, + llvm::ArrayRef( + const_cast( + reinterpret_cast(Name.data())), + Name.size())); +} + +/// Record the name of a record within a block. +void emitRecordID(llvm::BitstreamWriter &Stream, unsigned ID, + llvm::StringRef Name) { + assert(ID < 256 && "can't fit record ID in next to name"); + + llvm::SmallVector Buffer; + Buffer.resize(Name.size() + 1); + Buffer[0] = ID; + memcpy(Buffer.data() + 1, Name.data(), Name.size()); + + Stream.EmitRecord(llvm::bitc::BLOCKINFO_CODE_SETRECORDNAME, Buffer); +} +} // namespace + +void APINotesWriter::Implementation::writeBlockInfoBlock( + llvm::BitstreamWriter &Stream) { + llvm::BCBlockRAII Scope(Stream, llvm::bitc::BLOCKINFO_BLOCK_ID, 2); + +#define BLOCK(Block) emitBlockID(Stream, Block##_ID, #Block) +#define BLOCK_RECORD(NameSpace, Block) \ + emitRecordID(Stream, NameSpace::Block, #Block) + BLOCK(CONTROL_BLOCK); + BLOCK_RECORD(control_block, METADATA); + BLOCK_RECORD(control_block, MODULE_NAME); + + BLOCK(IDENTIFIER_BLOCK); + BLOCK_RECORD(identifier_block, IDENTIFIER_DATA); + + BLOCK(OBJC_CONTEXT_BLOCK); + BLOCK_RECORD(objc_context_block, OBJC_CONTEXT_ID_DATA); + + BLOCK(OBJC_PROPERTY_BLOCK); + BLOCK_RECORD(objc_property_block, OBJC_PROPERTY_DATA); + + BLOCK(OBJC_METHOD_BLOCK); + BLOCK_RECORD(objc_method_block, OBJC_METHOD_DATA); + + BLOCK(OBJC_SELECTOR_BLOCK); + BLOCK_RECORD(objc_selector_block, OBJC_SELECTOR_DATA); + + BLOCK(GLOBAL_VARIABLE_BLOCK); + BLOCK_RECORD(global_variable_block, GLOBAL_VARIABLE_DATA); + + BLOCK(GLOBAL_FUNCTION_BLOCK); + BLOCK_RECORD(global_function_block, GLOBAL_FUNCTION_DATA); +#undef BLOCK_RECORD +#undef BLOCK +} + +void APINotesWriter::Implementation::writeControlBlock( + llvm::BitstreamWriter &Stream) { + llvm::BCBlockRAII Scope(Stream, CONTROL_BLOCK_ID, 3); + + control_block::MetadataLayout Metadata(Stream); + Metadata.emit(Scratch, VERSION_MAJOR, VERSION_MINOR); + + control_block::ModuleNameLayout ModuleName(Stream); + ModuleName.emit(Scratch, this->ModuleName); + +#if defined(__APPLE__) && defined(SWIFT_DOWNSTREAM) + if (SwiftInferImportAsMember) { + control_block::ModuleOptionsLayout ModuleOptions(Stream); + ModuleOptions.emit(Scratch, SwiftInferImportAsMember); + } +#endif + + if (SourceFile) { + control_block::SourceFileLayout SourceFile(Stream); + SourceFile.emit(Scratch, this->SourceFile->getSize(), + this->SourceFile->getModificationTime()); + } +} + +namespace { +/// Used to serialize the on-disk identifier table. +class IdentifierTableInfo { +public: + using key_type = StringRef; + using key_type_ref = key_type; + using data_type = IdentifierID; + 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) { return llvm::djbHash(Key); } + + std::pair + EmitKeyDataLength(raw_ostream &OS, key_type_ref Key, data_type_ref) { + uint32_t KeyLength = Key.size(); + uint32_t DataLength = sizeof(uint32_t); + + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(KeyLength); + writer.write(DataLength); + return {KeyLength, DataLength}; + } + + void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) { OS << Key; } + + void EmitData(raw_ostream &OS, key_type_ref, data_type_ref Data, unsigned) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(Data); + } +}; +} // namespace + +void APINotesWriter::Implementation::writeIdentifierBlock( + llvm::BitstreamWriter &Stream) { + llvm::BCBlockRAII restoreBlock(Stream, IDENTIFIER_BLOCK_ID, 3); + + if (IdentifierIDs.empty()) + return; + + llvm::SmallString<4096> HashTableBlob; + uint32_t Offset; + { + llvm::OnDiskChainedHashTableGenerator Generator; + for (auto &II : IdentifierIDs) + Generator.insert(II.first(), II.second); + + llvm::raw_svector_ostream BlobStream(HashTableBlob); + // Make sure that no bucket is at offset 0 + llvm::support::endian::write(BlobStream, 0, + llvm::support::little); + Offset = Generator.Emit(BlobStream); + } + + identifier_block::IdentifierDataLayout IdentifierData(Stream); + IdentifierData.emit(Scratch, Offset, HashTableBlob); +} + +namespace { +/// Used to serialize the on-disk Objective-C context table. +class ObjCContextIDTableInfo { +public: + using key_type = std::pair; // identifier ID, is-protocol + using key_type_ref = key_type; + using data_type = unsigned; + using data_type_ref = const data_type &; + using hash_value_type = size_t; + using offset_type = unsigned; + + hash_value_type ComputeHash(key_type_ref Key) { + return static_cast(llvm::hash_value(Key)); + } + + std::pair EmitKeyDataLength(raw_ostream &OS, key_type_ref, + data_type_ref) { + uint32_t KeyLength = sizeof(uint32_t) + 1; + uint32_t DataLength = sizeof(uint32_t); + + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(KeyLength); + writer.write(DataLength); + return {KeyLength, DataLength}; + } + + void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(Key.first); + writer.write(Key.second); + } + + void EmitData(raw_ostream &OS, key_type_ref, data_type_ref Data, unsigned) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(Data); + } +}; + +/// Localized helper to make a type dependent, thwarting template argument +/// deduction. +template struct MakeDependent { typedef T Type; }; + +/// Retrieve the serialized size of the given VersionTuple, for use in +/// on-disk hash tables. +unsigned getVersionTupleSize(const VersionTuple &VT) { + unsigned size = sizeof(uint8_t) + /*major*/ sizeof(uint32_t); + if (VT.getMinor()) + size += sizeof(uint32_t); + if (VT.getSubminor()) + size += sizeof(uint32_t); + if (VT.getBuild()) + size += sizeof(uint32_t); + return size; +} + +/// Determine the size of an array of versioned information, +template +unsigned getVersionedInfoSize( + const llvm::SmallVectorImpl> &VI, + llvm::function_ref::Type &)> + getInfoSize) { + unsigned result = sizeof(uint16_t); // # of elements + for (const auto &E : VI) { + result += getVersionTupleSize(E.first); + result += getInfoSize(E.second); + } + return result; +} + +/// Emit a serialized representation of a version tuple. +void emitVersionTuple(raw_ostream &OS, const VersionTuple &VT) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + + // First byte contains the number of components beyond the 'major' component. + uint8_t descriptor; + if (VT.getBuild()) + descriptor = 3; + else if (VT.getSubminor()) + descriptor = 2; + else if (VT.getMinor()) + descriptor = 1; + else + descriptor = 0; + writer.write(descriptor); + + // Write the components. + writer.write(VT.getMajor()); + if (auto minor = VT.getMinor()) + writer.write(*minor); + if (auto subminor = VT.getSubminor()) + writer.write(*subminor); + if (auto build = VT.getBuild()) + writer.write(*build); +} + +/// Emit versioned information. +template +void emitVersionedInfo( + raw_ostream &OS, llvm::SmallVectorImpl> &VI, + llvm::function_ref::Type &)> + emitInfo) { + std::sort(VI.begin(), VI.end(), + [](const std::pair &LHS, + const std::pair &RHS) -> bool { + assert(LHS.first != RHS.first && + "two entries for the same version"); + return LHS.first < RHS.first; + }); + + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(VI.size()); + for (const auto &E : VI) { + emitVersionTuple(OS, E.first); + emitInfo(OS, E.second); + } +} + +/// On-disk hash table info key base for handling versioned data. +template +class VersionedTableInfo { + Derived &asDerived() { return *static_cast(this); } + + const Derived &asDerived() const { + return *static_cast(this); + } + +public: + using key_type = KeyType; + using key_type_ref = key_type; + using data_type = + llvm::SmallVector, 1>; + using data_type_ref = data_type &; + using hash_value_type = size_t; + using offset_type = unsigned; + + hash_value_type ComputeHash(key_type_ref Key) { + return llvm::hash_value(Key); + } + + std::pair + EmitKeyDataLength(raw_ostream &OS, key_type_ref Key, data_type_ref Data) { + uint32_t KeyLength = asDerived().getKeyLength(Key); + uint32_t DataLength = + getVersionedInfoSize(Data, [this](const UnversionedDataType &UI) { + return asDerived().getUnversionedInfoSize(UI); + }); + + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(KeyLength); + writer.write(DataLength); + return {KeyLength, DataLength}; + } + + void EmitData(raw_ostream &OS, key_type_ref, data_type_ref Data, unsigned) { + emitVersionedInfo( + OS, Data, [this](llvm::raw_ostream &OS, const UnversionedDataType &UI) { + asDerived().emitUnversionedInfo(OS, UI); + }); + } +}; + +/// Emit a serialized representation of the common entity information. +void emitCommonEntityInfo(raw_ostream &OS, const CommonEntityInfo &CEI) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + + uint8_t payload = 0; + if (auto swiftPrivate = CEI.isSwiftPrivate()) { + payload |= 0x01; + if (*swiftPrivate) + payload |= 0x02; + } + payload <<= 1; + payload |= CEI.Unavailable; + payload <<= 1; + payload |= CEI.UnavailableInSwift; + + writer.write(payload); + + writer.write(CEI.UnavailableMsg.size()); + OS.write(CEI.UnavailableMsg.c_str(), CEI.UnavailableMsg.size()); + + writer.write(CEI.SwiftName.size()); + OS.write(CEI.SwiftName.c_str(), CEI.SwiftName.size()); +} + +/// Retrieve the serialized size of the given CommonEntityInfo, for use in +/// on-disk hash tables. +unsigned getCommonEntityInfoSize(const CommonEntityInfo &CEI) { + return 5 + CEI.UnavailableMsg.size() + CEI.SwiftName.size(); +} + +// Retrieve the serialized size of the given CommonTypeInfo, for use +// in on-disk hash tables. +unsigned getCommonTypeInfoSize(const CommonTypeInfo &CTI) { + return 2 + (CTI.getSwiftBridge() ? CTI.getSwiftBridge()->size() : 0) + 2 + + (CTI.getNSErrorDomain() ? CTI.getNSErrorDomain()->size() : 0) + + getCommonEntityInfoSize(CTI); +} + +/// Emit a serialized representation of the common type information. +void emitCommonTypeInfo(raw_ostream &OS, const CommonTypeInfo &CTI) { + emitCommonEntityInfo(OS, CTI); + + llvm::support::endian::Writer writer(OS, llvm::support::little); + if (auto swiftBridge = CTI.getSwiftBridge()) { + writer.write(swiftBridge->size() + 1); + OS.write(swiftBridge->c_str(), swiftBridge->size()); + } else { + writer.write(0); + } + if (auto nsErrorDomain = CTI.getNSErrorDomain()) { + writer.write(nsErrorDomain->size() + 1); + OS.write(nsErrorDomain->c_str(), CTI.getNSErrorDomain()->size()); + } else { + writer.write(0); + } +} + +/// Used to serialize the on-disk Objective-C property table. +class ObjCContextInfoTableInfo + : public VersionedTableInfo { +public: + unsigned getKeyLength(key_type_ref) { return sizeof(uint32_t); } + + void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(Key); + } + + unsigned getUnversionedInfoSize(const ObjCContextInfo &OCI) { + return getCommonTypeInfoSize(OCI) + 1; + } + + void emitUnversionedInfo(raw_ostream &OS, const ObjCContextInfo &OCI) { + emitCommonTypeInfo(OS, OCI); + + uint8_t payload = 0; + if (auto swiftImportAsNonGeneric = OCI.getSwiftImportAsNonGeneric()) + payload |= (0x01 << 1) | swiftImportAsNonGeneric.value(); + payload <<= 2; + if (auto swiftObjCMembers = OCI.getSwiftObjCMembers()) + payload |= (0x01 << 1) | swiftObjCMembers.value(); + payload <<= 3; + if (auto nullable = OCI.getDefaultNullability()) + payload |= (0x01 << 2) | static_cast(*nullable); + payload = (payload << 1) | (OCI.hasDesignatedInits() ? 1 : 0); + + OS << payload; + } +}; +} // namespace + +void APINotesWriter::Implementation::writeObjCContextBlock( + llvm::BitstreamWriter &Stream) { + llvm::BCBlockRAII restoreBlock(Stream, OBJC_CONTEXT_BLOCK_ID, 3); + + if (ObjCContexts.empty()) + return; + + { + llvm::SmallString<4096> HashTableBlob; + uint32_t Offset; + { + llvm::OnDiskChainedHashTableGenerator Generator; + for (auto &OC : ObjCContexts) + Generator.insert(OC.first, OC.second.first); + + llvm::raw_svector_ostream BlobStream(HashTableBlob); + // Make sure that no bucket is at offset 0 + llvm::support::endian::write(BlobStream, 0, + llvm::support::little); + Offset = Generator.Emit(BlobStream); + } + + objc_context_block::ObjCContextIDLayout ObjCContextID(Stream); + ObjCContextID.emit(Scratch, Offset, HashTableBlob); + } + + { + llvm::SmallString<4096> HashTableBlob; + uint32_t Offset; + { + llvm::OnDiskChainedHashTableGenerator Generator; + for (auto &OC : ObjCContexts) + Generator.insert(OC.second.first, OC.second.second); + + llvm::raw_svector_ostream BlobStream(HashTableBlob); + // Make sure that no bucket is at offset 0 + llvm::support::endian::write(BlobStream, 0, + llvm::support::little); + Offset = Generator.Emit(BlobStream); + } + + objc_context_block::ObjCContextInfoLayout ObjCContextInfo(Stream); + ObjCContextInfo.emit(Scratch, Offset, HashTableBlob); + } +} + +namespace { +/// Retrieve the serialized size of the given VariableInfo, for use in +/// on-disk hash tables. +unsigned getVariableInfoSize(const VariableInfo &VI) { + return 2 + getCommonEntityInfoSize(VI) + 2 + VI.getType().size(); +} + +/// Emit a serialized representation of the variable information. +void emitVariableInfo(raw_ostream &OS, const VariableInfo &VI) { + emitCommonEntityInfo(OS, VI); + + uint8_t bytes[2] = {0, 0}; + if (auto nullable = VI.getNullability()) { + bytes[0] = 1; + bytes[1] = static_cast(*nullable); + } else { + // Nothing to do. + } + + OS.write(reinterpret_cast(bytes), 2); + + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(VI.getType().size()); + OS.write(VI.getType().data(), VI.getType().size()); +} + +/// Used to serialize the on-disk Objective-C property table. +class ObjCPropertyTableInfo + : public VersionedTableInfo, + ObjCPropertyInfo> { +public: + unsigned getKeyLength(key_type_ref) { + return sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint8_t); + } + + void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(std::get<0>(Key)); + writer.write(std::get<1>(Key)); + writer.write(std::get<2>(Key)); + } + + unsigned getUnversionedInfoSize(const ObjCPropertyInfo &OPI) { + return getVariableInfoSize(OPI) + 1; + } + + void emitUnversionedInfo(raw_ostream &OS, const ObjCPropertyInfo &OPI) { + emitVariableInfo(OS, OPI); + + uint8_t flags = 0; + if (auto value = OPI.getSwiftImportAsAccessors()) { + flags |= 1 << 0; + flags |= value.value() << 1; + } + OS << flags; + } +}; +} // namespace + +void APINotesWriter::Implementation::writeObjCPropertyBlock( + llvm::BitstreamWriter &Stream) { + llvm::BCBlockRAII Scope(Stream, OBJC_PROPERTY_BLOCK_ID, 3); + + if (ObjCProperties.empty()) + return; + + { + llvm::SmallString<4096> HashTableBlob; + uint32_t Offset; + { + llvm::OnDiskChainedHashTableGenerator Generator; + for (auto &OP : ObjCProperties) + Generator.insert(OP.first, OP.second); + + llvm::raw_svector_ostream BlobStream(HashTableBlob); + // Make sure that no bucket is at offset 0 + llvm::support::endian::write(BlobStream, 0, + llvm::support::little); + Offset = Generator.Emit(BlobStream); + } + + objc_property_block::ObjCPropertyDataLayout ObjCPropertyData(Stream); + ObjCPropertyData.emit(Scratch, Offset, HashTableBlob); + } +} + +namespace { +unsigned getFunctionInfoSize(const FunctionInfo &); +void emitFunctionInfo(llvm::raw_ostream &, const FunctionInfo &); + +/// Used to serialize the on-disk Objective-C method table. +class ObjCMethodTableInfo + : public VersionedTableInfo, + ObjCMethodInfo> { +public: + unsigned getKeyLength(key_type_ref) { + return sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint8_t); + } + + void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(std::get<0>(Key)); + writer.write(std::get<1>(Key)); + writer.write(std::get<2>(Key)); + } + + unsigned getUnversionedInfoSize(const ObjCMethodInfo &OMI) { + return getFunctionInfoSize(OMI) + 1; + } + + void emitUnversionedInfo(raw_ostream &OS, const ObjCMethodInfo &OMI) { + uint8_t flags = 0; + llvm::support::endian::Writer writer(OS, llvm::support::little); + flags = (flags << 1) | OMI.DesignatedInit; + flags = (flags << 1) | OMI.RequiredInit; + writer.write(flags); + + emitFunctionInfo(OS, OMI); + } +}; +} // namespace + +void APINotesWriter::Implementation::writeObjCMethodBlock( + llvm::BitstreamWriter &Stream) { + llvm::BCBlockRAII Scope(Stream, OBJC_METHOD_BLOCK_ID, 3); + + if (ObjCMethods.empty()) + return; + + { + llvm::SmallString<4096> HashTableBlob; + uint32_t Offset; + { + llvm::OnDiskChainedHashTableGenerator Generator; + for (auto &OM : ObjCMethods) + Generator.insert(OM.first, OM.second); + + llvm::raw_svector_ostream BlobStream(HashTableBlob); + // Make sure that no bucket is at offset 0 + llvm::support::endian::write(BlobStream, 0, + llvm::support::little); + Offset = Generator.Emit(BlobStream); + } + + objc_method_block::ObjCMethodDataLayout ObjCMethodData(Stream); + ObjCMethodData.emit(Scratch, Offset, HashTableBlob); + } +} + +namespace { +/// Used to serialize the on-disk Objective-C selector table. +class ObjCSelectorTableInfo { +public: + using key_type = StoredObjCSelector; + using key_type_ref = const key_type &; + using data_type = SelectorID; + using data_type_ref = data_type; + using hash_value_type = unsigned; + using offset_type = unsigned; + + hash_value_type ComputeHash(key_type_ref Key) { + return llvm::DenseMapInfo::getHashValue(Key); + } + + std::pair + EmitKeyDataLength(raw_ostream &OS, key_type_ref Key, data_type_ref) { + uint32_t KeyLength = + sizeof(uint16_t) + sizeof(uint32_t) * Key.Identifiers.size(); + uint32_t DataLength = sizeof(uint32_t); + + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(KeyLength); + writer.write(DataLength); + return {KeyLength, DataLength}; + } + + void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(Key.NumPieces); + for (auto Identifier : Key.Identifiers) + writer.write(Identifier); + } + + void EmitData(raw_ostream &OS, key_type_ref, data_type_ref Data, unsigned) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(Data); + } +}; +} // namespace + +void APINotesWriter::Implementation::writeObjCSelectorBlock( + llvm::BitstreamWriter &Stream) { + llvm::BCBlockRAII Scope(Stream, OBJC_SELECTOR_BLOCK_ID, 3); + + if (SelectorIDs.empty()) + return; + + { + llvm::SmallString<4096> HashTableBlob; + uint32_t Offset; + { + llvm::OnDiskChainedHashTableGenerator Generator; + for (auto &S : SelectorIDs) + Generator.insert(S.first, S.second); + + llvm::raw_svector_ostream BlobStream(HashTableBlob); + // Make sure that no bucket is at offset 0 + llvm::support::endian::write(BlobStream, 0, + llvm::support::little); + Offset = Generator.Emit(BlobStream); + } + + objc_selector_block::ObjCSelectorDataLayout ObjCSelectorData(Stream); + ObjCSelectorData.emit(Scratch, Offset, HashTableBlob); + } +} + +namespace { +/// Used to serialize the on-disk global variable table. +class GlobalVariableTableInfo + : public VersionedTableInfo { +public: + unsigned getKeyLength(key_type_ref) { return sizeof(uint32_t); } + + void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(Key); + } + + unsigned getUnversionedInfoSize(const GlobalVariableInfo &GVI) { + return getVariableInfoSize(GVI); + } + + void emitUnversionedInfo(raw_ostream &OS, const GlobalVariableInfo &GVI) { + emitVariableInfo(OS, GVI); + } +}; +} // namespace + +void APINotesWriter::Implementation::writeGlobalVariableBlock( + llvm::BitstreamWriter &Stream) { + llvm::BCBlockRAII Scope(Stream, GLOBAL_VARIABLE_BLOCK_ID, 3); + + if (GlobalVariables.empty()) + return; + + { + llvm::SmallString<4096> HashTableBlob; + uint32_t Offset; + { + llvm::OnDiskChainedHashTableGenerator Generator; + for (auto &GV : GlobalVariables) + Generator.insert(GV.first, GV.second); + + llvm::raw_svector_ostream BlobStream(HashTableBlob); + // Make sure that no bucket is at offset 0 + llvm::support::endian::write(BlobStream, 0, + llvm::support::little); + Offset = Generator.Emit(BlobStream); + } + + global_variable_block::GlobalVariableDataLayout GlobalVariableData(Stream); + GlobalVariableData.emit(Scratch, Offset, HashTableBlob); + } +} + +namespace { +unsigned getParamInfoSize(const ParamInfo &PI) { + return getVariableInfoSize(PI) + 1; +} + +void emitParamInfo(raw_ostream &OS, const ParamInfo &PI) { + emitVariableInfo(OS, PI); + + uint8_t flags = 0; + if (auto noescape = PI.isNoEscape()) { + flags |= 0x01; + if (*noescape) + flags |= 0x02; + } + flags <<= 3; + if (auto RCC = PI.getRetainCountConvention()) + flags |= static_cast(RCC.value()) + 1; + + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(flags); +} + +/// Retrieve the serialized size of the given FunctionInfo, for use in on-disk +/// hash tables. +unsigned getFunctionInfoSize(const FunctionInfo &FI) { + unsigned size = getCommonEntityInfoSize(FI) + 2 + sizeof(uint64_t); + size += sizeof(uint16_t); + for (const auto &P : FI.Params) + size += getParamInfoSize(P); + size += sizeof(uint16_t) + FI.ResultType.size(); + return size; +} + +/// Emit a serialized representation of the function information. +static void emitFunctionInfo(raw_ostream &OS, const FunctionInfo &FI) { + emitCommonEntityInfo(OS, FI); + + uint8_t flags = 0; + flags |= FI.NullabilityAudited; + flags <<= 3; + if (auto RCC = FI.getRetainCountConvention()) + flags |= static_cast(RCC.value()) + 1; + + llvm::support::endian::Writer writer(OS, llvm::support::little); + + writer.write(flags); + writer.write(FI.NumAdjustedNullable); + writer.write(FI.NullabilityPayload); + + writer.write(FI.Params.size()); + for (const auto &PI : FI.Params) + emitParamInfo(OS, PI); + + writer.write(FI.ResultType.size()); + writer.write(ArrayRef{FI.ResultType.data(), FI.ResultType.size()}); +} + +/// Used to serialize the on-disk global function table. +class GlobalFunctionTableInfo + : public VersionedTableInfo { +public: + unsigned getKeyLength(key_type_ref) { return sizeof(uint32_t); } + + void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(Key); + } + + unsigned getUnversionedInfoSize(const GlobalFunctionInfo &GFI) { + return getFunctionInfoSize(GFI); + } + + void emitUnversionedInfo(raw_ostream &OS, const GlobalFunctionInfo &GFI) { + emitFunctionInfo(OS, GFI); + } +}; +} // namespace + +void APINotesWriter::Implementation::writeGlobalFunctionBlock( + llvm::BitstreamWriter &Stream) { + llvm::BCBlockRAII Scope(Stream, GLOBAL_FUNCTION_BLOCK_ID, 3); + + if (GlobalFunctions.empty()) + return; + + { + llvm::SmallString<4096> HashTableBlob; + uint32_t Offset; + { + llvm::OnDiskChainedHashTableGenerator Generator; + for (auto &F : GlobalFunctions) + Generator.insert(F.first, F.second); + + llvm::raw_svector_ostream BlobStream(HashTableBlob); + // Make sure that no bucket is at offset 0 + llvm::support::endian::write(BlobStream, 0, + llvm::support::little); + Offset = Generator.Emit(BlobStream); + } + + global_function_block::GlobalFunctionDataLayout GlobalFunctionData(Stream); + GlobalFunctionData.emit(Scratch, Offset, HashTableBlob); + } +} + +namespace { +/// Used to serialize the on-disk global enum constant. +class EnumConstantTableInfo + : public VersionedTableInfo { +public: + unsigned getKeyLength(key_type_ref) { return sizeof(uint32_t); } + + void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(Key); + } + + unsigned getUnversionedInfoSize(const EnumConstantInfo &ECI) { + return getCommonEntityInfoSize(ECI); + } + + void emitUnversionedInfo(raw_ostream &OS, const EnumConstantInfo &ECI) { + emitCommonEntityInfo(OS, ECI); + } +}; +} // namespace + +void APINotesWriter::Implementation::writeEnumConstantBlock( + llvm::BitstreamWriter &Stream) { + llvm::BCBlockRAII Scope(Stream, ENUM_CONSTANT_BLOCK_ID, 3); + + if (EnumConstants.empty()) + return; + + { + llvm::SmallString<4096> HashTableBlob; + uint32_t Offset; + { + llvm::OnDiskChainedHashTableGenerator Generator; + for (auto &EC : EnumConstants) + Generator.insert(EC.first, EC.second); + + llvm::raw_svector_ostream BlobStream(HashTableBlob); + // Make sure that no bucket is at offset 0 + llvm::support::endian::write(BlobStream, 0, + llvm::support::little); + Offset = Generator.Emit(BlobStream); + } + + enum_constant_block::EnumConstantDataLayout EnumConstantData(Stream); + EnumConstantData.emit(Scratch, Offset, HashTableBlob); + } +} + +namespace { +template +class CommonTypeTableInfo + : public VersionedTableInfo { +public: + using key_type_ref = typename CommonTypeTableInfo::key_type_ref; + + unsigned getKeyLength(key_type_ref) { return sizeof(IdentifierID); } + + void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + writer.write(Key); + } + + unsigned getUnversionedInfoSize(const UnversionedDataType &UDT) { + return getCommonTypeInfoSize(UDT); + } + + void emitUnversionedInfo(raw_ostream &OS, const UnversionedDataType &UDT) { + emitCommonTypeInfo(OS, UDT); + } +}; + +/// Used to serialize the on-disk tag table. +class TagTableInfo : public CommonTypeTableInfo { +public: + unsigned getUnversionedInfoSize(const TagInfo &TI) { + return 1 + getCommonTypeInfoSize(TI); + } + + void emitUnversionedInfo(raw_ostream &OS, const TagInfo &TI) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + + uint8_t Flags = 0; + if (auto extensibility = TI.EnumExtensibility) { + Flags |= static_cast(extensibility.value()) + 1; + assert((Flags < (1 << 2)) && "must fit in two bits"); + } + + Flags <<= 2; + if (auto value = TI.isFlagEnum()) + Flags |= (value.value() << 1 | 1 << 0); + + writer.write(Flags); + + emitCommonTypeInfo(OS, TI); + } +}; +} // namespace + +void APINotesWriter::Implementation::writeTagBlock( + llvm::BitstreamWriter &Stream) { + llvm::BCBlockRAII Scope(Stream, TAG_BLOCK_ID, 3); + + if (Tags.empty()) + return; + + { + llvm::SmallString<4096> HashTableBlob; + uint32_t Offset; + { + llvm::OnDiskChainedHashTableGenerator Generator; + for (auto &T : Tags) + Generator.insert(T.first, T.second); + + llvm::raw_svector_ostream BlobStream(HashTableBlob); + // Make sure that no bucket is at offset 0 + llvm::support::endian::write(BlobStream, 0, + llvm::support::little); + Offset = Generator.Emit(BlobStream); + } + + tag_block::TagDataLayout TagData(Stream); + TagData.emit(Scratch, Offset, HashTableBlob); + } +} + +namespace { +/// Used to serialize the on-disk typedef table. +class TypedefTableInfo + : public CommonTypeTableInfo { +public: + unsigned getUnversionedInfoSize(const TypedefInfo &TI) { + return 1 + getCommonTypeInfoSize(TI); + } + + void emitUnversionedInfo(raw_ostream &OS, const TypedefInfo &TI) { + llvm::support::endian::Writer writer(OS, llvm::support::little); + + uint8_t Flags = 0; + if (auto swiftWrapper = TI.SwiftWrapper) + Flags |= static_cast(*swiftWrapper) + 1; + + writer.write(Flags); + + emitCommonTypeInfo(OS, TI); + } +}; +} // namespace + +void APINotesWriter::Implementation::writeTypedefBlock( + llvm::BitstreamWriter &Stream) { + llvm::BCBlockRAII Scope(Stream, TYPEDEF_BLOCK_ID, 3); + + if (Typedefs.empty()) + return; + + { + llvm::SmallString<4096> HashTableBlob; + uint32_t Offset; + { + llvm::OnDiskChainedHashTableGenerator Generator; + for (auto &T : Typedefs) + Generator.insert(T.first, T.second); + + llvm::raw_svector_ostream BlobStream(HashTableBlob); + // Make sure that no bucket is at offset 0 + llvm::support::endian::write(BlobStream, 0, + llvm::support::little); + Offset = Generator.Emit(BlobStream); + } + + typedef_block::TypedefDataLayout TypedefData(Stream); + TypedefData.emit(Scratch, Offset, HashTableBlob); + } +} + +// APINotesWriter + +APINotesWriter::APINotesWriter(llvm::StringRef ModuleName, const FileEntry *SF) + : Implementation(new class Implementation(ModuleName, SF)) {} + +APINotesWriter::~APINotesWriter() = default; + +void APINotesWriter::writeToStream(llvm::raw_ostream &OS) { + Implementation->writeToStream(OS); +} +} // namespace api_notes +} // namespace clang diff --git a/clang/lib/APINotes/CMakeLists.txt b/clang/lib/APINotes/CMakeLists.txt --- a/clang/lib/APINotes/CMakeLists.txt +++ b/clang/lib/APINotes/CMakeLists.txt @@ -2,6 +2,7 @@ Support) add_clang_library(clangAPINotes APINotesTypes.cpp + APINotesWriter.cpp APINotesYAMLCompiler.cpp LINK_LIBS clangBasic)