[clang] [Clang][TBAA] Represent unions in new struct-path TBAA (PR #226811)

Dávid Bolvanský via cfe-commits cfe-commits at lists.llvm.org
Sun Sep 27 10:15:02 PDT 2026


https://github.com/davidbolvansky created https://github.com/llvm/llvm-project/pull/226811

Whole-union accesses currently fall back to omnipotent-char TBAA, even with
new struct-path TBAA. This loses the outer access path and can make a union
copy appear to clobber an unrelated field.

Represent unions as aggregate nodes in the new format, with every member at
offset zero. Whole-union accesses then retain their type and range.

This is sound because overlapping members are recorded at the same offset, so
a whole-union access still aliases every direct and nested member. Accesses
through union member expressions remain conservative, and old TBAA is
unchanged. The test includes direct and nested escaped-member controls.

The motivating GCC `alias-access-path-13.c` case now folds the unaffected
sibling load.

Tests:

- `ninja check-clang-codegen` (6157 discovered, 2313 passed, 3 expected failures)
- focused `tbaa.cpp`, `tbaa-struct.cpp`, and `tbaa-union-access.c`


>From 83dd88f03dd9024bd1cef2b34bb4df4ba0727e3a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?D=C3=A1vid=20Bolvansk=C3=BD?= <david.bolvansky at gmail.com>
Date: Sun, 27 Sep 2026 19:14:31 +0200
Subject: [PATCH] [Clang][TBAA] Represent unions in new struct-path TBAA

---
 clang/lib/CodeGen/CodeGenTBAA.cpp      | 35 ++++++++++-----
 clang/test/CodeGen/tbaa-struct.cpp     | 10 +++--
 clang/test/CodeGen/tbaa-union-access.c | 61 ++++++++++++++++++++++++++
 3 files changed, 91 insertions(+), 15 deletions(-)
 create mode 100644 clang/test/CodeGen/tbaa-union-access.c

diff --git a/clang/lib/CodeGen/CodeGenTBAA.cpp b/clang/lib/CodeGen/CodeGenTBAA.cpp
index 1854df7c7c0f11..682c7f0096dd88 100644
--- a/clang/lib/CodeGen/CodeGenTBAA.cpp
+++ b/clang/lib/CodeGen/CodeGenTBAA.cpp
@@ -141,7 +141,7 @@ static bool TypeHasMayAlias(QualType QTy) {
 }
 
 /// Check if the given type is a valid base type to be used in access tags.
-static bool isValidBaseType(QualType QTy) {
+static bool isValidBaseType(QualType QTy, bool NewStructPathTBAA) {
   if (const auto *RD = QTy->getAsRecordDecl()) {
     // Incomplete types are not valid base access types.
     if (!RD->isCompleteDefinition())
@@ -149,8 +149,8 @@ static bool isValidBaseType(QualType QTy) {
     if (RD->hasFlexibleArrayMember())
       return false;
     // RD can be struct, union, class, interface or enum.
-    // For now, we only handle struct and class.
-    if (RD->isStruct() || RD->isClass())
+    // The new format can represent overlapping union members.
+    if (RD->isStruct() || RD->isClass() || (NewStructPathTBAA && RD->isUnion()))
       return true;
   }
   return false;
@@ -389,7 +389,7 @@ llvm::MDNode *CodeGenTBAA::getTypeInfo(QualType QTy) {
   // be considered may-alias too.
   // TODO: Combine getTypeInfo() and getValidBaseTypeInfo() into a single
   // function.
-  if (isValidBaseType(QTy))
+  if (isValidBaseType(QTy, CodeGenOpts.NewStructPathTBAA))
     return getValidBaseTypeInfo(QTy);
 
   const Type *Ty = Context.getCanonicalType(QTy).getTypePtr();
@@ -538,9 +538,10 @@ llvm::MDNode *CodeGenTBAA::getBaseTypeInfoHelper(const Type *Ty) {
         const CXXRecordDecl *BaseRD = BaseQTy->getAsCXXRecordDecl();
         if (BaseRD->isEmpty())
           continue;
-        llvm::MDNode *TypeNode = isValidBaseType(BaseQTy)
-                                     ? getValidBaseTypeInfo(BaseQTy)
-                                     : getTypeInfo(BaseQTy);
+        llvm::MDNode *TypeNode =
+            isValidBaseType(BaseQTy, CodeGenOpts.NewStructPathTBAA)
+                ? getValidBaseTypeInfo(BaseQTy)
+                : getTypeInfo(BaseQTy);
         if (!TypeNode)
           return nullptr;
         uint64_t Offset = Layout.getBaseClassOffset(BaseRD).getQuantity();
@@ -563,9 +564,10 @@ llvm::MDNode *CodeGenTBAA::getBaseTypeInfoHelper(const Type *Ty) {
       if (Field->isZeroSize(Context) || Field->isUnnamedBitField())
         continue;
       QualType FieldQTy = Field->getType();
-      llvm::MDNode *TypeNode = isValidBaseType(FieldQTy)
-                                   ? getValidBaseTypeInfo(FieldQTy)
-                                   : getTypeInfo(FieldQTy);
+      llvm::MDNode *TypeNode =
+          isValidBaseType(FieldQTy, CodeGenOpts.NewStructPathTBAA)
+              ? getValidBaseTypeInfo(FieldQTy)
+              : getTypeInfo(FieldQTy);
       if (!TypeNode)
         return nullptr;
 
@@ -576,6 +578,12 @@ llvm::MDNode *CodeGenTBAA::getBaseTypeInfoHelper(const Type *Ty) {
                                                         TypeNode));
     }
 
+    // New struct-path TBAA represents all union members at offset zero. Keep
+    // their actual types so a whole-union access aliases pointers to any of
+    // its members. Accesses through union member expressions remain may-alias.
+    assert((!RD->isUnion() || CodeGenOpts.NewStructPathTBAA) &&
+           "Union base types require new struct-path TBAA");
+
     SmallString<256> OutName;
     if (Features.CPlusPlus) {
       // Don't use the mangler for C code.
@@ -604,7 +612,8 @@ llvm::MDNode *CodeGenTBAA::getBaseTypeInfoHelper(const Type *Ty) {
 }
 
 llvm::MDNode *CodeGenTBAA::getValidBaseTypeInfo(QualType QTy) {
-  assert(isValidBaseType(QTy) && "Must be a valid base type");
+  assert(isValidBaseType(QTy, CodeGenOpts.NewStructPathTBAA) &&
+         "Must be a valid base type");
 
   const Type *Ty = Context.getCanonicalType(QTy).getTypePtr();
 
@@ -623,7 +632,9 @@ llvm::MDNode *CodeGenTBAA::getValidBaseTypeInfo(QualType QTy) {
 }
 
 llvm::MDNode *CodeGenTBAA::getBaseTypeInfo(QualType QTy) {
-  return isValidBaseType(QTy) ? getValidBaseTypeInfo(QTy) : nullptr;
+  return isValidBaseType(QTy, CodeGenOpts.NewStructPathTBAA)
+             ? getValidBaseTypeInfo(QTy)
+             : nullptr;
 }
 
 llvm::MDNode *CodeGenTBAA::getAccessTagInfo(TBAAAccessInfo Info) {
diff --git a/clang/test/CodeGen/tbaa-struct.cpp b/clang/test/CodeGen/tbaa-struct.cpp
index 2776ea2e4e8619..4b8636947ab602 100644
--- a/clang/test/CodeGen/tbaa-struct.cpp
+++ b/clang/test/CodeGen/tbaa-struct.cpp
@@ -217,7 +217,9 @@ void copy12(UnionMember2 *a1, UnionMember2 *a2) {
 // CHECK-NEW: [[META8]] = !{[[META4]], i64 2, !"short"}
 // CHECK-NEW: [[TBAA12]] = !{[[META13:![0-9]+]], [[META13]], i64 0, i64 24}
 // CHECK-NEW: [[META13]] = !{[[META4]], i64 24, !"_ZTS1B", [[META4]], i64 0, i64 1, [[META7]], i64 4, i64 16, [[META3]], i64 20, i64 4}
-// CHECK-NEW: [[TBAA15]] = !{[[META4]], [[META4]], i64 0, i64 12}
+// CHECK-NEW: [[TBAA15]] = !{[[UNION_U:![0-9]+]], [[UNION_U]], i64 0, i64 12}
+// CHECK-NEW: [[UNION_U]] = !{[[META4]], i64 12, !"_ZTS1U", [[META4]], i64 0, i64 8, [[STRUCT_S:![0-9]+]], i64 0, i64 12}
+// CHECK-NEW: [[STRUCT_S]] = !{[[META4]], i64 12, !"_ZTS1S", [[META4]], i64 0, i64 2, [[META4]], i64 4, i64 8}
 // CHECK-NEW: [[TBAA17]] = !{[[META18:![0-9]+]], [[META18]], i64 0, i64 3}
 // CHECK-NEW: [[META18]] = !{[[META4]], i64 3, !"_ZTS1C", [[META4]], i64 0, i64 1, [[META4]], i64 1, i64 1, [[META4]], i64 2, i64 1}
 // CHECK-NEW: [[TBAA20]] = !{[[META21:![0-9]+]], [[META21]], i64 0, i64 6}
@@ -231,7 +233,9 @@ void copy12(UnionMember2 *a1, UnionMember2 *a2) {
 // CHECK-NEW: [[TBAA33]] = !{[[META34:![0-9]+]], [[META34]], i64 0, i64 16}
 // CHECK-NEW: [[META34]] = !{[[META4]], i64 16, !"_ZTS15NamedBitfields3", [[META3]], i64 1, i64 4, [[META3]], i64 2, i64 4, [[META26]], i64 8, i64 8}
 // CHECK-NEW: [[TBAA37]] = !{[[META38:![0-9]+]], [[META38]], i64 0, i64 16}
-// CHECK-NEW: [[META38]] = !{[[META4]], i64 16, !"_ZTS12UnionMember1", [[META4]], i64 0, i64 8, [[META3]], i64 8, i64 4}
+// CHECK-NEW: [[META38]] = !{[[META4]], i64 16, !"_ZTS12UnionMember1", [[UNION_U2:![0-9]+]], i64 0, i64 8, [[META3]], i64 8, i64 4}
+// CHECK-NEW: [[UNION_U2]] = !{[[META4]], i64 8, !"_ZTS2U2", [[META26]], i64 0, i64 8, [[FLOAT:![0-9]+]], i64 0, i64 4}
+// CHECK-NEW: [[FLOAT]] = !{[[META4]], i64 4, !"float"}
 // CHECK-NEW: [[TBAA41]] = !{[[META42:![0-9]+]], [[META42]], i64 0, i64 16}
-// CHECK-NEW: [[META42]] = !{[[META4]], i64 16, !"_ZTS12UnionMember2", [[META3]], i64 0, i64 4, [[META4]], i64 8, i64 8}
+// CHECK-NEW: [[META42]] = !{[[META4]], i64 16, !"_ZTS12UnionMember2", [[META3]], i64 0, i64 4, [[UNION_U2]], i64 8, i64 8}
 //.
diff --git a/clang/test/CodeGen/tbaa-union-access.c b/clang/test/CodeGen/tbaa-union-access.c
new file mode 100644
index 00000000000000..a84030820ef123
--- /dev/null
+++ b/clang/test/CodeGen/tbaa-union-access.c
@@ -0,0 +1,61 @@
+// RUN: %clang_cc1 -triple x86_64-linux -O1 -emit-llvm %s -o - | \
+// RUN:   FileCheck %s --check-prefix=OLD
+// RUN: %clang_cc1 -triple x86_64-linux -O1 -new-struct-path-tbaa \
+// RUN:   -emit-llvm %s -o - | FileCheck %s --check-prefix=NEW
+// RUN: %clang_cc1 -triple x86_64-linux -O1 -disable-llvm-passes \
+// RUN:   -new-struct-path-tbaa -emit-llvm %s -o - | \
+// RUN:   FileCheck %s --check-prefix=IR
+
+struct Pair {
+  int x, y;
+};
+union U {
+  int i;
+  struct Pair pair;
+};
+struct Outer {
+  union U u;
+  int sibling;
+} *outer;
+union U *dest, *src;
+
+// OLD-LABEL: define{{.*}} i32 @distinct_object(
+// OLD: load i32, ptr
+// OLD: ret i32
+// NEW-LABEL: define{{.*}} i32 @distinct_object(
+// NEW-NOT: load i32, ptr
+// NEW: ret i32 123
+int distinct_object(void) {
+  outer->sibling = 123;
+  *dest = *src;
+  return outer->sibling;
+}
+
+// A pointer to a union member must still alias a whole-union store.
+// NEW-LABEL: define{{.*}} i32 @escaped_member(
+// NEW: load i32, ptr
+// NEW: ret i32
+int escaped_member(void) {
+  int *p = &dest->i;
+  *p = 123;
+  *dest = *src;
+  return *p;
+}
+
+// This also applies to pointers to nested members.
+// NEW-LABEL: define{{.*}} i32 @escaped_nested_member(
+// NEW: load i32, ptr
+// NEW: ret i32
+int escaped_nested_member(void) {
+  int *p = &dest->pair.y;
+  *p = 123;
+  *dest = *src;
+  return *p;
+}
+
+// IR: call void @llvm.memcpy{{.*}}, !tbaa [[TAG_U:![0-9]+]]
+// IR-DAG: [[CHAR:![0-9]+]] = !{!{{[0-9]+}}, i64 1, !"omnipotent char"}
+// IR-DAG: [[INT:![0-9]+]] = !{[[CHAR]], i64 4, !"int"}
+// IR-DAG: [[PAIR:![0-9]+]] = !{[[CHAR]], i64 8, !"Pair", [[INT]], i64 0, i64 4, [[INT]], i64 4, i64 4}
+// IR-DAG: [[UNION:![0-9]+]] = !{[[CHAR]], i64 8, !"U", [[INT]], i64 0, i64 4, [[PAIR]], i64 0, i64 8}
+// IR-DAG: [[TAG_U]] = !{[[UNION]], [[UNION]], i64 0, i64 8}



More information about the cfe-commits mailing list