PR37275 packed attribute should not apply to base classes
authorRichard Smith <richard-llvm@metafoo.co.uk>
Sun, 29 Apr 2018 04:55:46 +0000 (04:55 +0000)
committerRichard Smith <richard-llvm@metafoo.co.uk>
Sun, 29 Apr 2018 04:55:46 +0000 (04:55 +0000)
Clang incorrectly applied the packed attribute to base classes. Per GCC's
documentation and as can be observed from its behavior, packed only applies to
members, not base classes.

This change is conditioned behind -fclang-abi-compat so that an ABI break can
be avoided by users if desired.

Differential Revision: https://reviews.llvm.org/D46218

llvm-svn: 331136

clang/docs/ReleaseNotes.rst
clang/lib/AST/RecordLayoutBuilder.cpp
clang/test/CodeGenCXX/alignment.cpp
clang/test/SemaCXX/class-layout.cpp

index 6efc98f..b6672bd 100644 (file)
@@ -78,6 +78,12 @@ Non-comprehensive list of changes in this release
   standard-layout if all base classes and the first data member (or bit-field)
   can be laid out at offset zero.
 
+- Clang's handling of the GCC ``packed`` class attribute in C++ has been fixed
+  to apply only to non-static data members and not to base classes. This fixes
+  an ABI difference between Clang and GCC, but creates an ABI difference between
+  Clang 7 and earlier versions. The old behavior can be restored by setting
+  ``-fclang-abi-compat`` to ``6`` or earlier.
+
 - ...
 
 New Compiler Flags
index 390c8e0..47e696a 100644 (file)
@@ -967,7 +967,7 @@ void ItaniumRecordLayoutBuilder::ComputeBaseSubobjectInfo(
 
 void ItaniumRecordLayoutBuilder::EnsureVTablePointerAlignment(
     CharUnits UnpackedBaseAlign) {
-  CharUnits BaseAlign = (Packed) ? CharUnits::One() : UnpackedBaseAlign;
+  CharUnits BaseAlign = Packed ? CharUnits::One() : UnpackedBaseAlign;
 
   // The maximum field alignment overrides base align.
   if (!MaxFieldAlignment.isZero()) {
@@ -1175,9 +1175,14 @@ ItaniumRecordLayoutBuilder::LayoutBase(const BaseSubobjectInfo *Base) {
       HasExternalLayout = External.getExternalVBaseOffset(Base->Class, Offset);
   }
   
+  // Clang <= 6 incorrectly applied the 'packed' attribute to base classes.
+  // Per GCC's documentation, it only applies to non-static data members.
   CharUnits UnpackedBaseAlign = Layout.getNonVirtualAlignment();
-  CharUnits BaseAlign = (Packed) ? CharUnits::One() : UnpackedBaseAlign;
+  CharUnits BaseAlign = (Packed && Context.getLangOpts().getClangABICompat() <=
+                                       LangOptions::ClangABI::Ver6)
+                            ? CharUnits::One()
+                            : UnpackedBaseAlign;
+
   // If we have an empty base class, try to place it at offset 0.
   if (Base->Class->isEmpty() &&
       (!HasExternalLayout || Offset == CharUnits::Zero()) &&
index 49cb344..2549afd 100644 (file)
@@ -1,4 +1,5 @@
-// RUN: %clang_cc1 %s -emit-llvm -o - -triple=x86_64-apple-darwin10 | FileCheck %s
+// RUN: %clang_cc1 %s -emit-llvm -o - -triple=x86_64-apple-darwin10 | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NOCOMPAT
+// RUN: %clang_cc1 %s -emit-llvm -o - -triple=x86_64-apple-darwin10 -fclang-abi-compat=6.0 | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-V6COMPAT
 
 extern int int_source();
 extern void int_sink(int x);
@@ -54,11 +55,13 @@ namespace test0 {
     // CHECK: [[B_P:%.*]] = bitcast i8* [[T1]] to [[B]]*
     // CHECK: [[FIELD_P:%.*]] = bitcast [[B]]* [[B_P]] to i8*
     // CHECK: [[TRUNC:%.*]] = trunc i32 [[CALL]] to i8
-    // CHECK: [[OLD_VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 2
+    // CHECK-V6COMPAT: [[OLD_VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 2
+    // CHECK-NOCOMPAT: [[OLD_VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 4
     // CHECK: [[T0:%.*]] = and i8 [[TRUNC]], 3
     // CHECK: [[T1:%.*]] = and i8 [[OLD_VALUE]], -4
     // CHECK: [[T2:%.*]] = or i8 [[T1]], [[T0]]
-    // CHECK: store i8 [[T2]], i8* [[FIELD_P]], align 2
+    // CHECK-V6COMPAT: store i8 [[T2]], i8* [[FIELD_P]], align 2
+    // CHECK-NOCOMPAT: store i8 [[T2]], i8* [[FIELD_P]], align 4
     c.onebit = int_source();
 
     // CHECK: [[C_P:%.*]] = load [[C]]*, [[C]]**
@@ -66,7 +69,8 @@ namespace test0 {
     // CHECK: [[T1:%.*]] = getelementptr inbounds i8, i8* [[T0]], i64 8
     // CHECK: [[B_P:%.*]] = bitcast i8* [[T1]] to [[B]]*
     // CHECK: [[FIELD_P:%.*]] = bitcast [[B]]* [[B_P]] to i8*
-    // CHECK: [[VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 2
+    // CHECK-V6COMPAT: [[VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 2
+    // CHECK-NOCOMPAT: [[VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 4
     // CHECK: [[T0:%.*]] = shl i8 [[VALUE]], 6
     // CHECK: [[T1:%.*]] = ashr i8 [[T0]], 6
     // CHECK: [[T2:%.*]] = sext i8 [[T1]] to i32
@@ -83,11 +87,13 @@ namespace test0 {
     // CHECK: [[B_P:%.*]] = bitcast i8* [[T1]] to [[B]]*
     // CHECK: [[FIELD_P:%.*]] = bitcast [[B]]* [[B_P]] to i8*
     // CHECK: [[TRUNC:%.*]] = trunc i32 [[CALL]] to i8
-    // CHECK: [[OLD_VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 2
+    // CHECK-V6COMPAT: [[OLD_VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 2
+    // CHECK-NOCOMPAT: [[OLD_VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 4
     // CHECK: [[T0:%.*]] = and i8 [[TRUNC]], 3
     // CHECK: [[T1:%.*]] = and i8 [[OLD_VALUE]], -4
     // CHECK: [[T2:%.*]] = or i8 [[T1]], [[T0]]
-    // CHECK: store i8 [[T2]], i8* [[FIELD_P]], align 2
+    // CHECK-V6COMPAT: store i8 [[T2]], i8* [[FIELD_P]], align 2
+    // CHECK-NOCOMPAT: store i8 [[T2]], i8* [[FIELD_P]], align 4
     c->onebit = int_source();
 
     // CHECK: [[C_P:%.*]] = load [[C:%.*]]*, [[C]]**
@@ -95,7 +101,8 @@ namespace test0 {
     // CHECK: [[T1:%.*]] = getelementptr inbounds i8, i8* [[T0]], i64 8
     // CHECK: [[B_P:%.*]] = bitcast i8* [[T1]] to [[B:%.*]]*
     // CHECK: [[FIELD_P:%.*]] = bitcast [[B]]* [[B_P]] to i8*
-    // CHECK: [[VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 2
+    // CHECK-V6COMPAT: [[VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 2
+    // CHECK-NOCOMPAT: [[VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 4
     // CHECK: [[T0:%.*]] = shl i8 [[VALUE]], 6
     // CHECK: [[T1:%.*]] = ashr i8 [[T0]], 6
     // CHECK: [[T2:%.*]] = sext i8 [[T1]] to i32
@@ -107,7 +114,8 @@ namespace test0 {
   // in an alignment-2 variable.
   // CHECK-LABEL: @_ZN5test01dEv
   void d() {
-    // CHECK: [[C_P:%.*]] = alloca [[C:%.*]], align 2
+    // CHECK-V6COMPAT: [[C_P:%.*]] = alloca [[C:%.*]], align 2
+    // CHECK-NOCOMPAT: [[C_P:%.*]] = alloca [[C:%.*]], align 4
     C c;
 
     // CHECK: [[CALL:%.*]] = call i32 @_Z10int_sourcev()
@@ -116,18 +124,21 @@ namespace test0 {
     // CHECK: [[B_P:%.*]] = bitcast i8* [[T1]] to [[B]]*
     // CHECK: [[FIELD_P:%.*]] = bitcast [[B]]* [[B_P]] to i8*
     // CHECK: [[TRUNC:%.*]] = trunc i32 [[CALL]] to i8
-    // CHECK: [[OLD_VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 2
+    // CHECK-V6COMPAT: [[OLD_VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 2
+    // CHECK-NOCOMPAT: [[OLD_VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 4
     // CHECK: [[T0:%.*]] = and i8 [[TRUNC]], 3
     // CHECK: [[T1:%.*]] = and i8 [[OLD_VALUE]], -4
     // CHECK: [[T2:%.*]] = or i8 [[T1]], [[T0]]
-    // CHECK: store i8 [[T2]], i8* [[FIELD_P]], align 2
+    // CHECK-V6COMPAT: store i8 [[T2]], i8* [[FIELD_P]], align 2
+    // CHECK-NOCOMPAT: store i8 [[T2]], i8* [[FIELD_P]], align 4
     c.onebit = int_source();
 
     // CHECK: [[T0:%.*]] = bitcast [[C]]* [[C_P]] to i8*
     // CHECK: [[T1:%.*]] = getelementptr inbounds i8, i8* [[T0]], i64 8
     // CHECK: [[B_P:%.*]] = bitcast i8* [[T1]] to [[B:%.*]]*
     // CHECK: [[FIELD_P:%.*]] = bitcast [[B]]* [[B_P]] to i8*
-    // CHECK: [[VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 2
+    // CHECK-V6COMPAT: [[VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 2
+    // CHECK-NOCOMPAT: [[VALUE:%.*]] = load i8, i8* [[FIELD_P]], align 4
     // CHECK: [[T0:%.*]] = shl i8 [[VALUE]], 6
     // CHECK: [[T1:%.*]] = ashr i8 [[T0]], 6
     // CHECK: [[T2:%.*]] = sext i8 [[T1]] to i32
index 96552de..5403bd6 100644 (file)
@@ -1,5 +1,6 @@
 // RUN: %clang_cc1 -triple x86_64-unknown-unknown %s -fsyntax-only -verify -std=c++98 -Wno-inaccessible-base
 // RUN: %clang_cc1 -triple x86_64-unknown-unknown %s -fsyntax-only -verify -std=c++11 -Wno-inaccessible-base
+// RUN: %clang_cc1 -triple x86_64-unknown-unknown %s -fsyntax-only -verify -std=c++11 -Wno-inaccessible-base -fclang-abi-compat=6 -DCLANG_ABI_COMPAT=6
 // expected-no-diagnostics
 
 #define SA(n, p) int a##n[(p) ? 1 : -1]
@@ -570,3 +571,36 @@ namespace test18 {
   SA(0, sizeof(might_use_tail_padding) == 80);
 }
 } // namespace PR16537
+
+namespace PR37275 {
+  struct X { char c; };
+
+  struct A { int n; };
+  _Static_assert(_Alignof(A) == _Alignof(int), "");
+
+  // __attribute__((packed)) does not apply to base classes.
+  struct __attribute__((packed)) B : X, A {};
+#if defined(CLANG_ABI_COMPAT) && CLANG_ABI_COMPAT <= 6
+  _Static_assert(_Alignof(B) == 1, "");
+  _Static_assert(__builtin_offsetof(B, n) == 1, "");
+#else
+  _Static_assert(_Alignof(B) == _Alignof(int), "");
+  _Static_assert(__builtin_offsetof(B, n) == 4, "");
+#endif
+
+  // #pragma pack does, though.
+#pragma pack(push, 2)
+  struct C : X, A {};
+  _Static_assert(_Alignof(C) == 2, "");
+  _Static_assert(__builtin_offsetof(C, n) == 2, "");
+
+  struct __attribute__((packed)) D : X, A {};
+#if defined(CLANG_ABI_COMPAT) && CLANG_ABI_COMPAT <= 6
+  _Static_assert(_Alignof(D) == 1, "");
+  _Static_assert(__builtin_offsetof(D, n) == 1, "");
+#else
+  _Static_assert(_Alignof(D) == 2, "");
+  _Static_assert(__builtin_offsetof(D, n) == 2, "");
+#endif
+#pragma pack(pop)
+}