[Clang] Diagnose conflict between always_inline/noinline attributes
Project: llvm/llvm-project | Language: C++ | Concepts: api-design, error-handling, refactoring, testing-strategy | Difficulty: mid
Link: https://github.com/llvm/llvm-project/pull/215173
The problem
Clang previously allowed both always_inline and noinline attributes to be applied to the same function or statement, either silently or resulting in a crash under assertions. This could happen, for example, through redeclarations, template specializations, or nested statement attributes. There was no diagnostic warning or error for this mutually exclusive combination, and attribute propagation between templates could also incorrectly transfer conflicting attributes.
Why it's non-trivial
A naive fix might only check for conflicts when both attributes are present on the same declaration, missing subtler cases:
- Attributes can be inherited across redeclarations or template specializations.
- Statement attributes can be nested, with inner and outer scopes, and may interact in complex ways.
- Some attributes are unrelated (e.g.,
noinlineandnomerge) and should not clobber each other. - The fix must avoid breaking unrelated attribute propagation and must be validated by tests.
The approach
1. Declare the attributes as mutually exclusive in the attribute table:
+def : MutualExclusions<[AlwaysInline, NoInline]>;
This line in Attr.td tells Clang's attribute system that AlwaysInline and NoInline cannot coexist. This enables automatic diagnostics for direct conflicts.
2. Correct attribute propagation in code generation for statement attributes:
In both CIRGenStmt.cpp and CGStmt.cpp, the logic for handling nested statement attributes is updated:
- bool noinline = false;
- bool alwaysinline = false;
+ bool noinline = inNoInlineAttributedStmt;
+ bool alwaysinline = inAlwaysInlineAttributedStmt;
This initializes the local attribute state from the outer (enclosing) attribute context, preserving unrelated attributes.
When encountering a new attribute, set and clear the conflicting one:
case attr::NoInline:
noinline = true;
+ alwaysinline = false;
break;
case attr::AlwaysInline:
alwaysinline = true;
+ noinline = false;
break;
When one attribute is set, the other is cleared, ensuring only one can be active at a time.
Add a runtime assertion as a safety net:
+ assert(!(alwaysinline && noinline) &&
+ "alwaysinline and noinline are mutually exclusive");
This assertion catches any logic errors during development.
3. Prevent propagation of conflicting attributes to explicit template specializations:
In SemaDecl.cpp, during attribute merging, skip propagation of always_inline or noinline to explicit specializations:
- if (isa<InferredNoReturnAttr>(I)) {
+ // Don't propagate inferred noreturn or conflicting inline attributes to
+ // explicit specializations.
+ if (isa<InferredNoReturnAttr>(I) || isa<AlwaysInlineAttr>(I) ||
+ isa<NoInlineAttr>(I)) {
if (auto *FD = dyn_cast<FunctionDecl>(New);
FD &&
FD->getTemplateSpecializationKind() == TSK_ExplicitSpecialization)
- continue; // Don't propagate inferred noreturn attributes to explicit
+ continue;
}
This prevents accidental inheritance of conflicting attributes when specializing templates.
4. Add and update tests to cover all cases:
- New and updated tests in
clang/test/CodeGen/attr-noinline-always-inline.cpp,clang/test/Sema/attr-noinline.cpp, and others check:- Diagnostics for conflicting attributes on the same declaration, on redeclarations, and on statements.
- Attribute propagation in template specializations.
- That unrelated attributes (like
nomerge) are not clobbered. - That only the correct attribute is present in the generated IR.
From the review thread
No notable review discussion on this PR.
Takeaways
- Declare attribute exclusivity in a central place: Use a mutual exclusion mechanism in your attribute system to automatically diagnose conflicts, rather than relying on scattered manual checks.
- Handle attribute propagation carefully: When attributes can be inherited (e.g., via templates or redeclarations), ensure that conflicting or context-specific attributes are not incorrectly propagated.
- Test all interaction surfaces: When changing attribute logic, add tests for direct application, redeclaration, template specialization, and nested statement scopes to catch subtle bugs.