[clang] [Clang] Fix BitInt padding clearing on big-endian targets (PR #215808)
Victor Campos via cfe-commits
cfe-commits at lists.llvm.org
Wed Aug 12 07:05:32 PDT 2026
https://github.com/vhscampos created https://github.com/llvm/llvm-project/pull/215808
This patch fixes the padding clearing logic of `_BitInt`s.
Before this patch, the clearing logic assumed little endian. But the memory layout of BitInts differs between little and big endian:
- In LE, the occupied bits start from the lowest address and go on contiguously up until the BitInt's declared size. The padding bits then start from that point and go contiguously until the end of the storage unit.
- In BE, since the byte order is reversed, the occupied bit interval is not contiguous if the storage unit is larger than the BitInt's size.
Therefore, the logic must tell the two cases apart and perform the calculations accordingly.
>From e75890f9a3e93e126231c1bb57ca1c15074f9b0b Mon Sep 17 00:00:00 2001
From: Victor Campos <victor.campos at arm.com>
Date: Wed, 5 Aug 2026 16:05:21 +0100
Subject: [PATCH] [Clang] Fix BitInt padding clearing on big-endian targets
This patch fixes the padding clearing logic of `_BitInt`s.
Before this patch, the clearing logic assumed little endian. But the
memory layout of BitInts differs between little and big endian:
- In LE, the occupied bits start from the lowest address and go on
contiguously up until the BitInt's declared size. The padding bits
then start from that point and go contiguously until the end of the
storage unit.
- In BE, since the byte order is reversed, the occupied bit interval
is not contiguous if the storage unit is larger than the BitInt's
size.
Therefore, the logic must tell the two cases apart and perform the
calculations accordingly.
---
clang/lib/CodeGen/CGBuiltin.cpp | 43 ++++++++++
.../CodeGen/builtin-clear-padding-codegen.c | 81 +++++++++++++++++++
.../builtin-clear-padding-codegen.cpp | 81 +++++++++++++++++++
3 files changed, 205 insertions(+)
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 3521fc10f1387..6d5e0c83b9474 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -2672,6 +2672,43 @@ struct PaddingClearer {
return CGF.getContext().getTypeSize(Ty);
}
+ // Compute the occupied bit intervals for a BitInt. In the case of little
+ // endian, the occupied bits are always contiguous so a single interval
+ // is sufficient. However in big endian, the intervals can be disjoint.
+ SmallVector<BitInterval> computeBitIntOccupiedIntervals(const Data &D) const {
+ uint64_t BitIntervalStart = D.StartBitOffset;
+ assert(BitIntervalStart % CharWidth == 0 &&
+ "BitInt start address is not byte aligned!");
+ QualType Ty = D.Ty;
+ uint64_t SizeInBits = getScalarOccupiedSizeInBits(Ty);
+ bool IsLittleEndian = CGF.getTarget().isLittleEndian();
+
+ if (IsLittleEndian)
+ return {BitInterval{BitIntervalStart, BitIntervalStart + SizeInBits}};
+
+ SmallVector<BitInterval, 2> Intervals;
+ uint64_t StorageSizeInBits = CGF.getContext().getTypeSize(Ty);
+ uint64_t ContiguousBitCount = SizeInBits & (~0ULL << 3);
+ uint64_t ContiguousIntervalStart =
+ BitIntervalStart + StorageSizeInBits - ContiguousBitCount;
+
+ Intervals.push_back(BitInterval{
+ ContiguousIntervalStart, ContiguousIntervalStart + ContiguousBitCount});
+
+ uint64_t RemainingBitCount = SizeInBits - ContiguousBitCount;
+ assert(RemainingBitCount < CharWidth);
+
+ if (RemainingBitCount == 0)
+ return Intervals;
+
+ uint64_t RemainingIntervalStart =
+ BitIntervalStart + (StorageSizeInBits - SizeInBits) & (~0ULL << 3);
+ Intervals.push_back(BitInterval{
+ RemainingIntervalStart, RemainingIntervalStart + RemainingBitCount});
+
+ return Intervals;
+ }
+
void Visit(const Data &D) {
if (auto *AT = dyn_cast<ConstantArrayType>(D.Ty)) {
VisitArray(AT, D.StartBitOffset);
@@ -2700,6 +2737,12 @@ struct PaddingClearer {
return;
}
+ if (D.Ty->isBitIntType()) {
+ auto Intervals = computeBitIntOccupiedIntervals(D);
+ OccuppiedIntervals.append(Intervals);
+ return;
+ }
+
uint64_t SizeBit = getScalarOccupiedSizeInBits(D.Ty);
OccuppiedIntervals.push_back(
BitInterval{D.StartBitOffset, D.StartBitOffset + SizeBit});
diff --git a/clang/test/CodeGen/builtin-clear-padding-codegen.c b/clang/test/CodeGen/builtin-clear-padding-codegen.c
index 0564c52dd2a49..fceb7b623ba6d 100644
--- a/clang/test/CodeGen/builtin-clear-padding-codegen.c
+++ b/clang/test/CodeGen/builtin-clear-padding-codegen.c
@@ -1,6 +1,10 @@
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
// RUN: %clang_cc1 -std=c11 -triple=x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s --check-prefix=LINUX
// RUN: %clang_cc1 -std=c11 -triple=x86_64-pc-windows-msvc -emit-llvm -o - %s | FileCheck %s --check-prefix=WINDOWS
+// RUN: %clang_cc1 -std=c11 -triple=armv7a-none-eabi -emit-llvm -o - %s | FileCheck %s --check-prefix=ARM-LE
+// RUN: %clang_cc1 -std=c11 -triple=armebv7a-none-eabi -emit-llvm -o - %s | FileCheck %s --check-prefix=ARM-BE
+// RUN: %clang_cc1 -std=c11 -triple=aarch64-none-eabi -emit-llvm -o - %s | FileCheck %s --check-prefix=ARM-LE
+// RUN: %clang_cc1 -std=c11 -triple=aarch64_be-none-eabi -emit-llvm -o - %s | FileCheck %s --check-prefix=ARM-BE
struct Empty {};
@@ -122,11 +126,88 @@ void testPrimitiveLongDouble(long double *ld) {
// WINDOWS-NEXT: store i8 0, ptr %6, align 1
// WINDOWS-NEXT: ret void
//
+// ARM-LE-LABEL: define dso_local void @testBitInt(
+// ARM-LE: [[OBJ:%.*]] = load ptr, ptr {{.*}}, align {{[0-9]+}}
+// ARM-LE-NEXT: [[BYTE12:%.*]] = getelementptr i8, ptr [[OBJ]], i32 12
+// ARM-LE-NEXT: [[VALUE:%.*]] = load i8, ptr [[BYTE12]], align 4
+// ARM-LE-NEXT: [[MASKED:%.*]] = and i8 [[VALUE]], 1
+// ARM-LE-NEXT: store i8 [[MASKED]], ptr [[BYTE12]], align 4
+// ARM-LE-NEXT: [[BYTE13:%.*]] = getelementptr i8, ptr [[OBJ]], i32 13
+// ARM-LE-NEXT: store i8 0, ptr [[BYTE13]], align 1
+// ARM-LE-NEXT: [[BYTE14:%.*]] = getelementptr i8, ptr [[OBJ]], i32 14
+// ARM-LE-NEXT: store i8 0, ptr [[BYTE14]], align 2
+// ARM-LE-NEXT: [[BYTE15:%.*]] = getelementptr i8, ptr [[OBJ]], i32 15
+// ARM-LE-NEXT: store i8 0, ptr [[BYTE15]], align 1
+// ARM-LE-NEXT: ret void
+//
+// ARM-BE-LABEL: define dso_local void @testBitInt(
+// ARM-BE: [[OBJ:%.*]] = load ptr, ptr {{.*}}, align {{[0-9]+}}
+// ARM-BE-NEXT: [[BYTE0:%.*]] = getelementptr i8, ptr [[OBJ]], i32 0
+// ARM-BE-NEXT: store i8 0, ptr [[BYTE0]], align {{[0-9]+}}
+// ARM-BE-NEXT: [[BYTE1:%.*]] = getelementptr i8, ptr [[OBJ]], i32 1
+// ARM-BE-NEXT: store i8 0, ptr [[BYTE1]], align 1
+// ARM-BE-NEXT: [[BYTE2:%.*]] = getelementptr i8, ptr [[OBJ]], i32 2
+// ARM-BE-NEXT: store i8 0, ptr [[BYTE2]], align 2
+// ARM-BE-NEXT: [[BYTE3:%.*]] = getelementptr i8, ptr [[OBJ]], i32 3
+// ARM-BE-NEXT: [[VALUE:%.*]] = load i8, ptr [[BYTE3]], align 1
+// ARM-BE-NEXT: [[MASKED:%.*]] = and i8 [[VALUE]], 1
+// ARM-BE-NEXT: store i8 [[MASKED]], ptr [[BYTE3]], align 1
+// ARM-BE-NEXT: ret void
+//
void testBitInt(_BitInt(97) *bi) {
// Storage is widened to 128 bits; clear bits [97, 128).
__builtin_clear_padding(bi);
}
+// ARM-LE-LABEL: define dso_local void @testBitIntByteAligned(
+// ARM-LE: [[OBJ:%.*]] = load ptr, ptr {{.*}}, align {{[0-9]+}}
+// ARM-LE-NEXT: [[BYTE3:%.*]] = getelementptr i8, ptr [[OBJ]], i32 3
+// ARM-LE-NEXT: store i8 0, ptr [[BYTE3]], align 1
+// ARM-LE-NEXT: ret void
+//
+// ARM-BE-LABEL: define dso_local void @testBitIntByteAligned(
+// ARM-BE: [[OBJ:%.*]] = load ptr, ptr {{.*}}, align {{[0-9]+}}
+// ARM-BE-NEXT: [[BYTE0:%.*]] = getelementptr i8, ptr [[OBJ]], i32 0
+// ARM-BE-NEXT: store i8 0, ptr [[BYTE0]], align 4
+// ARM-BE-NEXT: ret void
+//
+void testBitIntByteAligned(_BitInt(24) *bi) {
+ // Storage is widened to 32 bits; clear the leading or trailing byte.
+ __builtin_clear_padding(bi);
+}
+
+// ARM-LE-LABEL: define dso_local void @testVolatileBitInt(
+// ARM-LE: [[OBJ:%.*]] = load ptr, ptr {{.*}}, align {{[0-9]+}}
+// ARM-LE-NEXT: [[BYTE12:%.*]] = getelementptr i8, ptr [[OBJ]], i32 12
+// ARM-LE-NEXT: [[VALUE:%.*]] = load i8, ptr [[BYTE12]], align 4
+// ARM-LE-NEXT: [[MASKED:%.*]] = and i8 [[VALUE]], 1
+// ARM-LE-NEXT: store i8 [[MASKED]], ptr [[BYTE12]], align 4
+// ARM-LE-NEXT: [[BYTE13:%.*]] = getelementptr i8, ptr [[OBJ]], i32 13
+// ARM-LE-NEXT: store i8 0, ptr [[BYTE13]], align 1
+// ARM-LE-NEXT: [[BYTE14:%.*]] = getelementptr i8, ptr [[OBJ]], i32 14
+// ARM-LE-NEXT: store i8 0, ptr [[BYTE14]], align 2
+// ARM-LE-NEXT: [[BYTE15:%.*]] = getelementptr i8, ptr [[OBJ]], i32 15
+// ARM-LE-NEXT: store i8 0, ptr [[BYTE15]], align 1
+// ARM-LE-NEXT: ret void
+//
+// ARM-BE-LABEL: define dso_local void @testVolatileBitInt(
+// ARM-BE: [[OBJ:%.*]] = load ptr, ptr {{.*}}, align {{[0-9]+}}
+// ARM-BE-NEXT: [[BYTE0:%.*]] = getelementptr i8, ptr [[OBJ]], i32 0
+// ARM-BE-NEXT: store i8 0, ptr [[BYTE0]], align {{[0-9]+}}
+// ARM-BE-NEXT: [[BYTE1:%.*]] = getelementptr i8, ptr [[OBJ]], i32 1
+// ARM-BE-NEXT: store i8 0, ptr [[BYTE1]], align 1
+// ARM-BE-NEXT: [[BYTE2:%.*]] = getelementptr i8, ptr [[OBJ]], i32 2
+// ARM-BE-NEXT: store i8 0, ptr [[BYTE2]], align 2
+// ARM-BE-NEXT: [[BYTE3:%.*]] = getelementptr i8, ptr [[OBJ]], i32 3
+// ARM-BE-NEXT: [[VALUE:%.*]] = load i8, ptr [[BYTE3]], align 1
+// ARM-BE-NEXT: [[MASKED:%.*]] = and i8 [[VALUE]], 1
+// ARM-BE-NEXT: store i8 [[MASKED]], ptr [[BYTE3]], align 1
+// ARM-BE-NEXT: ret void
+//
+void testVolatileBitInt(volatile _BitInt(97) *bi) {
+ __builtin_clear_padding(bi);
+}
+
// LINUX-LABEL: define dso_local void @testPrimitiveComplexLongDouble(
// LINUX-SAME: ptr noundef [[C:%.*]]) #[[ATTR0]] {
diff --git a/clang/test/CodeGenCXX/builtin-clear-padding-codegen.cpp b/clang/test/CodeGenCXX/builtin-clear-padding-codegen.cpp
index 01475ca5154a3..bd52b0e10662e 100644
--- a/clang/test/CodeGenCXX/builtin-clear-padding-codegen.cpp
+++ b/clang/test/CodeGenCXX/builtin-clear-padding-codegen.cpp
@@ -1,6 +1,10 @@
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
// RUN: %clang_cc1 -std=c++20 -triple=x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s --check-prefix=LINUX
// RUN: %clang_cc1 -std=c++20 -triple=x86_64-pc-windows-msvc -emit-llvm -o - %s | FileCheck %s --check-prefix=WINDOWS
+// RUN: %clang_cc1 -std=c++20 -triple=armv7a-none-eabi -emit-llvm -o - %s | FileCheck %s --check-prefix=ARM-LE
+// RUN: %clang_cc1 -std=c++20 -triple=armebv7a-none-eabi -emit-llvm -o - %s | FileCheck %s --check-prefix=ARM-BE
+// RUN: %clang_cc1 -std=c++20 -triple=aarch64-none-eabi -emit-llvm -o - %s | FileCheck %s --check-prefix=ARM-LE
+// RUN: %clang_cc1 -std=c++20 -triple=aarch64_be-none-eabi -emit-llvm -o - %s | FileCheck %s --check-prefix=ARM-BE
struct Empty {};
@@ -118,11 +122,88 @@ void testPrimitiveLongDouble(long double *ld) {
// WINDOWS-NEXT: store i8 0, ptr %6, align 1
// WINDOWS-NEXT: ret void
//
+// ARM-LE-LABEL: define dso_local void @_Z10testBitIntPDB97_(
+// ARM-LE: [[OBJ:%.*]] = load ptr, ptr {{.*}}, align {{[0-9]+}}
+// ARM-LE-NEXT: [[BYTE12:%.*]] = getelementptr i8, ptr [[OBJ]], i32 12
+// ARM-LE-NEXT: [[VALUE:%.*]] = load i8, ptr [[BYTE12]], align 4
+// ARM-LE-NEXT: [[MASKED:%.*]] = and i8 [[VALUE]], 1
+// ARM-LE-NEXT: store i8 [[MASKED]], ptr [[BYTE12]], align 4
+// ARM-LE-NEXT: [[BYTE13:%.*]] = getelementptr i8, ptr [[OBJ]], i32 13
+// ARM-LE-NEXT: store i8 0, ptr [[BYTE13]], align 1
+// ARM-LE-NEXT: [[BYTE14:%.*]] = getelementptr i8, ptr [[OBJ]], i32 14
+// ARM-LE-NEXT: store i8 0, ptr [[BYTE14]], align 2
+// ARM-LE-NEXT: [[BYTE15:%.*]] = getelementptr i8, ptr [[OBJ]], i32 15
+// ARM-LE-NEXT: store i8 0, ptr [[BYTE15]], align 1
+// ARM-LE-NEXT: ret void
+//
+// ARM-BE-LABEL: define dso_local void @_Z10testBitIntPDB97_(
+// ARM-BE: [[OBJ:%.*]] = load ptr, ptr {{.*}}, align {{[0-9]+}}
+// ARM-BE-NEXT: [[BYTE0:%.*]] = getelementptr i8, ptr [[OBJ]], i32 0
+// ARM-BE-NEXT: store i8 0, ptr [[BYTE0]], align {{[0-9]+}}
+// ARM-BE-NEXT: [[BYTE1:%.*]] = getelementptr i8, ptr [[OBJ]], i32 1
+// ARM-BE-NEXT: store i8 0, ptr [[BYTE1]], align 1
+// ARM-BE-NEXT: [[BYTE2:%.*]] = getelementptr i8, ptr [[OBJ]], i32 2
+// ARM-BE-NEXT: store i8 0, ptr [[BYTE2]], align 2
+// ARM-BE-NEXT: [[BYTE3:%.*]] = getelementptr i8, ptr [[OBJ]], i32 3
+// ARM-BE-NEXT: [[VALUE:%.*]] = load i8, ptr [[BYTE3]], align 1
+// ARM-BE-NEXT: [[MASKED:%.*]] = and i8 [[VALUE]], 1
+// ARM-BE-NEXT: store i8 [[MASKED]], ptr [[BYTE3]], align 1
+// ARM-BE-NEXT: ret void
+//
void testBitInt(_BitInt(97) *bi) {
// Storage is widened to 128 bits; clear bits [97, 128).
__builtin_clear_padding(bi);
}
+// ARM-LE-LABEL: define dso_local void @_Z21testBitIntByteAlignedPDB24_(
+// ARM-LE: [[OBJ:%.*]] = load ptr, ptr {{.*}}, align {{[0-9]+}}
+// ARM-LE-NEXT: [[BYTE3:%.*]] = getelementptr i8, ptr [[OBJ]], i32 3
+// ARM-LE-NEXT: store i8 0, ptr [[BYTE3]], align 1
+// ARM-LE-NEXT: ret void
+//
+// ARM-BE-LABEL: define dso_local void @_Z21testBitIntByteAlignedPDB24_(
+// ARM-BE: [[OBJ:%.*]] = load ptr, ptr {{.*}}, align {{[0-9]+}}
+// ARM-BE-NEXT: [[BYTE0:%.*]] = getelementptr i8, ptr [[OBJ]], i32 0
+// ARM-BE-NEXT: store i8 0, ptr [[BYTE0]], align 4
+// ARM-BE-NEXT: ret void
+//
+void testBitIntByteAligned(_BitInt(24) *bi) {
+ // Storage is widened to 32 bits; clear the leading or trailing byte.
+ __builtin_clear_padding(bi);
+}
+
+// ARM-LE-LABEL: define dso_local void @_Z18testVolatileBitIntPVDB97_(
+// ARM-LE: [[OBJ:%.*]] = load ptr, ptr {{.*}}, align {{[0-9]+}}
+// ARM-LE-NEXT: [[BYTE12:%.*]] = getelementptr i8, ptr [[OBJ]], i32 12
+// ARM-LE-NEXT: [[VALUE:%.*]] = load i8, ptr [[BYTE12]], align 4
+// ARM-LE-NEXT: [[MASKED:%.*]] = and i8 [[VALUE]], 1
+// ARM-LE-NEXT: store i8 [[MASKED]], ptr [[BYTE12]], align 4
+// ARM-LE-NEXT: [[BYTE13:%.*]] = getelementptr i8, ptr [[OBJ]], i32 13
+// ARM-LE-NEXT: store i8 0, ptr [[BYTE13]], align 1
+// ARM-LE-NEXT: [[BYTE14:%.*]] = getelementptr i8, ptr [[OBJ]], i32 14
+// ARM-LE-NEXT: store i8 0, ptr [[BYTE14]], align 2
+// ARM-LE-NEXT: [[BYTE15:%.*]] = getelementptr i8, ptr [[OBJ]], i32 15
+// ARM-LE-NEXT: store i8 0, ptr [[BYTE15]], align 1
+// ARM-LE-NEXT: ret void
+//
+// ARM-BE-LABEL: define dso_local void @_Z18testVolatileBitIntPVDB97_(
+// ARM-BE: [[OBJ:%.*]] = load ptr, ptr {{.*}}, align {{[0-9]+}}
+// ARM-BE-NEXT: [[BYTE0:%.*]] = getelementptr i8, ptr [[OBJ]], i32 0
+// ARM-BE-NEXT: store i8 0, ptr [[BYTE0]], align {{[0-9]+}}
+// ARM-BE-NEXT: [[BYTE1:%.*]] = getelementptr i8, ptr [[OBJ]], i32 1
+// ARM-BE-NEXT: store i8 0, ptr [[BYTE1]], align 1
+// ARM-BE-NEXT: [[BYTE2:%.*]] = getelementptr i8, ptr [[OBJ]], i32 2
+// ARM-BE-NEXT: store i8 0, ptr [[BYTE2]], align 2
+// ARM-BE-NEXT: [[BYTE3:%.*]] = getelementptr i8, ptr [[OBJ]], i32 3
+// ARM-BE-NEXT: [[VALUE:%.*]] = load i8, ptr [[BYTE3]], align 1
+// ARM-BE-NEXT: [[MASKED:%.*]] = and i8 [[VALUE]], 1
+// ARM-BE-NEXT: store i8 [[MASKED]], ptr [[BYTE3]], align 1
+// ARM-BE-NEXT: ret void
+//
+void testVolatileBitInt(volatile _BitInt(97) *bi) {
+ __builtin_clear_padding(bi);
+}
+
// LINUX-LABEL: define dso_local void @_Z30testPrimitiveComplexLongDoublePCe(
// LINUX-SAME: ptr noundef [[C:%.*]]) #[[ATTR0]] {
More information about the cfe-commits
mailing list