https://github.com/phoebewang created 
https://github.com/llvm/llvm-project/pull/217847

The x86-64 SysV classifier ignored all unnamed bit-fields as padding. A 
non-zero-width unnamed bit-field is real storage, though, so an eightbyte 
occupied only by one was left NO_CLASS. When a struct holds a run of 
__int128/_BitInt(128) bit-fields, record lowering merges the run into a single 
i128 access unit that spans both eightbytes, but classification then marked 
only one of them INTEGER. The i128 was therefore queried at offset 8 (tripping 
assert(IROffset == 0) in GetINTEGERTypeAtOffset) or returned as the low part 
while the high eightbyte was NO_CLASS (tripping assert(Hi == Integer) in the 
callers); without assertions it was passed in a register pair the ABI never 
assigned.

Classify a non-zero-width unnamed bit-field like a named one so both eightbytes 
of such a struct are INTEGER. The value is then passed and returned as an i128 
in two integer registers, matching GCC, and the existing i128 asserts hold. 
Zero-length bit-fields are still ignored, as they occupy no storage.

Fixes #202205.

>From 5ed88c38f8e20ebec5bacd58f18e78a26da8826e Mon Sep 17 00:00:00 2001
From: Phoebe Wang <[email protected]>
Date: Fri, 21 Aug 2026 01:27:59 -0700
Subject: [PATCH] [Clang][CodeGen][X86] Classify non-zero-width unnamed
 bit-fields as INTEGER

The x86-64 SysV classifier ignored all unnamed bit-fields as padding. A
non-zero-width unnamed bit-field is real storage, though, so an eightbyte
occupied only by one was left NO_CLASS. When a struct holds a run of
__int128/_BitInt(128) bit-fields, record lowering merges the run into a
single i128 access unit that spans both eightbytes, but classification then
marked only one of them INTEGER. The i128 was therefore queried at offset 8
(tripping assert(IROffset == 0) in GetINTEGERTypeAtOffset) or returned as the
low part while the high eightbyte was NO_CLASS (tripping assert(Hi == Integer)
in the callers); without assertions it was passed in a register pair the ABI
never assigned.

Classify a non-zero-width unnamed bit-field like a named one so both eightbytes
of such a struct are INTEGER. The value is then passed and returned as an i128
in two integer registers, matching GCC, and the existing i128 asserts hold.
Zero-length bit-fields are still ignored, as they occupy no storage.

Fixes #202205.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
---
 clang/docs/ReleaseNotes.md                |  7 ++++++
 clang/lib/CodeGen/Targets/X86.cpp         | 12 +++++++---
 clang/test/CodeGen/X86/x86_64-arguments.c | 29 +++++++++++++++++++++++
 3 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 8c9467ca7b742..48a8cc31f2618 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -96,6 +96,13 @@ features cannot lower the translation-unit ABI level;
 - On MIPS N32/N64, an `__int128` now correctly start in an even-numbered 
register
   or 16-byte aligned stack slot, matching GCC.
 
+- On x86-64 System V, an eightbyte occupied only by a non-zero-width unnamed
+  bit-field is now classified INTEGER rather than NO_CLASS, matching GCC. As a
+  result a struct holding a run of `__int128`/`_BitInt(128)` bit-fields (which
+  record lowering merges into a single 128-bit access unit) is now passed and
+  returned in the two integer registers the ABI assigns it, instead of one.
+  This also fixes a crash when such a struct was passed or returned. 
(#GH202205)
+
 ### AST Dumping Potentially Breaking Changes
 
 ### Clang Frontend Potentially Breaking Changes
diff --git a/clang/lib/CodeGen/Targets/X86.cpp 
b/clang/lib/CodeGen/Targets/X86.cpp
index f0d108f3279fd..78b732bb0be6b 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -2204,8 +2204,13 @@ void X86_64ABIInfo::classify(QualType Ty, uint64_t 
OffsetBase, Class &Lo,
       uint64_t Offset = OffsetBase + Layout.getFieldOffset(idx);
       bool BitField = i->isBitField();
 
-      // Ignore padding bit-fields.
-      if (BitField && i->isUnnamedBitField())
+      // Ignore zero-length bit-fields: they occupy no storage and only affect
+      // alignment. A non-zero-width unnamed bit-field, however, is real 
storage
+      // and its eightbyte(s) must be classified INTEGER, matching GCC. 
Skipping
+      // it would leave an eightbyte NO_CLASS even though record lowering 
merged
+      // the run into a single i128 access unit, which later trips the i128
+      // handling in classify{Return,Argument}Type / GetINTEGERTypeAtOffset.
+      if (BitField && i->isUnnamedBitField() && i->isZeroLengthBitField())
         continue;
 
       // AMD64-ABI 3.2.3p2: Rule 1. If the size of an object is larger than
@@ -2246,7 +2251,8 @@ void X86_64ABIInfo::classify(QualType Ty, uint64_t 
OffsetBase, Class &Lo,
       // structure to be passed in memory even if unaligned, and
       // therefore they can straddle an eightbyte.
       if (BitField) {
-        assert(!i->isUnnamedBitField());
+        // Named and non-zero-width unnamed bit-fields reach here; zero-length
+        // bit-fields were skipped above.
         uint64_t Offset = OffsetBase + Layout.getFieldOffset(idx);
         uint64_t Size = i->getBitWidthValue();
 
diff --git a/clang/test/CodeGen/X86/x86_64-arguments.c 
b/clang/test/CodeGen/X86/x86_64-arguments.c
index 580f9487395d3..e156a759d23c9 100644
--- a/clang/test/CodeGen/X86/x86_64-arguments.c
+++ b/clang/test/CodeGen/X86/x86_64-arguments.c
@@ -590,6 +590,35 @@ _BitInt(128) f74(__uint128_t b, __uint128_t c, __uint128_t 
d, long e, _BitInt(12
   return a;
 }
 
+// A run of (u)int128_t bit-fields lowers to a single i128 access unit that
+// spans both eightbytes. A non-zero-width unnamed bit-field is real storage,
+// so both eightbytes classify INTEGER and the value is passed/returned as an
+// i128 (two integer registers), matching GCC -- regardless of whether the
+// named field sits in the low or high eightbyte.
+struct s75 {
+  __uint128_t : 124;
+  __uint128_t a : 4;
+};
+// CHECK-LABEL: define{{.*}} i128 @f75()
+struct s75 f75(void) {
+  return (struct s75){0};
+}
+// CHECK-LABEL: define{{.*}} void @f76(i128 %a.coerce)
+void f76(struct s75 a) {
+}
+
+struct s77 {
+  __uint128_t a : 4;
+  __uint128_t : 124;
+};
+// CHECK-LABEL: define{{.*}} i128 @f77()
+struct s77 f77(void) {
+  return (struct s77){0};
+}
+// CHECK-LABEL: define{{.*}} void @f78(i128 %a.coerce)
+void f78(struct s77 a) {
+}
+
 /// The synthesized __va_list_tag does not have file/line fields.
 // CHECK:      = distinct !DICompositeType(tag: DW_TAG_structure_type, name: 
"__va_list_tag",
 // CHECK-NOT:  file:

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to