https://github.com/RedNicStone created https://github.com/llvm/llvm-project/pull/207551
__attribute__((annotate(...))) already round-trips through ClangIR for functions and globals but is silently dropped for record (struct/class/union) declarations. Unlike LLVM IR, CIR retains a structured record type deep into the pipeline, so this metadata can be preserved. Emit a new module-scope cir.record_annotation op (name + annotations, using the existing #cir.annotation encoding) once per annotated RecordDecl The op is keyed by the record type's name so it correlates with the !cir.struct/!cir.union type in the IR. It has no LLVM IR equivalent and is erased during LLVM lowering. Closes the astRecordDeclAttr() marker at the record-layout site. The flag is retained for the type-construction sites. >From 774bef400b19d5a85b3c1993f960f62f3e198786 Mon Sep 17 00:00:00 2001 From: RedNicStone <[email protected]> Date: Sun, 5 Jul 2026 02:09:27 +0100 Subject: [PATCH] [CIR] Preserve annotate attributes on record declarations __attribute__((annotate(...))) already round-trips through ClangIR for functions and globals but is silently dropped for record (struct/class/union) declarations. Unlike LLVM IR, CIR retains a structured record type deep into the pipeline, so this metadata can be preserved. Emit a new module-scope cir.record_annotation op (name + annotations, using the existing #cir.annotation encoding) once per annotated RecordDecl The op is keyed by the record type's name so it correlates with the !cir.struct/!cir.union type in the IR. It has no LLVM IR equivalent and is erased during LLVM lowering. Closes the astRecordDeclAttr() marker at the record-layout site. The flag is retained for the type-construction sites. --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 28 +++++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenModule.cpp | 25 +++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenModule.h | 5 ++++ .../CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp | 2 +- .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 9 ++++++ clang/test/CIR/CodeGen/annotate-attribute.cpp | 18 ++++++++++++ 6 files changed, 86 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index e83fb81291007..615f509fcb605 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -3263,6 +3263,34 @@ def CIR_GetGlobalOp : CIR_Op<"get_global", [ }]; } +//===----------------------------------------------------------------------===// +// RecordAnnotationOp +//===----------------------------------------------------------------------===// + +def CIR_RecordAnnotationOp : CIR_Op<"record_annotation"> { + let summary = "Declares the source-level annotations on a record type"; + let description = [{ + The `cir.record_annotation` operation records the + `__attribute__((annotate(...)))` attributes attached to a + `clang::RecordDecl` (struct/class/union). It is emitted once per annotated + record, since `cir::RecordType` is uniqued by name/body and does not carry + an op-level attribute slot the way `cir.global`/`cir.func` do. + + `name` matches the record type's own `getName()`. `annotations` uses the + same `#cir.annotation` encoding as `cir.global`/`cir.func`. + + Example: + + ``` + cir.record_annotation "Tagged" [#cir.annotation<"type_ann">] + ``` + }]; + + let arguments = (ins StrAttr:$name, CIR_AnnotationArrayAttr:$annotations); + + let assemblyFormat = "$name $annotations attr-dict"; +} + //===----------------------------------------------------------------------===// // VTableAddrPointOp //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 7a05aed4c33e0..e9bf7f92cabe7 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -4172,6 +4172,31 @@ void CIRGenModule::addGlobalAnnotations(const ValueDecl *d, func.setAnnotationsAttr(builder.getArrayAttr(annotations)); } +void CIRGenModule::emitRecordAnnotations(const RecordDecl *rd, + cir::RecordType ty) { + if (!rd->hasAttr<AnnotateAttr>()) + return; + // Anonymous records have no type name to correlate the annotations with so + // there is nothing meaningful to emit. + mlir::StringAttr name = ty.getName(); + if (!name) + return; + + llvm::SmallVector<mlir::Attribute> annotations; + for (const auto *i : rd->specific_attrs<AnnotateAttr>()) + annotations.push_back(emitAnnotateAttr(i)); + + // Record layout can be computed while emitting a function. + mlir::OpBuilder::InsertionGuard guard(builder); + if (lastGlobalOp) + builder.setInsertionPointAfter(lastGlobalOp); + else + builder.setInsertionPointToStart(getModule().getBody()); + lastGlobalOp = + cir::RecordAnnotationOp::create(builder, getLoc(rd->getLocation()), name, + builder.getArrayAttr(annotations)); +} + void CIRGenModule::emitGlobalAnnotations() { for (const auto &[mangledName, vd] : deferredAnnotations) { mlir::Operation *gv = getGlobalValue(mangledName); diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 144f8c7b9f3e7..ec38d8c51ae52 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -933,6 +933,11 @@ class CIRGenModule : public CIRGenTypeCache { /// Add global annotations for a global value (GlobalOp or FuncOp). void addGlobalAnnotations(const clang::ValueDecl *d, mlir::Operation *gv); + /// Emit a `cir.record_annotation` op for a record declaration carrying + /// `__attribute__((annotate(...)))`. The op is keyed by \p ty's name so it + /// correlates with the record's `!cir.struct`/`!cir.union` type in the IR. + void emitRecordAnnotations(const clang::RecordDecl *rd, cir::RecordType ty); + private: /// Search \p currentClass and its non-virtual base subobjects for \p field, /// appending CIR field indices along the path from \p currentClass. diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp index e33b2065ecb67..3962d79e2c08d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp @@ -734,7 +734,7 @@ CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) { // Fill in the record *after* computing the base type. Filling in the body // signifies that the type is no longer opaque and record layout is complete, // but we may need to recursively layout rd while laying D out as a base type. - assert(!cir::MissingFeatures::astRecordDeclAttr()); + cgm.emitRecordAnnotations(rd, *ty); ty->complete(lowering.fieldTypes, lowering.packed, lowering.padded, lowering.unionPadding); diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 0e412090a16da..ce359f7140b09 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -3930,6 +3930,15 @@ mlir::LogicalResult CIRToLLVMBrOpLowering::matchAndRewrite( return mlir::LogicalResult::success(); } +mlir::LogicalResult CIRToLLVMRecordAnnotationOpLowering::matchAndRewrite( + cir::RecordAnnotationOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + // Record annotations carry source-level metadata that has no LLVM IR + // representation. Drop the op when lowering to LLVM. + rewriter.eraseOp(op); + return mlir::LogicalResult::success(); +} + mlir::LogicalResult CIRToLLVMGetMemberOpLowering::matchAndRewrite( cir::GetMemberOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { diff --git a/clang/test/CIR/CodeGen/annotate-attribute.cpp b/clang/test/CIR/CodeGen/annotate-attribute.cpp index ec83abb417473..41155d95a5299 100644 --- a/clang/test/CIR/CodeGen/annotate-attribute.cpp +++ b/clang/test/CIR/CodeGen/annotate-attribute.cpp @@ -33,6 +33,15 @@ struct __attribute__((annotate("type_ann"))) Tagged { static int sget() { return 7; } }; +// The annotate attribute on the record declaration itself is preserved as a +// module-scope cir.record_annotation op. This has no classic-CodeGen or +// LLVM-IR equivalent (LLVM IR cannot annotate a type), so it is deliberately +// only checked on the CIR output. Like the globals, record_annotation ops are +// emitted before any function, so these DAG checks live with the global block +// above (the Box<int> case is defined near the bottom of the file). +// CIR-DAG: cir.record_annotation "Tagged" [#cir.annotation<"type_ann">] +// CIR-DAG: cir.record_annotation "Box<int>" [#cir.annotation<"tpl_ann">] + // CIR: cir.func {{.*}} @{{.*Tagged.*get.*}}({{.*}}) {{.*}}[#cir.annotation<"method_ann">] // CIR: cir.func {{.*}} @{{.*Tagged.*sget.*}}() {{.*}}[#cir.annotation<"static_method_ann">] @@ -58,3 +67,12 @@ namespace ns { __attribute__((annotate("ns_global_ann"))) int g = 5; } + +// Class templates propagate the inheritable annotate attribute onto each instantiation, +// so the annotation is preserved per specialization. +// The corresponding CIR-DAG check lives in the global block near the top, +// since record_annotation ops are emitted before any function. +template <typename T> struct __attribute__((annotate("tpl_ann"))) Box { + T v; +}; +Box<int> boxInstance; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
