//===-- 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 { friend class APINotesWriter; template using VersionedSmallVector = llvm::SmallVector, 1>; std::string ModuleName; const FileEntry *SourceFile; /// Scratch space for bitstream writing. llvm::SmallVector Scratch; /// Mapping from strings to identifier IDs. llvm::StringMap IdentifierIDs; /// Information about contexts (Objective-C classes or protocols or C++ /// namespaces). /// /// Indexed by the parent context ID, context kind and the identifier ID of /// this context and provides both the context ID and information describing /// the context within that module. llvm::DenseMap>> ObjCContexts; /// Information about parent contexts for each context. /// /// Indexed by context ID, provides the parent context ID. llvm::DenseMap ParentContexts; /// Mapping from context IDs to the identifier ID holding the name. llvm::DenseMap ObjCContextNames; /// 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 context ID, contextKind, identifier ID. llvm::DenseMap< ContextTableKey, llvm::SmallVector, 1>> GlobalVariables; /// Information about global functions. /// /// Indexed by the context ID, contextKind, identifier ID. llvm::DenseMap< ContextTableKey, llvm::SmallVector, 1>> GlobalFunctions; /// Information about enumerators. /// /// Indexed by the identifier ID. llvm::DenseMap< unsigned, llvm::SmallVector, 1>> EnumConstants; /// Information about tags. /// /// Indexed by the context ID, contextKind, identifier ID. llvm::DenseMap, 1>> Tags; /// Information about typedefs. /// /// Indexed by the context ID, contextKind, identifier ID. llvm::DenseMap, 1>> Typedefs; /// Retrieve the ID for the given identifier. IdentifierID getIdentifier(StringRef Identifier) { if (Identifier.empty()) return 0; auto Known = IdentifierIDs.find(Identifier); if (Known != IdentifierIDs.end()) return Known->second; // Add to the identifier table. Known = IdentifierIDs.insert({Identifier, IdentifierIDs.size() + 1}).first; return Known->second; } /// Retrieve the ID for the given selector. SelectorID getSelector(ObjCSelectorRef SelectorRef) { // Translate the selector reference into a stored selector. StoredObjCSelector Selector; Selector.Identifiers.reserve(SelectorRef.Identifiers.size()); for (auto piece : SelectorRef.Identifiers) Selector.Identifiers.push_back(getIdentifier(piece)); // Look for the stored selector. auto Known = SelectorIDs.find(Selector); if (Known != SelectorIDs.end()) return Known->second; // Add to the selector table. Known = SelectorIDs.insert({Selector, SelectorIDs.size()}).first; return Known->second; } private: 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 (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::endianness::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::endianness::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::endianness::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 = ContextTableKey; 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(Key.hashValue()); } std::pair EmitKeyDataLength(raw_ostream &OS, key_type_ref, data_type_ref) { uint32_t KeyLength = sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint32_t); uint32_t DataLength = sizeof(uint32_t); llvm::support::endian::Writer writer(OS, llvm::endianness::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::endianness::little); writer.write(Key.parentContextID); writer.write(Key.contextKind); writer.write(Key.contextID); } void EmitData(raw_ostream &OS, key_type_ref, data_type_ref Data, unsigned) { llvm::support::endian::Writer writer(OS, llvm::endianness::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::endianness::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::endianness::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; 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::endianness::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::endianness::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::endianness::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::endianness::little); writer.write(Key); } hash_value_type ComputeHash(key_type_ref Key) { return static_cast(llvm::hash_value(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) | (uint8_t)swiftImportAsNonGeneric.value(); payload <<= 2; if (auto swiftObjCMembers = OCI.getSwiftObjCMembers()) payload |= (0x01 << 1) | (uint8_t)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::endianness::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::endianness::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::endianness::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::endianness::little); writer.write(std::get<0>(Key)); writer.write(std::get<1>(Key)); writer.write(std::get<2>(Key)); } hash_value_type ComputeHash(key_type_ref Key) { return static_cast(llvm::hash_value(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::endianness::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::endianness::little); writer.write(std::get<0>(Key)); writer.write(std::get<1>(Key)); writer.write(std::get<2>(Key)); } hash_value_type ComputeHash(key_type_ref key) { return static_cast(llvm::hash_value(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::endianness::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::endianness::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::endianness::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::endianness::little); writer.write(Key.NumArgs); 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::endianness::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::endianness::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) + sizeof(uint8_t) + sizeof(uint32_t); } void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) { llvm::support::endian::Writer writer(OS, llvm::endianness::little); writer.write(Key.parentContextID); writer.write(Key.contextKind); writer.write(Key.contextID); } hash_value_type ComputeHash(key_type_ref Key) { return static_cast(Key.hashValue()); } 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::endianness::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::endianness::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. 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::endianness::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) + sizeof(uint8_t) + sizeof(uint32_t); } void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) { llvm::support::endian::Writer writer(OS, llvm::endianness::little); writer.write(Key.parentContextID); writer.write(Key.contextKind); writer.write(Key.contextID); } hash_value_type ComputeHash(key_type_ref Key) { return static_cast(Key.hashValue()); } 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::endianness::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::endianness::little); writer.write(Key); } hash_value_type ComputeHash(key_type_ref Key) { return static_cast(llvm::hash_value(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::endianness::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; using hash_value_type = typename CommonTypeTableInfo::hash_value_type; unsigned getKeyLength(key_type_ref) { return sizeof(uint32_t) + sizeof(uint8_t) + sizeof(IdentifierID); } void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) { llvm::support::endian::Writer writer(OS, llvm::endianness::little); writer.write(Key.parentContextID); writer.write(Key.contextKind); writer.write(Key.contextID); } hash_value_type ComputeHash(key_type_ref Key) { return static_cast(Key.hashValue()); } 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 2 + (TI.SwiftImportAs ? TI.SwiftImportAs->size() : 0) + 2 + (TI.SwiftRetainOp ? TI.SwiftRetainOp->size() : 0) + 2 + (TI.SwiftReleaseOp ? TI.SwiftReleaseOp->size() : 0) + 1 + getCommonTypeInfoSize(TI); } void emitUnversionedInfo(raw_ostream &OS, const TagInfo &TI) { llvm::support::endian::Writer writer(OS, llvm::endianness::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); if (auto ImportAs = TI.SwiftImportAs) { writer.write(ImportAs->size() + 1); OS.write(ImportAs->c_str(), ImportAs->size()); } else { writer.write(0); } if (auto RetainOp = TI.SwiftRetainOp) { writer.write(RetainOp->size() + 1); OS.write(RetainOp->c_str(), RetainOp->size()); } else { writer.write(0); } if (auto ReleaseOp = TI.SwiftReleaseOp) { writer.write(ReleaseOp->size() + 1); OS.write(ReleaseOp->c_str(), ReleaseOp->size()); } else { writer.write(0); } 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::endianness::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::endianness::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::endianness::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); } ContextID APINotesWriter::addObjCContext(std::optional ParentCtxID, StringRef Name, ContextKind Kind, const ObjCContextInfo &Info, VersionTuple SwiftVersion) { IdentifierID NameID = Implementation->getIdentifier(Name); uint32_t RawParentCtxID = ParentCtxID ? ParentCtxID->Value : -1; ContextTableKey Key(RawParentCtxID, static_cast(Kind), NameID); auto Known = Implementation->ObjCContexts.find(Key); if (Known == Implementation->ObjCContexts.end()) { unsigned NextID = Implementation->ObjCContexts.size() + 1; Implementation::VersionedSmallVector EmptyVersionedInfo; Known = Implementation->ObjCContexts .insert(std::make_pair( Key, std::make_pair(NextID, EmptyVersionedInfo))) .first; Implementation->ObjCContextNames[NextID] = NameID; Implementation->ParentContexts[NextID] = RawParentCtxID; } // Add this version information. auto &VersionedVec = Known->second.second; bool Found = false; for (auto &Versioned : VersionedVec) { if (Versioned.first == SwiftVersion) { Versioned.second |= Info; Found = true; break; } } if (!Found) VersionedVec.push_back({SwiftVersion, Info}); return ContextID(Known->second.first); } void APINotesWriter::addObjCProperty(ContextID CtxID, StringRef Name, bool IsInstanceProperty, const ObjCPropertyInfo &Info, VersionTuple SwiftVersion) { IdentifierID NameID = Implementation->getIdentifier(Name); Implementation ->ObjCProperties[std::make_tuple(CtxID.Value, NameID, IsInstanceProperty)] .push_back({SwiftVersion, Info}); } void APINotesWriter::addObjCMethod(ContextID CtxID, ObjCSelectorRef Selector, bool IsInstanceMethod, const ObjCMethodInfo &Info, VersionTuple SwiftVersion) { SelectorID SelID = Implementation->getSelector(Selector); auto Key = std::tuple{CtxID.Value, SelID, IsInstanceMethod}; Implementation->ObjCMethods[Key].push_back({SwiftVersion, Info}); // If this method is a designated initializer, update the class to note that // it has designated initializers. if (Info.DesignatedInit) { assert(Implementation->ParentContexts.contains(CtxID.Value)); uint32_t ParentCtxID = Implementation->ParentContexts[CtxID.Value]; ContextTableKey CtxKey(ParentCtxID, static_cast(ContextKind::ObjCClass), Implementation->ObjCContextNames[CtxID.Value]); assert(Implementation->ObjCContexts.contains(CtxKey)); auto &VersionedVec = Implementation->ObjCContexts[CtxKey].second; bool Found = false; for (auto &Versioned : VersionedVec) { if (Versioned.first == SwiftVersion) { Versioned.second.setHasDesignatedInits(true); Found = true; break; } } if (!Found) { VersionedVec.push_back({SwiftVersion, ObjCContextInfo()}); VersionedVec.back().second.setHasDesignatedInits(true); } } } void APINotesWriter::addGlobalVariable(std::optional Ctx, llvm::StringRef Name, const GlobalVariableInfo &Info, VersionTuple SwiftVersion) { IdentifierID VariableID = Implementation->getIdentifier(Name); ContextTableKey Key(Ctx, VariableID); Implementation->GlobalVariables[Key].push_back({SwiftVersion, Info}); } void APINotesWriter::addGlobalFunction(std::optional Ctx, llvm::StringRef Name, const GlobalFunctionInfo &Info, VersionTuple SwiftVersion) { IdentifierID NameID = Implementation->getIdentifier(Name); ContextTableKey Key(Ctx, NameID); Implementation->GlobalFunctions[Key].push_back({SwiftVersion, Info}); } void APINotesWriter::addEnumConstant(llvm::StringRef Name, const EnumConstantInfo &Info, VersionTuple SwiftVersion) { IdentifierID EnumConstantID = Implementation->getIdentifier(Name); Implementation->EnumConstants[EnumConstantID].push_back({SwiftVersion, Info}); } void APINotesWriter::addTag(std::optional Ctx, llvm::StringRef Name, const TagInfo &Info, VersionTuple SwiftVersion) { IdentifierID TagID = Implementation->getIdentifier(Name); ContextTableKey Key(Ctx, TagID); Implementation->Tags[Key].push_back({SwiftVersion, Info}); } void APINotesWriter::addTypedef(std::optional Ctx, llvm::StringRef Name, const TypedefInfo &Info, VersionTuple SwiftVersion) { IdentifierID TypedefID = Implementation->getIdentifier(Name); ContextTableKey Key(Ctx, TypedefID); Implementation->Typedefs[Key].push_back({SwiftVersion, Info}); } } // namespace api_notes } // namespace clang