llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Egor Zhdan (egorzhdan) <details> <summary>Changes</summary> This adds support for adding Clang attributes to C++ methods declared within C++ records by using API Notes. For instance: ``` Tags: - Name: IntWrapper Methods: - Name: getIncremented Availability: none ``` This is the first instance of something within a C++ record being annotated with API Notes, so it adds the necessary infra to make a C++ record an "API Notes context". Notably this does not add support for nested C++ tags. That will be added in a follow-up patch. rdar://131387880 --- Patch is 33.60 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/99512.diff 15 Files Affected: - (modified) clang/include/clang/APINotes/APINotesReader.h (+21) - (modified) clang/include/clang/APINotes/APINotesWriter.h (+8) - (modified) clang/include/clang/APINotes/Types.h (+7) - (modified) clang/lib/APINotes/APINotesFormat.h (+29) - (modified) clang/lib/APINotes/APINotesReader.cpp (+149) - (modified) clang/lib/APINotes/APINotesWriter.cpp (+72) - (modified) clang/lib/APINotes/APINotesYAMLCompiler.cpp (+94-70) - (modified) clang/lib/Sema/SemaAPINotes.cpp (+41-9) - (added) clang/test/APINotes/Inputs/Headers/Methods.apinotes (+8) - (added) clang/test/APINotes/Inputs/Headers/Methods.h (+14) - (modified) clang/test/APINotes/Inputs/Headers/Namespaces.apinotes (+3) - (modified) clang/test/APINotes/Inputs/Headers/Namespaces.h (+1) - (modified) clang/test/APINotes/Inputs/Headers/module.modulemap (+4) - (added) clang/test/APINotes/methods.cpp (+9) - (modified) clang/test/APINotes/namespaces.cpp (+5) ``````````diff diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h index 37a4ff7a69712..03657352c49a5 100644 --- a/clang/include/clang/APINotes/APINotesReader.h +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -141,6 +141,16 @@ class APINotesReader { ObjCSelectorRef Selector, bool IsInstanceMethod); + /// Look for information regarding the given C++ method in the given C++ tag + /// context. + /// + /// \param CtxID The ID that references the parent context, i.e. a C++ tag. + /// \param Name The name of the C++ method we're looking for. + /// + /// \returns Information about the method, if known. + VersionedInfo<CXXMethodInfo> lookupCXXMethod(ContextID CtxID, + llvm::StringRef Name); + /// Look for information regarding the given global variable. /// /// \param Name The name of the global variable. @@ -166,6 +176,17 @@ class APINotesReader { /// \returns information about the enumerator, if known. VersionedInfo<EnumConstantInfo> lookupEnumConstant(llvm::StringRef Name); + /// Look for the context ID of the given C++ tag. + /// + /// \param Name The name of the tag we're looking for. + /// \param ParentCtx The context in which this tag is declared, e.g. a C++ + /// namespace. + /// + /// \returns The ID, if known. + std::optional<ContextID> + lookupTagID(llvm::StringRef Name, + std::optional<Context> ParentCtx = std::nullopt); + /// Look for information regarding the given tag /// (struct/union/enum/C++ class). /// diff --git a/clang/include/clang/APINotes/APINotesWriter.h b/clang/include/clang/APINotes/APINotesWriter.h index e82dbc7c9540e..e0fe5eacef725 100644 --- a/clang/include/clang/APINotes/APINotesWriter.h +++ b/clang/include/clang/APINotes/APINotesWriter.h @@ -78,6 +78,14 @@ class APINotesWriter { bool IsInstanceMethod, const ObjCMethodInfo &Info, llvm::VersionTuple SwiftVersion); + /// Add information about a specific C++ method. + /// + /// \param CtxID The context in which this method resides, i.e. a C++ tag. + /// \param Name The name of the method. + /// \param Info Information about this method. + void addCXXMethod(ContextID CtxID, llvm::StringRef Name, + const CXXMethodInfo &Info, llvm::VersionTuple SwiftVersion); + /// Add information about a global variable. /// /// \param Name The name of this global variable. diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index b389aa8d56f16..c8e5e4df25d17 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -656,6 +656,12 @@ class GlobalFunctionInfo : public FunctionInfo { GlobalFunctionInfo() {} }; +/// Describes API notes data for a C++ method. +class CXXMethodInfo : public FunctionInfo { +public: + CXXMethodInfo() {} +}; + /// Describes API notes data for an enumerator. class EnumConstantInfo : public CommonEntityInfo { public: @@ -789,6 +795,7 @@ enum class ContextKind : uint8_t { ObjCClass = 0, ObjCProtocol = 1, Namespace = 2, + Tag = 3, }; struct Context { diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 42dfe7a773a97..cd6456dbe37b2 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -63,6 +63,10 @@ enum BlockID { /// about the method. OBJC_METHOD_BLOCK_ID, + /// The C++ method data block, which maps C++ (context id, method name) pairs + /// to information about the method. + CXX_METHOD_BLOCK_ID, + /// The Objective-C selector data block, which maps Objective-C /// selector names (# of pieces, identifier IDs) to the selector ID /// used in other tables. @@ -181,6 +185,20 @@ using ObjCMethodDataLayout = >; } // namespace objc_method_block +namespace cxx_method_block { +enum { + CXX_METHOD_DATA = 1, +}; + +using CXXMethodDataLayout = + llvm::BCRecordLayout<CXX_METHOD_DATA, // record ID + llvm::BCVBR<16>, // table offset within the blob (see + // below) + llvm::BCBlob // map from C++ (context id, name) + // tuples to C++ method information + >; +} // namespace cxx_method_block + namespace objc_selector_block { enum { OBJC_SELECTOR_DATA = 1, @@ -269,6 +287,17 @@ struct ContextTableKey { : parentContextID(parentContextID), contextKind(contextKind), contextID(contextID) {} + ContextTableKey(std::optional<ContextID> ParentContextID, ContextKind Kind, + uint32_t ContextID) + : parentContextID(ParentContextID ? ParentContextID->Value : -1), + contextKind(static_cast<uint8_t>(Kind)), contextID(ContextID) {} + + ContextTableKey(std::optional<Context> ParentContext, ContextKind Kind, + uint32_t ContextID) + : ContextTableKey(ParentContext ? std::make_optional(ParentContext->id) + : std::nullopt, + Kind, ContextID) {} + llvm::hash_code hashValue() const { return llvm::hash_value( std::tuple{parentContextID, contextKind, contextID}); diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 7600738374840..768fdc231bd57 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -473,6 +473,29 @@ class GlobalFunctionTableInfo } }; +/// Used to deserialize the on-disk C++ method table. +class CXXMethodTableInfo + : public VersionedTableInfo<CXXMethodTableInfo, + SingleDeclTableKey, CXXMethodInfo> { +public: + static internal_key_type ReadKey(const uint8_t *Data, unsigned Length) { + auto CtxID = endian::readNext<uint32_t, llvm::endianness::little>(Data); + auto NameID = endian::readNext<uint32_t, llvm::endianness::little>(Data); + return {CtxID, NameID}; + } + + hash_value_type ComputeHash(internal_key_type Key) { + return static_cast<size_t>(Key.hashValue()); + } + + static CXXMethodInfo readUnversioned(internal_key_type Key, + const uint8_t *&Data) { + CXXMethodInfo Info; + ReadFunctionInfo(Data, Info); + return Info; + } +}; + /// Used to deserialize the on-disk enumerator table. class EnumConstantTableInfo : public VersionedTableInfo<EnumConstantTableInfo, uint32_t, @@ -630,6 +653,12 @@ class APINotesReader::Implementation { /// The Objective-C method table. std::unique_ptr<SerializedObjCMethodTable> ObjCMethodTable; + using SerializedCXXMethodTable = + llvm::OnDiskIterableChainedHashTable<CXXMethodTableInfo>; + + /// The C++ method table. + std::unique_ptr<SerializedCXXMethodTable> CXXMethodTable; + using SerializedObjCSelectorTable = llvm::OnDiskIterableChainedHashTable<ObjCSelectorTableInfo>; @@ -683,6 +712,8 @@ class APINotesReader::Implementation { llvm::SmallVectorImpl<uint64_t> &Scratch); bool readObjCMethodBlock(llvm::BitstreamCursor &Cursor, llvm::SmallVectorImpl<uint64_t> &Scratch); + bool readCXXMethodBlock(llvm::BitstreamCursor &Cursor, + llvm::SmallVectorImpl<uint64_t> &Scratch); bool readObjCSelectorBlock(llvm::BitstreamCursor &Cursor, llvm::SmallVectorImpl<uint64_t> &Scratch); bool readGlobalVariableBlock(llvm::BitstreamCursor &Cursor, @@ -1140,6 +1171,81 @@ bool APINotesReader::Implementation::readObjCMethodBlock( return false; } +bool APINotesReader::Implementation::readCXXMethodBlock( + llvm::BitstreamCursor &Cursor, llvm::SmallVectorImpl<uint64_t> &Scratch) { + if (Cursor.EnterSubBlock(CXX_METHOD_BLOCK_ID)) + return true; + + llvm::Expected<llvm::BitstreamEntry> MaybeNext = Cursor.advance(); + if (!MaybeNext) { + // FIXME this drops the error on the floor. + consumeError(MaybeNext.takeError()); + return false; + } + llvm::BitstreamEntry Next = MaybeNext.get(); + while (Next.Kind != llvm::BitstreamEntry::EndBlock) { + if (Next.Kind == llvm::BitstreamEntry::Error) + return true; + + if (Next.Kind == llvm::BitstreamEntry::SubBlock) { + // Unknown sub-block, possibly for use by a future version of the + // API notes format. + if (Cursor.SkipBlock()) + return true; + + MaybeNext = Cursor.advance(); + if (!MaybeNext) { + // FIXME this drops the error on the floor. + consumeError(MaybeNext.takeError()); + return false; + } + Next = MaybeNext.get(); + continue; + } + + Scratch.clear(); + llvm::StringRef BlobData; + llvm::Expected<unsigned> MaybeKind = + Cursor.readRecord(Next.ID, Scratch, &BlobData); + if (!MaybeKind) { + // FIXME this drops the error on the floor. + consumeError(MaybeKind.takeError()); + return false; + } + unsigned Kind = MaybeKind.get(); + switch (Kind) { + case cxx_method_block::CXX_METHOD_DATA: { + // Already saw C++ method table. + if (CXXMethodTable) + return true; + + uint32_t tableOffset; + cxx_method_block::CXXMethodDataLayout::readRecord(Scratch, tableOffset); + auto base = reinterpret_cast<const uint8_t *>(BlobData.data()); + + CXXMethodTable.reset(SerializedCXXMethodTable::Create( + base + tableOffset, base + sizeof(uint32_t), base)); + break; + } + + default: + // Unknown record, possibly for use by a future version of the + // module format. + break; + } + + MaybeNext = Cursor.advance(); + if (!MaybeNext) { + // FIXME this drops the error on the floor. + consumeError(MaybeNext.takeError()); + return false; + } + Next = MaybeNext.get(); + } + + return false; +} + bool APINotesReader::Implementation::readObjCSelectorBlock( llvm::BitstreamCursor &Cursor, llvm::SmallVectorImpl<uint64_t> &Scratch) { if (Cursor.EnterSubBlock(OBJC_SELECTOR_BLOCK_ID)) @@ -1692,6 +1798,14 @@ APINotesReader::APINotesReader(llvm::MemoryBuffer *InputBuffer, } break; + case CXX_METHOD_BLOCK_ID: + if (!HasValidControlBlock || + Implementation->readCXXMethodBlock(Cursor, Scratch)) { + Failed = true; + return; + } + break; + case OBJC_SELECTOR_BLOCK_ID: if (!HasValidControlBlock || Implementation->readObjCSelectorBlock(Cursor, Scratch)) { @@ -1911,6 +2025,23 @@ auto APINotesReader::lookupObjCMethod(ContextID CtxID, ObjCSelectorRef Selector, return {Implementation->SwiftVersion, *Known}; } +auto APINotesReader::lookupCXXMethod(ContextID CtxID, llvm::StringRef Name) + -> VersionedInfo<CXXMethodInfo> { + if (!Implementation->CXXMethodTable) + return std::nullopt; + + std::optional<IdentifierID> NameID = Implementation->getIdentifier(Name); + if (!NameID) + return std::nullopt; + + auto Known = Implementation->CXXMethodTable->find( + SingleDeclTableKey(CtxID.Value, *NameID)); + if (Known == Implementation->CXXMethodTable->end()) + return std::nullopt; + + return {Implementation->SwiftVersion, *Known}; +} + auto APINotesReader::lookupGlobalVariable(llvm::StringRef Name, std::optional<Context> Ctx) -> VersionedInfo<GlobalVariableInfo> { @@ -1965,6 +2096,24 @@ auto APINotesReader::lookupEnumConstant(llvm::StringRef Name) return {Implementation->SwiftVersion, *Known}; } +auto APINotesReader::lookupTagID(llvm::StringRef Name, + std::optional<Context> ParentCtx) + -> std::optional<ContextID> { + if (!Implementation->ContextIDTable) + return std::nullopt; + + std::optional<IdentifierID> TagID = Implementation->getIdentifier(Name); + if (!TagID) + return std::nullopt; + + auto KnownID = Implementation->ContextIDTable->find( + ContextTableKey(ParentCtx, ContextKind::Tag, *TagID)); + if (KnownID == Implementation->ContextIDTable->end()) + return std::nullopt; + + return ContextID(*KnownID); +} + auto APINotesReader::lookupTag(llvm::StringRef Name, std::optional<Context> Ctx) -> VersionedInfo<TagInfo> { if (!Implementation->TagTable) diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 1090d3f20df21..e259c761591ba 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -70,6 +70,13 @@ class APINotesWriter::Implementation { llvm::SmallVector<std::pair<VersionTuple, ObjCMethodInfo>, 1>> ObjCMethods; + /// Information about C++ methods. + /// + /// Indexed by the context ID and name ID. + llvm::DenseMap<SingleDeclTableKey, + llvm::SmallVector<std::pair<VersionTuple, CXXMethodInfo>, 1>> + CXXMethods; + /// Mapping from selectors to selector ID. llvm::DenseMap<StoredObjCSelector, SelectorID> SelectorIDs; @@ -150,6 +157,7 @@ class APINotesWriter::Implementation { void writeContextBlock(llvm::BitstreamWriter &Stream); void writeObjCPropertyBlock(llvm::BitstreamWriter &Stream); void writeObjCMethodBlock(llvm::BitstreamWriter &Stream); + void writeCXXMethodBlock(llvm::BitstreamWriter &Stream); void writeObjCSelectorBlock(llvm::BitstreamWriter &Stream); void writeGlobalVariableBlock(llvm::BitstreamWriter &Stream); void writeGlobalFunctionBlock(llvm::BitstreamWriter &Stream); @@ -181,6 +189,7 @@ void APINotesWriter::Implementation::writeToStream(llvm::raw_ostream &OS) { writeContextBlock(Stream); writeObjCPropertyBlock(Stream); writeObjCMethodBlock(Stream); + writeCXXMethodBlock(Stream); writeObjCSelectorBlock(Stream); writeGlobalVariableBlock(Stream); writeGlobalFunctionBlock(Stream); @@ -765,6 +774,34 @@ class ObjCMethodTableInfo emitFunctionInfo(OS, OMI); } }; + +/// Used to serialize the on-disk C++ method table. +class CXXMethodTableInfo + : public VersionedTableInfo<CXXMethodTableInfo, SingleDeclTableKey, + CXXMethodInfo> { +public: + unsigned getKeyLength(key_type_ref) { + return sizeof(uint32_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<uint32_t>(Key.parentContextID); + writer.write<uint32_t>(Key.nameID); + } + + hash_value_type ComputeHash(key_type_ref key) { + return static_cast<size_t>(key.hashValue()); + } + + unsigned getUnversionedInfoSize(const CXXMethodInfo &OMI) { + return getFunctionInfoSize(OMI); + } + + void emitUnversionedInfo(raw_ostream &OS, const CXXMethodInfo &OMI) { + emitFunctionInfo(OS, OMI); + } +}; } // namespace void APINotesWriter::Implementation::writeObjCMethodBlock( @@ -794,6 +831,33 @@ void APINotesWriter::Implementation::writeObjCMethodBlock( } } +void APINotesWriter::Implementation::writeCXXMethodBlock( + llvm::BitstreamWriter &Stream) { + llvm::BCBlockRAII Scope(Stream, CXX_METHOD_BLOCK_ID, 3); + + if (CXXMethods.empty()) + return; + + { + llvm::SmallString<4096> HashTableBlob; + uint32_t Offset; + { + llvm::OnDiskChainedHashTableGenerator<CXXMethodTableInfo> Generator; + for (auto &OM : CXXMethods) + 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<uint32_t>(BlobStream, 0, + llvm::endianness::little); + Offset = Generator.Emit(BlobStream); + } + + cxx_method_block::CXXMethodDataLayout CXXMethodData(Stream); + CXXMethodData.emit(Scratch, Offset, HashTableBlob); + } +} + namespace { /// Used to serialize the on-disk Objective-C selector table. class ObjCSelectorTableInfo { @@ -1344,6 +1408,14 @@ void APINotesWriter::addObjCMethod(ContextID CtxID, ObjCSelectorRef Selector, } } +void APINotesWriter::addCXXMethod(ContextID CtxID, llvm::StringRef Name, + const CXXMethodInfo &Info, + VersionTuple SwiftVersion) { + IdentifierID NameID = Implementation->getIdentifier(Name); + SingleDeclTableKey Key(CtxID.Value, NameID); + Implementation->CXXMethods[Key].push_back({SwiftVersion, Info}); +} + void APINotesWriter::addGlobalVariable(std::optional<Context> Ctx, llvm::StringRef Name, const GlobalVariableInfo &Info, diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 870b64e3b7a9b..060e1fdaf2fd9 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -420,6 +420,7 @@ struct Tag { std::optional<bool> FlagEnum; std::optional<EnumConvenienceAliasKind> EnumConvenienceKind; std::optional<bool> SwiftCopyable; + FunctionsSeq Methods; }; typedef std::vector<Tag> TagsSeq; @@ -454,6 +455,7 @@ template <> struct MappingTraits<Tag> { IO.mapOptional("FlagEnum", T.FlagEnum); IO.mapOptional("EnumKind", T.EnumConvenienceKind); IO.mapOptional("SwiftCopyable", T.SwiftCopyable); + IO.mapOptional("Methods", T.Methods); } }; } // namespace yaml @@ -874,6 +876,96 @@ class YAMLConverter { TheNamespace.Items, SwiftVersion); } + void convertFunction(const Function &Function, FunctionInfo &FI) { + convertAvailability(Function.Availability, FI, Function.Name); + FI.setSwiftPrivate(Function.SwiftPrivate); + FI.SwiftName = std::string(Function.SwiftName); + convertParams(Function.Params, FI); + convertNullability(Function.Nullability, Function.NullabilityOfRet, FI, + Function.Name); + FI.ResultType = std::string(Function.ResultType); + FI.setRetainCountConvention(Function.RetainCountConvention); + } + + void convertTagContext(std::optional<Context> ParentContext, const Tag &T, + VersionTuple SwiftVersion) { + TagInfo TI; + std::optional<ContextID> ParentContextID = + ParentContext ? std::optional<ContextID>(ParentContext->id) + : std::nullopt; + convertCommonType(T, TI, T.Name); + + if ((T.SwiftRetainOp || T.SwiftReleaseOp) && !T.SwiftImportAs) { + emitError(llvm::Twine("should declare SwiftImportAs to use " + "SwiftRetainOp and SwiftReleaseOp (for ") + + T.Name + ")"); + return; + } + if (T.SwiftReleaseOp.has_value() != T.SwiftRetainOp.has_value()) { + emitError(llvm::Twine("should declare both SwiftReleaseOp and " + "SwiftRetainOp (for ") + + T.Name + ")"); + return; + } + + if (T.SwiftImportAs) + TI.SwiftImportAs = T.SwiftImportAs; + if (T.SwiftRetainOp) + TI.SwiftRetainOp = T.SwiftRetainOp; + if (T.SwiftReleaseOp) + TI.SwiftReleaseOp = T.SwiftReleaseOp; + + if (T.SwiftCopyable) + TI.setSwiftCopyable(T.SwiftCopyable); + + if (T.EnumConvenienceKind) { + if (T.EnumExtensibility) { + emitError( + llvm::Twine("cannot mix EnumKind and EnumExtensibility (for ") + + T.Name + ")"); + return; + } + if (T.FlagEnum) { + emitError(llvm::Twine("cannot mix EnumKind and FlagEnum (for ") + + T.Name + ")"); + return; + } + switch (*T.EnumConvenienceKind) { + case EnumConvenienceAliasKind::None: + TI.EnumExtensibility = EnumExtensibilityKind::None; + TI.setFlagEnum(false); + break; + case EnumConvenienceAliasKind::CFEnum: + TI.EnumExtensibility = EnumExtensibilityKind::Open; + TI.setFlagEnum(false); + break; + case EnumConvenienceAliasKind::CFOptions: + TI.EnumExtensibility = EnumExtensibilityKind::Open; + TI.setFlagEnum(true); + break; + case EnumConvenienceAliasKind::CFClosedEnum: + TI.EnumExtensibility = EnumExtensibilityKind::Closed; + TI.setFlagEnum(false); + break; + ... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/99512 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits