Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3e25d7e
Add an off-by-default warning to complain about MSVC bitfield padding
ojhunt Nov 22, 2024
fbe679e
Merge remote-tracking branch 'origin/llvm.org/main' into users/ojhunt…
ojhunt Nov 30, 2024
11447fe
Address feedback
ojhunt Dec 1, 2024
a18c032
Documentation and renaming
ojhunt Dec 2, 2024
75a46fe
Add release note
ojhunt Dec 2, 2024
40dc8cf
Merge branch 'main' into users/ojhunt/warn-msvc-bitfield-packing
ojhunt Dec 2, 2024
bd3ffbe
Add links to other docs
ojhunt Dec 2, 2024
c4b2d4a
Address feedback
ojhunt Dec 3, 2024
e1b5296
Merge branch 'main' into users/ojhunt/warn-msvc-bitfield-packing
ojhunt Dec 4, 2024
ca50d1e
Update documentation label
ojhunt Dec 4, 2024
fa39d28
Merge remote-tracking branch 'origin/llvm.org/main' into users/ojhunt…
ojhunt Dec 31, 2024
196c034
Merge branch 'main' into users/ojhunt/warn-msvc-bitfield-packing
ojhunt Jan 4, 2025
d88b87b
Merge branch 'main' into users/ojhunt/warn-msvc-bitfield-packing
ojhunt Jan 16, 2025
bd2b006
Merge branch 'main' into users/ojhunt/warn-msvc-bitfield-packing
ojhunt Mar 3, 2025
ab3b7c3
Don't try to diagnose the mismatch unless the warning is enabled
ojhunt Mar 3, 2025
d3fb2f2
Sigh i really thought i had the formatting correct
ojhunt Mar 3, 2025
80e3549
Merge remote-tracking branch 'origin/llvm.org/main' into users/ojhunt…
ojhunt Apr 11, 2025
5e64d46
Change diagnostic spelling, switch to 'Microsoft ABI' to describe the…
ojhunt Apr 11, 2025
73128b6
Merge remote-tracking branch 'origin/llvm.org/main' into users/ojhunt…
ojhunt May 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ Improvements to Clang's diagnostics

- Improve the diagnostics for shadows template parameter to report correct location (#GH129060).

- A new off-by-default warning ``-Wms-bitfield-compatibility`` has been added to alert to cases where bit-field
packing may differ under the MS struct ABI (#GH117428).

Improvements to Clang's time-trace
----------------------------------

Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -8572,6 +8572,7 @@ its underlying representation to be a WebAssembly ``funcref``.

def PreferredTypeDocumentation : Documentation {
let Category = DocCatField;
let Label = "langext-preferred_type_documentation";
let Content = [{
This attribute allows adjusting the type of a bit-field in debug information.
This can be helpful when a bit-field is intended to store an enumeration value,
Expand Down
46 changes: 46 additions & 0 deletions clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,52 @@ def Packed : DiagGroup<"packed", [PackedNonPod]>;
def PaddedBitField : DiagGroup<"padded-bitfield">;
def Padded : DiagGroup<"padded", [PaddedBitField]>;
def UnalignedAccess : DiagGroup<"unaligned-access">;
def MSBitfieldCompatibility : DiagGroup<"ms-bitfield-compatibility"> {
code Documentation = [{
Under the MS Windows platform ABI, adjacent bit-fields are not packed if the
underlying type has a different storage size. This warning indicates that a
pair of adjacent bit-fields may not pack in the same way due to this behavioural
difference.

This can occur when mixing different types explicitly:

.. code-block:: c++

struct S {
uint16_t field1 : 1;
uint32_t field2 : 1;
};

or more subtly through enums

.. code-block:: c++

enum Enum1 { /* ... */ };
enum class Enum2 : unsigned char { /* ... */ };
struct S {
Enum1 field1 : 1;
Enum2 field2 : 1;
};

In each of these cases under the MS Windows platform ABI the second bit-field
will not be packed with the preceding bit-field, and instead will be aligned
as if the fields were each separately defined integer fields of their respective
storage size. For binary compatibility this is obviously and observably
incompatible, however where bit-fields are being used solely for memory use
reduction this incomplete packing may silently increase the size of objects vs
what is expected.

This issue can be addressed by ensuring the storage type of each bit-field is
the same, either by explicitly using the same integer type, or in the case of
enum types declaring the enum types with the same storage size. For enum types
where you cannot specify the underlying type, the options are to either switch
to int sized storage for all specifiers or to resort to declaring the
bit-fields with explicit integer storage types and cast in and out of the field.
If such a solution is required the
:ref:`preferred_type <langext-preferred_type_documentation>` attribute can be
used to convey the actual field type to debuggers and other tooling.
}];
}

def PessimizingMove : DiagGroup<"pessimizing-move">;
def ReturnStdMove : DiagGroup<"return-std-move">;
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -6519,6 +6519,13 @@ def warn_signed_bitfield_enum_conversion : Warning<
InGroup<BitFieldEnumConversion>, DefaultIgnore;
def note_change_bitfield_sign : Note<
"consider making the bit-field type %select{unsigned|signed}0">;
def warn_ms_bitfield_mismatched_storage_packing : Warning<
"bit-field %0 of type %1 has a different storage size than the "
"preceding bit-field (%2 vs %3 bytes) and will not be packed under "
"the MS Windows platform ABI">,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
"the MS Windows platform ABI">,
"the Microsoft ABI">,

Copy link
Collaborator

Choose a reason for hiding this comment

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

In the codebase, we tend to use "Windows" to cover behaviors that need to be shared with GCC and MinGW, and "Microsoft" to cover behaviors that are unique to MSVC. This tends to boil down to C vs. C++. If it's part of the C ABI, it's a Windows behavior, under the rationale that any C compiler for the platform would need to match this behavior, like enums always using a fixed type of int rather than expanding to long long if the members require it. C++ behaviors are high cost and "optional".

This mostly reflects the actual coding logic required, which is to check whether the OS is Windows, or whether the "environment" is Microsoft, so perhaps this is not the best language to communicate with users

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for the explanation!

Would you recommend we go with Windows ABI and leave the Microsoft off? Or should we use Microsoft Windows ABI? I mostly want to avoid MS.

Copy link
Collaborator

@rnk rnk Mar 11, 2025

Choose a reason for hiding this comment

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

In this context I would just say "Windows ABI".

I do recall preferring the use of the company name ("Microsoft") over the use of the product name ("MSVC" / "Visual C++") in the past for obscure legal reasons, but now that Clang ships as part of Visual Studio, clarity is more important than marginal increases in legal risk. This is probably why the code talks about the "Microsoft C++ ABI" not the "MSVC(++) ABI".

Copy link
Collaborator

Choose a reason for hiding this comment

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

SGTM!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've switched to Microsoft ABI, and the diagnostic is now ms-bitfield-padding

InGroup<MSBitfieldCompatibility>, DefaultIgnore;
def note_ms_bitfield_mismatched_storage_size_previous : Note<
"preceding bit-field %0 declared here with type %1">;

def warn_missing_braces : Warning<
"suggest braces around initialization of subobject">,
Expand Down
27 changes: 25 additions & 2 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19192,9 +19192,9 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,

// Verify that all the fields are okay.
SmallVector<FieldDecl*, 32> RecFields;

const FieldDecl *PreviousField = nullptr;
for (ArrayRef<Decl *>::iterator i = Fields.begin(), end = Fields.end();
i != end; ++i) {
i != end; PreviousField = cast<FieldDecl>(*i), ++i) {
FieldDecl *FD = cast<FieldDecl>(*i);

// Get the type for the field.
Expand Down Expand Up @@ -19406,6 +19406,29 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,

if (Record && FD->getType().isVolatileQualified())
Record->setHasVolatileMember(true);
bool ReportMSBitfieldStoragePacking =
Record && PreviousField &&
!Diags.isIgnored(diag::warn_ms_bitfield_mismatched_storage_packing,
Record->getLocation());
auto IsNonDependentBitField = [](const FieldDecl *FD) {
return FD->isBitField() && !FD->getType()->isDependentType();
};

if (ReportMSBitfieldStoragePacking && IsNonDependentBitField(FD) &&
IsNonDependentBitField(PreviousField)) {
CharUnits FDStorageSize = Context.getTypeSizeInChars(FD->getType());
CharUnits PreviousFieldStorageSize =
Context.getTypeSizeInChars(PreviousField->getType());
if (FDStorageSize != PreviousFieldStorageSize) {
Diag(FD->getLocation(),
diag::warn_ms_bitfield_mismatched_storage_packing)
<< FD << FD->getType() << FDStorageSize.getQuantity()
<< PreviousFieldStorageSize.getQuantity();
Diag(PreviousField->getLocation(),
diag::note_ms_bitfield_mismatched_storage_size_previous)
<< PreviousField << PreviousField->getType();
}
}
// Keep track of the number of named members.
if (FD->getIdentifier())
++NumNamedMembers;
Expand Down
44 changes: 37 additions & 7 deletions clang/test/Sema/bitfield-layout.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// RUN: %clang_cc1 %s -fsyntax-only -verify -triple=aarch64-linux-gnu
// RUN: %clang_cc1 %s -fsyntax-only -verify -triple=x86_64-pc-linux-gnu
// RUN: %clang_cc1 %s -fsyntax-only -verify -triple=x86_64-scei-ps4
// RUN: %clang_cc1 %s -fsyntax-only -verify=checkms -triple=i686-apple-darwin9 -Wms-bitfield-compatibility

// expected-no-diagnostics
#include <stddef.h>

Expand All @@ -24,12 +26,27 @@ CHECK_ALIGN(struct, a, 1)
#endif

// Zero-width bit-fields with packed
struct __attribute__((packed)) a2 { short x : 9; char : 0; int y : 17; };
struct __attribute__((packed)) a2 {
short x : 9; // #a2x
char : 0; // #a2anon
// checkms-warning@-1 {{bit-field '' of type 'char' has a different storage size than the preceding bit-field (1 vs 2 bytes) and will not be packed under the MS Windows platform ABI}}
// checkms-note@#a2x {{preceding bit-field 'x' declared here with type 'short'}}
int y : 17;
// checkms-warning@-1 {{bit-field 'y' of type 'int' has a different storage size than the preceding bit-field (4 vs 1 bytes) and will not be packed under the MS Windows platform ABI}}
// checkms-note@#a2anon {{preceding bit-field '' declared here with type 'char'}}
};

CHECK_SIZE(struct, a2, 5)
CHECK_ALIGN(struct, a2, 1)

// Zero-width bit-fields at the end of packed struct
struct __attribute__((packed)) a3 { short x : 9; int : 0; };
struct __attribute__((packed)) a3 {
short x : 9; // #a3x
int : 0;
// checkms-warning@-1 {{bit-field '' of type 'int' has a different storage size than the preceding bit-field (4 vs 2 bytes) and will not be packed under the MS Windows platform ABI}}
// checkms-note@#a3x {{preceding bit-field 'x' declared here with type 'short'}}
};

#if defined(__arm__) || defined(__aarch64__)
CHECK_SIZE(struct, a3, 4)
CHECK_ALIGN(struct, a3, 4)
Expand All @@ -39,7 +56,12 @@ CHECK_ALIGN(struct, a3, 1)
#endif

// For comparison, non-zero-width bit-fields at the end of packed struct
struct __attribute__((packed)) a4 { short x : 9; int : 1; };
struct __attribute__((packed)) a4 {
short x : 9; // #a4x
int : 1;
// checkms-warning@-1 {{bit-field '' of type 'int' has a different storage size than the preceding bit-field (4 vs 2 bytes) and will not be packed under the MS Windows platform ABI}}
// checkms-note@#a4x {{preceding bit-field 'x' declared here with type 'short'}}
};
CHECK_SIZE(struct, a4, 2)
CHECK_ALIGN(struct, a4, 1)

Expand Down Expand Up @@ -165,22 +187,28 @@ CHECK_OFFSET(struct, g4, c, 3);
#endif

struct g5 {
char : 1;
char : 1; // #g5
__attribute__((aligned(1))) int n : 24;
// checkms-warning@-1 {{bit-field 'n' of type 'int' has a different storage size than the preceding bit-field (4 vs 1 bytes) and will not be packed under the MS Windows platform ABI}}
// checkms-note@#g5 {{preceding bit-field '' declared here with type 'char'}}
};
CHECK_SIZE(struct, g5, 4);
CHECK_ALIGN(struct, g5, 4);

struct __attribute__((packed)) g6 {
char : 1;
char : 1; // #g6
__attribute__((aligned(1))) int n : 24;
// checkms-warning@-1 {{bit-field 'n' of type 'int' has a different storage size than the preceding bit-field (4 vs 1 bytes) and will not be packed under the MS Windows platform ABI}}
// checkms-note@#g6 {{preceding bit-field '' declared here with type 'char'}}
};
CHECK_SIZE(struct, g6, 4);
CHECK_ALIGN(struct, g6, 1);

struct g7 {
char : 1;
char : 1; // #g7
__attribute__((aligned(1))) int n : 25;
// checkms-warning@-1 {{bit-field 'n' of type 'int' has a different storage size than the preceding bit-field (4 vs 1 bytes) and will not be packed under the MS Windows platform ABI}}
// checkms-note@#g7 {{preceding bit-field '' declared here with type 'char'}}
};
#if defined(__ORBIS__)
CHECK_SIZE(struct, g7, 4);
Expand All @@ -190,8 +218,10 @@ CHECK_SIZE(struct, g7, 8);
CHECK_ALIGN(struct, g7, 4);

struct __attribute__((packed)) g8 {
char : 1;
char : 1; // #g8
__attribute__((aligned(1))) int n : 25;
// checkms-warning@-1 {{bit-field 'n' of type 'int' has a different storage size than the preceding bit-field (4 vs 1 bytes) and will not be packed under the MS Windows platform ABI}}
// checkms-note@#g8 {{preceding bit-field '' declared here with type 'char'}}
};
#if defined(__ORBIS__)
CHECK_SIZE(struct, g8, 4);
Expand Down
1 change: 1 addition & 0 deletions clang/test/Sema/bitfield-layout_1.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// RUN: %clang_cc1 %s -fsyntax-only -verify -triple=arm-linux-gnueabihf
// RUN: %clang_cc1 %s -fsyntax-only -verify -triple=aarch64-linux-gnu
// RUN: %clang_cc1 %s -fsyntax-only -verify -triple=x86_64-pc-linux-gnu
// RUN: %clang_cc1 %s -fsyntax-only -verify -triple=i686-apple-darwin9 -Wms-bitfield-compatibility
// expected-no-diagnostics

#define CHECK_SIZE(name, size) \
Expand Down
6 changes: 5 additions & 1 deletion clang/test/Sema/mms-bitfields.c
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
// RUN: %clang_cc1 -mms-bitfields -fsyntax-only -verify -triple x86_64-apple-darwin9 %s
// RUN: %clang_cc1 -mms-bitfields -fsyntax-only -Wms-bitfield-compatibility -verify=checkms -triple x86_64-apple-darwin9 %s

// expected-no-diagnostics

// The -mms-bitfields commandline parameter should behave the same
// as the ms_struct attribute.
struct
{
int a : 1;
int a : 1; // #a
short b : 1;
// checkms-warning@-1 {{bit-field 'b' of type 'short' has a different storage size than the preceding bit-field (2 vs 4 bytes) and will not be packed under the MS Windows platform ABI}}
// checkms-note@#a {{preceding bit-field 'a' declared here with type 'int'}}
} t;

// MS pads out bitfields between different types.
Expand Down
1 change: 1 addition & 0 deletions clang/test/SemaCXX/bitfield.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 %s -verify
// RUN: %clang_cc1 %s -verify -Wms-bitfield-compatibility

// expected-no-diagnostics

Expand Down
Loading