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

Reply via email to