Skip to content

Conversation

Sharp0802
Copy link

This PR fixes an issue where the [[clang::annotate]] attribute on C++ constructors (CXXConstructorDecl) was not correctly lowered, and resolves a subsequent crash in the cir-to-llvm pipeline caused by the initial fix.

1. The Problem

There were two distinct problems:

  • Initial Bug: The [[clang::annotate]] attribute was completely ignored when applied to a C++ constructor. While it worked for regular functions, the specific code path for handling CXXConstructorDecl in CIRGenModule did not process this attribute.

  • Downstream Crash: After fixing the initial bug to generate the correct annotations and cir.global_annotations in the CIR dialect, the cir-translate tool would crash with a "redefinition of symbol" error for the annotation string (e.g., .str.annotation).

2. Analysis and Root Cause

  • Cause of Initial Bug: In CIRGenModule::emitGlobalDefinition, the code path for constructors and destructors branches to ABI->emitCXXStructor. This path was missing the logic to check for an AnnotateAttr and add it to the deferredAnnotations map, which is correctly handled for regular FunctionDecls.

  • Cause of Downstream Crash: The cir-to-llvm lowering pipeline has two mechanisms for handling annotations:

    1. The LoweringPrepare pass processes the cir.global_annotations attribute on the ModuleOp and generates the corresponding @llvm.global.annotations array and its associated global string constants.
    2. A later stage in the cir-translate binary also attempts to process the same cir.global_annotations attribute.

    The issue was that LoweringPreparePass did not consume (remove) the cir.global_annotations attribute after processing it. This left the attribute on the module, causing the later stage in cir-translate to re-process it, leading to the symbol redefinition crash.

3. Implementation

This PR addresses both issues with two key changes:

  1. In CIRGenModule.cpp:

    • The logic to handle AnnotateAttr has been added to the CXXConstructorDecl / CXXDestructorDecl path within emitGlobalDefinition. This ensures that constructor annotations are correctly identified and deferred for processing, just like regular functions.
  2. In LoweringPreparePass:

    • After the buildGlobalAnnotationValues() function successfully processes the cir.global_annotations attribute and generates the necessary LLVM globals, the pass now removes the cir.global_annotations attribute from the ModuleOp. This "consumes" the attribute, preventing any subsequent pass from redundantly processing it.

4. Verification

With these changes, a C++ constructor with a [[clang::annotate]] attribute is now correctly lowered through the entire pipeline:

  • The ClangIR (cir dialect) correctly contains both the local annotations on the cir.func and the module-level cir.global_annotations.
  • The cir-opt -cir-to-llvm pass successfully lowers this to the LLVM dialect.
  • The cir-translate tool successfully converts the LLVM dialect to LLVM IR text without crashing.
  • The final LLVM IR contains the expected @llvm.global.annotations metadata for the constructor.

This fix ensures that annotation metadata is preserved correctly and robustly for C++ constructors.

Copy link

github-actions bot commented Jun 23, 2025

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions cpp,c,h -- clang/include/clang/CIR/Dialect/IR/CIRTraits.h clang/test/CIR/CodeGen/X86/avx-shuffle-builtins.c clang/test/CIR/CodeGen/X86/avx2-builtins.c clang/test/CIR/CodeGen/X86/palignr.c clang/test/CIR/CodeGen/X86/sse3-builtins.c clang/test/CIR/CodeGen/aapcs-volatile-bitfields.c clang/test/CIR/CodeGen/builtin-bcopy.cpp clang/test/CIR/CodeGen/builtin-types.c clang/test/CIR/CodeGen/builtin-x86-pshufd.cpp clang/test/CIR/CodeGen/builtin-x86-pslldqi.cpp clang/test/CIR/CodeGen/class_cast.cpp clang/test/CIR/CodeGen/hot-attr.cpp clang/test/CIR/CodeGen/mms-bitfields.c clang/test/CIR/CodeGen/static_class_ref.cpp clang/test/CIR/CodeGen/type-trait.cpp clang/test/CIR/CodeGen/virtual-destructor-explicit-unqualified-call.cpp clang/test/CIR/Transforms/setjmp-longjmp-lower.c clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h clang/include/clang/CIR/Dialect/IR/CIRDataLayout.h clang/include/clang/CIR/Dialect/IR/CIRDialect.h clang/include/clang/CIR/MissingFeatures.h clang/lib/CIR/CodeGen/CIRGenAtomic.cpp clang/lib/CIR/CodeGen/CIRGenBuilder.h clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp clang/lib/CIR/CodeGen/CIRGenBuiltinNVPTX.cpp clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp clang/lib/CIR/CodeGen/CIRGenExpr.cpp clang/lib/CIR/CodeGen/CIRGenExprAgg.cpp clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp clang/lib/CIR/CodeGen/CIRGenFunction.cpp clang/lib/CIR/CodeGen/CIRGenFunction.h clang/lib/CIR/CodeGen/CIRGenModule.cpp clang/lib/CIR/CodeGen/CIRGenModule.h clang/lib/CIR/CodeGen/CIRRecordLayoutBuilder.cpp clang/lib/CIR/Dialect/IR/CIRDialect.cpp clang/lib/CIR/Dialect/Transforms/LifetimeCheck.cpp clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerTypes.cpp clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVMIR.cpp clang/test/CIR/CodeGen/AArch64/neon-misc.c clang/test/CIR/CodeGen/X86/avx-builtins.c clang/test/CIR/CodeGen/X86/avx512bw-builtins.c clang/test/CIR/CodeGen/X86/avx512dq-builtins.c clang/test/CIR/CodeGen/X86/avx512f-builtins.c clang/test/CIR/CodeGen/X86/avx512vl-builtins.c clang/test/CIR/CodeGen/X86/avx512vlbw-buiiltins.c clang/test/CIR/CodeGen/X86/avx512vldq-builtins.c clang/test/CIR/CodeGen/X86/sse-builtins.c clang/test/CIR/CodeGen/X86/sse2-builtins.c clang/test/CIR/CodeGen/X86/sse41-builtins.c clang/test/CIR/CodeGen/address-space-conversion.cpp clang/test/CIR/CodeGen/agg-copy.c clang/test/CIR/CodeGen/applearm64-array-cookies.cpp clang/test/CIR/CodeGen/array-init-destroy.cpp clang/test/CIR/CodeGen/array-init.c clang/test/CIR/CodeGen/array-init.cpp clang/test/CIR/CodeGen/array-new-init.cpp clang/test/CIR/CodeGen/array.cpp clang/test/CIR/CodeGen/assign-operator.cpp clang/test/CIR/CodeGen/builtin-setjmp-longjmp.c clang/test/CIR/CodeGen/builtins-memory.c clang/test/CIR/CodeGen/complex-cast.cpp clang/test/CIR/CodeGen/complex.cpp clang/test/CIR/CodeGen/dynamic-cast-relative-layout.cpp clang/test/CIR/CodeGen/dynamic-cast.cpp clang/test/CIR/CodeGen/globals.cpp clang/test/CIR/CodeGen/initlist-ptr-ptr.cpp clang/test/CIR/CodeGen/move.cpp clang/test/CIR/CodeGen/new.cpp clang/test/CIR/CodeGen/pointer-arith-ext.c clang/test/CIR/CodeGen/pointers.cpp clang/test/CIR/CodeGen/unary.cpp clang/test/CIR/CodeGen/var-arg-float.c clang/test/CIR/CodeGen/var-arg-scope.c clang/test/CIR/CodeGen/var-arg.c clang/test/CIR/CodeGen/vectype-ext.cpp clang/test/CIR/CodeGen/virtual-base-cast.cpp clang/test/CIR/CodeGen/vtt.cpp clang/test/CIR/Lowering/var-arg-x86_64.c clang/test/CIR/Transforms/mem2reg.c
View the diff from clang-format here.
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 5c01706447..ca55565f93 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -302,7 +302,7 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
            "Destructor shouldn't have explicit parameters");
     assert(ReturnValue.isNull() && "Destructor shouldn't have return value");
     if (useVirtualCall) {
-      CIRGenFunction* CGF = CGM.getCurrCIRGenFun();
+      CIRGenFunction *CGF = CGM.getCurrCIRGenFun();
       CGM.getCXXABI().emitVirtualDestructorCall(
           *CGF, dtor, Dtor_Complete, This.getAddress(),
           dyn_cast<CXXMemberCallExpr>(CE));

Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the detailed explanation, some comments but this mostly ready to go

@bcardosolopes
Copy link
Member

Seems like some clang-format is needed

@Sharp0802 Sharp0802 reopened this Aug 26, 2025
@bcardosolopes
Copy link
Member

looks like clang-format is failing, you also need to add a testcase

@Sharp0802
Copy link
Author

Testcases added:

  • clang/test/CIR/CodeGen/attr-annotate-constructor.cpp
  • clang/test/CIR/CodeGen/attr-annotate-destructor.cpp

clang-format passes for changed files.

@xlauko
Copy link
Collaborator

xlauko commented Sep 2, 2025

Is this also a problem in classical codegen, because at quick glance I don't see path there handling the AnnotateAttr for ctors/dtors. If it is somewhere deeper we should probably follow the classical codegen not adhoc patch like this.

@erichkeane might have better insight on how this works in clang?

@erichkeane
Copy link
Collaborator

Is this also a problem in classical codegen, because at quick glance I don't see path there handling the AnnotateAttr for ctors/dtors. If it is somewhere deeper we should probably follow the classical codegen not adhoc patch like this.

@erichkeane might have better insight on how this works in clang?

I don't really have any idea unfortunately, I haven't dug into the codegen parts of this. I wouldn't doubt that this needs help in Classic codegen either

if (isa<CXXConstructorDecl>(method) || isa<CXXDestructorDecl>(method))
if (isa<CXXConstructorDecl>(method) || isa<CXXDestructorDecl>(method)) {
if (fd->getAttr<AnnotateAttr>())
deferredAnnotations[getMangledName(gd)] = cast<ValueDecl>(d);
Copy link
Member

@bcardosolopes bcardosolopes Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you need to add this here? It might just work as is. EmitGlobal both in OG and in the incubator handles it generically for FunctionDecl

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got the same feeling, thats why I was asking the question above.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I'm just pointing out how this work (as an answer to your question) via a rhetoric question. @Sharp0802 please remove this bit, if that doesn't do the job, you need to debug and find out what you are missing elsewhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants