llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Egor Zhdan (egorzhdan) <details> <summary>Changes</summary> This allows annotating fields of C/C++ structs using API Notes. Previously API Notes supported Objective-C properties, but not fields. rdar://131548377 --- Patch is 22.46 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/104088.diff 12 Files Affected: - (modified) clang/include/clang/APINotes/APINotesReader.h (+7) - (modified) clang/include/clang/APINotes/APINotesWriter.h (+8) - (modified) clang/include/clang/APINotes/Types.h (+6) - (modified) clang/lib/APINotes/APINotesFormat.h (+19-1) - (modified) clang/lib/APINotes/APINotesReader.cpp (+130) - (modified) clang/lib/APINotes/APINotesWriter.cpp (+73) - (modified) clang/lib/APINotes/APINotesYAMLCompiler.cpp (+52-12) - (modified) clang/lib/Sema/SemaAPINotes.cpp (+20) - (added) clang/test/APINotes/Inputs/Headers/Fields.apinotes (+23) - (added) clang/test/APINotes/Inputs/Headers/Fields.h (+21) - (modified) clang/test/APINotes/Inputs/Headers/module.modulemap (+5) - (added) clang/test/APINotes/fields.cpp (+17) ``````````diff diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h index 03657352c49a59..baf63340640242 100644 --- a/clang/include/clang/APINotes/APINotesReader.h +++ b/clang/include/clang/APINotes/APINotesReader.h @@ -141,6 +141,13 @@ class APINotesReader { ObjCSelectorRef Selector, bool IsInstanceMethod); + /// Look for information regarding the given field of a C struct. + /// + /// \param Name The name of the field. + /// + /// \returns information about the field, if known. + VersionedInfo<FieldInfo> lookupField(ContextID CtxID, llvm::StringRef Name); + /// Look for information regarding the given C++ method in the given C++ tag /// context. /// diff --git a/clang/include/clang/APINotes/APINotesWriter.h b/clang/include/clang/APINotes/APINotesWriter.h index e0fe5eacef7258..3cc16c3d959faf 100644 --- a/clang/include/clang/APINotes/APINotesWriter.h +++ b/clang/include/clang/APINotes/APINotesWriter.h @@ -86,6 +86,14 @@ class APINotesWriter { void addCXXMethod(ContextID CtxID, llvm::StringRef Name, const CXXMethodInfo &Info, llvm::VersionTuple SwiftVersion); + /// Add information about a specific C record field. + /// + /// \param CtxID The context in which this field resides, i.e. a C/C++ tag. + /// \param Name The name of the field. + /// \param Info Information about this field. + void addField(ContextID CtxID, llvm::StringRef Name, const FieldInfo &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 f972d0cf26640d..89889910d1a073 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/C++ record field. +class FieldInfo : public VariableInfo { +public: + FieldInfo() {} +}; + /// Describes API notes data for a C++ method. class CXXMethodInfo : public FunctionInfo { public: diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index fba5f4e8907dae..d724a9ea471b54 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -24,7 +24,7 @@ const uint16_t VERSION_MAJOR = 0; /// API notes file minor version number. /// /// When the format changes IN ANY WAY, this number should be incremented. -const uint16_t VERSION_MINOR = 29; // SwiftConformsTo +const uint16_t VERSION_MINOR = 30; // fields const uint8_t kSwiftCopyable = 1; const uint8_t kSwiftNonCopyable = 2; @@ -72,6 +72,10 @@ enum BlockID { /// used in other tables. OBJC_SELECTOR_BLOCK_ID, + /// The fields data block, which maps names fields of C records to + /// information about the field. + FIELD_BLOCK_ID, + /// The global variables data block, which maps global variable names to /// information about the global variable. GLOBAL_VARIABLE_BLOCK_ID, @@ -199,6 +203,20 @@ using CXXMethodDataLayout = >; } // namespace cxx_method_block +namespace field_block { +enum { + FIELD_DATA = 1, +}; + +using FieldDataLayout = + llvm::BCRecordLayout<FIELD_DATA, // record ID + llvm::BCVBR<16>, // table offset within the blob (see + // below) + llvm::BCBlob // map from C (context id, name) + // tuples to C field information + >; +} // namespace field_block + namespace objc_selector_block { enum { OBJC_SELECTOR_DATA = 1, diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index c05fdffe4a071b..a79156ccf732c2 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -299,6 +299,28 @@ class ObjCPropertyTableInfo } }; +/// Used to deserialize the on-disk C record field table. +class FieldTableInfo + : public VersionedTableInfo<FieldTableInfo, SingleDeclTableKey, FieldInfo> { +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 FieldInfo readUnversioned(internal_key_type Key, + const uint8_t *&Data) { + FieldInfo Info; + ReadVariableInfo(Data, Info); + return Info; + } +}; + /// Read serialized ParamInfo. void ReadParamInfo(const uint8_t *&Data, ParamInfo &Info) { ReadVariableInfo(Data, Info); @@ -653,6 +675,12 @@ class APINotesReader::Implementation { /// The Objective-C property table. std::unique_ptr<SerializedObjCPropertyTable> ObjCPropertyTable; + using SerializedFieldTable = + llvm::OnDiskIterableChainedHashTable<FieldTableInfo>; + + /// The C record field table. + std::unique_ptr<SerializedFieldTable> FieldTable; + using SerializedObjCMethodTable = llvm::OnDiskIterableChainedHashTable<ObjCMethodTableInfo>; @@ -720,6 +748,8 @@ class APINotesReader::Implementation { llvm::SmallVectorImpl<uint64_t> &Scratch); bool readCXXMethodBlock(llvm::BitstreamCursor &Cursor, llvm::SmallVectorImpl<uint64_t> &Scratch); + bool readFieldBlock(llvm::BitstreamCursor &Cursor, + llvm::SmallVectorImpl<uint64_t> &Scratch); bool readObjCSelectorBlock(llvm::BitstreamCursor &Cursor, llvm::SmallVectorImpl<uint64_t> &Scratch); bool readGlobalVariableBlock(llvm::BitstreamCursor &Cursor, @@ -1252,6 +1282,81 @@ bool APINotesReader::Implementation::readCXXMethodBlock( return false; } +bool APINotesReader::Implementation::readFieldBlock( + llvm::BitstreamCursor &Cursor, llvm::SmallVectorImpl<uint64_t> &Scratch) { + if (Cursor.EnterSubBlock(FIELD_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 field_block::FIELD_DATA: { + // Already saw C++ method table. + if (FieldTable) + return true; + + uint32_t tableOffset; + field_block::FieldDataLayout::readRecord(Scratch, tableOffset); + auto base = reinterpret_cast<const uint8_t *>(BlobData.data()); + + FieldTable.reset(SerializedFieldTable::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)) @@ -1812,6 +1917,14 @@ APINotesReader::APINotesReader(llvm::MemoryBuffer *InputBuffer, } break; + case FIELD_BLOCK_ID: + if (!HasValidControlBlock || + Implementation->readFieldBlock(Cursor, Scratch)) { + Failed = true; + return; + } + break; + case OBJC_SELECTOR_BLOCK_ID: if (!HasValidControlBlock || Implementation->readObjCSelectorBlock(Cursor, Scratch)) { @@ -2031,6 +2144,23 @@ auto APINotesReader::lookupObjCMethod(ContextID CtxID, ObjCSelectorRef Selector, return {Implementation->SwiftVersion, *Known}; } +auto APINotesReader::lookupField(ContextID CtxID, llvm::StringRef Name) + -> VersionedInfo<FieldInfo> { + if (!Implementation->FieldTable) + return std::nullopt; + + std::optional<IdentifierID> NameID = Implementation->getIdentifier(Name); + if (!NameID) + return std::nullopt; + + auto Known = Implementation->FieldTable->find( + SingleDeclTableKey(CtxID.Value, *NameID)); + if (Known == Implementation->FieldTable->end()) + return std::nullopt; + + return {Implementation->SwiftVersion, *Known}; +} + auto APINotesReader::lookupCXXMethod(ContextID CtxID, llvm::StringRef Name) -> VersionedInfo<CXXMethodInfo> { if (!Implementation->CXXMethodTable) diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index cf3a0bee393eee..fe3e6f53764e89 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -62,6 +62,13 @@ class APINotesWriter::Implementation { llvm::SmallVector<std::pair<VersionTuple, ObjCPropertyInfo>, 1>> ObjCProperties; + /// Information about C record fields. + /// + /// Indexed by the context ID and name ID. + llvm::DenseMap<SingleDeclTableKey, + llvm::SmallVector<std::pair<VersionTuple, FieldInfo>, 1>> + Fields; + /// Information about Objective-C methods. /// /// Indexed by the context ID, selector ID, and Boolean (stored as a char) @@ -158,6 +165,7 @@ class APINotesWriter::Implementation { void writeObjCPropertyBlock(llvm::BitstreamWriter &Stream); void writeObjCMethodBlock(llvm::BitstreamWriter &Stream); void writeCXXMethodBlock(llvm::BitstreamWriter &Stream); + void writeFieldBlock(llvm::BitstreamWriter &Stream); void writeObjCSelectorBlock(llvm::BitstreamWriter &Stream); void writeGlobalVariableBlock(llvm::BitstreamWriter &Stream); void writeGlobalFunctionBlock(llvm::BitstreamWriter &Stream); @@ -190,6 +198,7 @@ void APINotesWriter::Implementation::writeToStream(llvm::raw_ostream &OS) { writeObjCPropertyBlock(Stream); writeObjCMethodBlock(Stream); writeCXXMethodBlock(Stream); + writeFieldBlock(Stream); writeObjCSelectorBlock(Stream); writeGlobalVariableBlock(Stream); writeGlobalFunctionBlock(Stream); @@ -858,6 +867,62 @@ void APINotesWriter::Implementation::writeCXXMethodBlock( } } +namespace { +/// Used to serialize the on-disk C field table. +class FieldTableInfo + : public VersionedTableInfo<FieldTableInfo, SingleDeclTableKey, FieldInfo> { +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 FieldInfo &FI) { + return getVariableInfoSize(FI); + } + + void emitUnversionedInfo(raw_ostream &OS, const FieldInfo &FI) { + emitVariableInfo(OS, FI); + } +}; +} // namespace + +void APINotesWriter::Implementation::writeFieldBlock( + llvm::BitstreamWriter &Stream) { + llvm::BCBlockRAII Scope(Stream, FIELD_BLOCK_ID, 3); + + if (Fields.empty()) + return; + + { + llvm::SmallString<4096> HashTableBlob; + uint32_t Offset; + { + llvm::OnDiskChainedHashTableGenerator<FieldTableInfo> Generator; + for (auto &MD : Fields) + Generator.insert(MD.first, MD.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); + } + + field_block::FieldDataLayout FieldData(Stream); + FieldData.emit(Scratch, Offset, HashTableBlob); + } +} + namespace { /// Used to serialize the on-disk Objective-C selector table. class ObjCSelectorTableInfo { @@ -1423,6 +1488,14 @@ void APINotesWriter::addCXXMethod(ContextID CtxID, llvm::StringRef Name, Implementation->CXXMethods[Key].push_back({SwiftVersion, Info}); } +void APINotesWriter::addField(ContextID CtxID, llvm::StringRef Name, + const FieldInfo &Info, + VersionTuple SwiftVersion) { + IdentifierID NameID = Implementation->getIdentifier(Name); + SingleDeclTableKey Key(CtxID.Value, NameID); + Implementation->Fields[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 2205686c4d15c3..16fd59244086fd 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -405,6 +405,38 @@ template <> struct ScalarEnumerationTraits<EnumConvenienceAliasKind> { } // namespace yaml } // namespace llvm +namespace { +struct Field { + StringRef Name; + std::optional<NullabilityKind> Nullability; + AvailabilityItem Availability; + std::optional<bool> SwiftPrivate; + StringRef SwiftName; + StringRef Type; +}; + +typedef std::vector<Field> FieldsSeq; +} // namespace + +LLVM_YAML_IS_SEQUENCE_VECTOR(Field) + +namespace llvm { +namespace yaml { +template <> struct MappingTraits<Field> { + static void mapping(IO &IO, Field &F) { + IO.mapRequired("Name", F.Name); + IO.mapOptional("Nullability", F.Nullability, std::nullopt); + IO.mapOptional("Availability", F.Availability.Mode, + APIAvailability::Available); + IO.mapOptional("AvailabilityMsg", F.Availability.Msg, StringRef("")); + IO.mapOptional("SwiftPrivate", F.SwiftPrivate); + IO.mapOptional("SwiftName", F.SwiftName, StringRef("")); + IO.mapOptional("Type", F.Type, StringRef("")); + } +}; +} // namespace yaml +} // namespace llvm + namespace { struct Tag; typedef std::vector<Tag> TagsSeq; @@ -425,6 +457,7 @@ struct Tag { std::optional<EnumConvenienceAliasKind> EnumConvenienceKind; std::optional<bool> SwiftCopyable; FunctionsSeq Methods; + FieldsSeq Fields; /// Tags that are declared within the current tag. Only the tags that have /// corresponding API Notes will be listed. @@ -463,6 +496,7 @@ template <> struct MappingTraits<Tag> { IO.mapOptional("EnumKind", T.EnumConvenienceKind); IO.mapOptional("SwiftCopyable", T.SwiftCopyable); IO.mapOptional("Methods", T.Methods); + IO.mapOptional("Fields", T.Fields); IO.mapOptional("Tags", T.Tags); } }; @@ -793,6 +827,16 @@ class YAMLConverter { SwiftVersion); } + template <typename T> + void convertVariable(const T &Entity, VariableInfo &VI) { + convertAvailability(Entity.Availability, VI, Entity.Name); + VI.setSwiftPrivate(Entity.SwiftPrivate); + VI.SwiftName = std::string(Entity.SwiftName); + if (Entity.Nullability) + VI.setNullabilityAudited(*Entity.Nullability); + VI.setType(std::string(Entity.Type)); + } + void convertContext(std::optional<ContextID> ParentContextID, const Class &C, ContextKind Kind, VersionTuple SwiftVersion) { // Write the class. @@ -848,14 +892,9 @@ class YAMLConverter { // Translate from Property into ObjCPropertyInfo. ObjCPropertyInfo PI; - convertAvailability(Property.Availability, PI, Property.Name); - PI.setSwiftPrivate(Property.SwiftPrivate); - PI.SwiftName = std::string(Property.SwiftName); - if (Property.Nullability) - PI.setNullabilityAudited(*Property.Nullability); + convertVariable(Property, PI); if (Property.SwiftImportAsAccessors) PI.setSwiftImportAsAccessors(*Property.SwiftImportAsAccessors); - PI.setType(std::string(Property.Type)); // Add both instance and class properties with this name. if (Property.Kind) { @@ -970,6 +1009,12 @@ class YAMLConverter { CI, SwiftVersion); Context TagCtx(TagCtxID, ContextKind::Tag); + for (const auto &Field : T.Fields) { + FieldInfo FI; + convertVariable(Field, FI); + Writer.addField(TagCtxID, Field.Name, FI, SwiftVersion); + } + for (const auto &CXXMethod : T.Methods) { CXXMethodInfo MI; convertFunction(CXXMethod, MI); @@ -1037,12 +1082,7 @@ class YAMLConverter { } GlobalVariableInfo GVI; - convertAvailability(Global.Availability, GVI, Global.Name); - GVI.setSwiftPrivate(Global.SwiftPrivate); - GVI.SwiftName = std::string(Global.SwiftName); - if (Global.Nullability) - GVI.setNullabilityAudited(*Global.Nullability); - GVI.setType(std::string(Global.Type)); + convertVariable(Global, GVI); Writer.addGlobalVariable(Ctx, Global.Name, GVI, SwiftVersion); } diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp index 65b56bd1c8efc7..d0236d08c98e68 100644 --- a/clang/lib/Sema/SemaAPINotes.cpp +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -433,6 +433,15 @@ static void ProcessAPINotes(Sema &S, VarDecl *D, metadata); } +/// Process API notes for a C field. +static void ProcessAPINotes(Sema &S, FieldDecl *D, + const api_notes::FieldInfo &Info, + VersionedInfoMetadata metadata) { + // Handle common entity information. + ProcessAPINotes(S, D, static_cast<const api_notes::VariableInfo &>(Info), + metadata); +} + /// Process API notes for an Objective-C property. static void ProcessAPINotes(Sema &S, ObjCPropertyDecl *D, const api_notes::ObjCPropertyInfo &Info, @@ -1062,6 +1071,17 @@ void Sema::ProcessAPINotes(Decl *D) { } } + if (auto Field = dyn_cast<FieldDecl>(D)) { + if (!Field->isUnnamedBitField() && !Field->isAnonymousStructOrUnion()) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { + if (auto Context = UnwindTagContext(TagContext, APINotes)) { + auto Info = Reader->lookupField(Context->id, Field->getName()); + ProcessVersionedAPINotes(*this, Field, Info); + } + } + } + } + if (auto Tag = dyn_cast<TagDecl>(D)) { for (auto Reader : APINotes.findAPINotes(D->getLocation())) { if (auto Context = UnwindTagContext(TagContext, APINotes)) { diff --git a/clang/test/APINotes/Inputs/Headers/Fields.apinotes b/clang/test/APINotes/Inputs/Headers/Fields.apinotes new file mode 100644 index 00000000000000..931da52ba29d18 --- /dev/null +++ b/clang/test/APINotes/Inputs/Headers/Fields.apinotes @@ -0,0 +1,23 @@ +--- +Name: Fields +Tags: +- Name: IntWrapper + Fields: + - Name: value + Availability: none + AvailabilityMsg: "oh no" +- Name: Outer + Tags: + - Name: Inner + Fields: + - Name: value + Availability: none + AvailabilityMsg: "oh no 2" + Methods: + - Name: value + Availability: none + Availability... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/104088 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits