https://github.com/adams381 created
https://github.com/llvm/llvm-project/pull/197085
`bool` bit-fields like `bool flag : 1;` trip an assertion in CIR codegen
because `cir.set_bitfield` and `cir.get_bitfield` are constrained to take and
produce `CIR_IntType` values, but CIRGen was passing `convertType(boolType)` (=
`!cir.bool`) as the op's result type. Both load and store paths fail with:
Assertion `isa<To>(Val) && "cast<Ty>() argument of incompatible type!"'
failed
[To = mlir::detail::TypedValue<cir::IntType>, From = mlir::OpResult]
This is the second-largest libcxx-with-CIR blocker behind #197068 — ~119 of
1,494 fails in our May 11 `std/` baseline, mostly in `<format>` parsing state
where bool bit-fields are common.
The fix mirrors classic CodeGen at the bit-field boundary: widen the bool
source to the storage integer type for the op call, and narrow the integer
result back to bool with `int_to_bool` so callers that consume the
assignment-expression value — and the read half of compound assignments or
bit-field-to-bit-field copies — still see a `!cir.bool`. Non-bool bit-fields
are unaffected.
New test at `clang/test/CIR/CodeGen/bool-bitfield.cpp` covers store, load,
store-with-result-used, compound `|=`, bit-field-to-bit-field copy, and a
regression check for an int bit-field sharing storage with a bool one. CIR +
LLVM + OGCG checks throughout.
>From bac1566d9217647e5e8ba699e85138f427f62458 Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Mon, 11 May 2026 20:00:44 -0700
Subject: [PATCH] [CIR] Lower bool bit-fields correctly
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
`cir.set_bitfield` and `cir.get_bitfield` are constrained to take and
produce `CIR_IntType` values, but a bit-field whose declared type is
`bool` (e.g. `bool flag : 1;`) is naturally `!cir.bool` at the CIRGen
layer. CIRGen was passing `convertType(boolType)` (= `!cir.bool`) as
the op's result type, which trips MLIR's auto-generated TypedValue
cast in the op constructor:
Assertion `isa<To>(Val) && "cast<Ty>() argument of incompatible
type!"' failed
[To = mlir::detail::TypedValue<cir::IntType>,
From = mlir::OpResult]
This is the second-largest libcxx-with-CIR blocker behind PR
#197068 (119 of 1,494 fails in the May 11 post-fix baseline; mostly
in `<format>` parsing state, where bool bit-fields are common).
Mirror what classic CodeGen does at the bit-field boundary: widen
the bool source to the bit-field's storage integer type for the op
call (`bool_to_int`), and narrow the integer result back to bool
with `int_to_bool` so callers that consume the value of an
assignment expression — and the read half of compound assignments
or bit-field-to-bit-field copies — still see a `!cir.bool`. The
op's result is `Pure`, so the narrowing cast is naturally DCE'd
when an assignment expression is used at statement level.
Non-bool bit-fields (signed/unsigned integers of any width,
`_BitInt(N)`, regular and scoped enums with integer underlying
type) are unaffected — they keep their previous code shape with
no extra casts.
New `clang/test/CIR/CodeGen/bool-bitfield.cpp` with CIR + LLVM +
OGCG checks covering:
- store to a `bool` bit-field (statement form, narrowing cast
DCE'd),
- store with the assignment-expression value used (narrowing
preserved),
- load from a `bool` bit-field,
- compound `|=` to a `bool` bit-field (exercises both halves of
the fix in a single read-modify-write expression),
- bit-field-to-bit-field copy (`p->flag = p->other` between two
`bool` bit-fields),
- mix of bool and int bit-fields in the same storage word
(regression check that ordinary int bit-fields keep their shape
with no `bool_to_int` / `int_to_bool` introduced).
AAPCS volatile bit-fields are not directly exercised by this PR
(the host build target is x86_64); the volatile/AAPCS path is
preserved structurally, with the bool↔int wrap applied on top.
ninja check-clang-cir-codegen and ninja check-clang-cir remain
green.
Co-authored-by: Cursor <[email protected]>
---
clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 42 ++++++-
clang/test/CIR/CodeGen/bool-bitfield.cpp | 144 +++++++++++++++++++++++
2 files changed, 180 insertions(+), 6 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/bool-bitfield.cpp
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 34a7e4d655610..e3df2e344358d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -528,9 +528,27 @@ mlir::Value
CIRGenFunction::emitStoreThroughBitfieldLValue(RValue src,
assert(currSrcLoc && "must pass in source location");
- return builder.createSetBitfield(*currSrcLoc, resLTy, ptr,
- ptr.getElementType(), src.getValue(), info,
- dst.isVolatileQualified(), useVoaltile);
+ // cir.set_bitfield consumes and produces integer values only. When the
+ // user-visible field type is bool (e.g. `bool flag : 1;`), widen the
+ // source to the storage's integer type for the op call and narrow the
+ // op's integer result back to bool for callers that consume the value
+ // of the assignment expression.
+ bool isBool = mlir::isa<cir::BoolType>(resLTy);
+ mlir::Value srcVal = src.getValue();
+ mlir::Type opResLTy = resLTy;
+ if (isBool) {
+ opResLTy = ptr.getElementType();
+ srcVal = builder.createBoolToInt(srcVal, opResLTy);
+ }
+
+ mlir::Value stored = builder.createSetBitfield(
+ *currSrcLoc, opResLTy, ptr, ptr.getElementType(), srcVal, info,
+ dst.isVolatileQualified(), useVoaltile);
+
+ if (isBool)
+ stored = builder.createCast(*currSrcLoc, cir::CastKind::int_to_bool,
stored,
+ resLTy);
+ return stored;
}
RValue CIRGenFunction::emitLoadOfBitfieldLValue(LValue lv, SourceLocation loc)
{
@@ -543,9 +561,21 @@ RValue CIRGenFunction::emitLoadOfBitfieldLValue(LValue lv,
SourceLocation loc) {
bool useVoaltile = lv.isVolatileQualified() && info.volatileOffset != 0 &&
isAAPCS(cgm.getTarget());
- mlir::Value field =
- builder.createGetBitfield(getLoc(loc), resLTy, ptr, ptr.getElementType(),
- info, lv.isVolatile(), useVoaltile);
+ // cir.get_bitfield always produces an integer. When the user-visible
+ // field type is bool (e.g. `bool flag : 1;`), perform the load in the
+ // storage's integer type and narrow the result back to bool with
+ // int_to_bool (i != 0).
+ bool isBool = mlir::isa<cir::BoolType>(resLTy);
+ mlir::Type opResLTy = isBool ? ptr.getElementType() : resLTy;
+
+ mlir::Value field = builder.createGetBitfield(getLoc(loc), opResLTy, ptr,
+ ptr.getElementType(), info,
+ lv.isVolatile(), useVoaltile);
+
+ if (isBool)
+ field = builder.createCast(getLoc(loc), cir::CastKind::int_to_bool, field,
+ resLTy);
+
assert(!cir::MissingFeatures::opLoadEmitScalarRangeCheck() && "NYI");
return RValue::get(field);
}
diff --git a/clang/test/CIR/CodeGen/bool-bitfield.cpp
b/clang/test/CIR/CodeGen/bool-bitfield.cpp
new file mode 100644
index 0000000000000..84a068663369d
--- /dev/null
+++ b/clang/test/CIR/CodeGen/bool-bitfield.cpp
@@ -0,0 +1,144 @@
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir
-emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir
-emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -emit-llvm %s
-o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG
+
+// `cir.set_bitfield` and `cir.get_bitfield` produce integer values only,
+// but a bit-field whose declared type is `bool` (e.g. `bool flag : 1;`,
+// common in libcxx's `std::format` parsing state) is naturally typed as
+// `!cir.bool` at the CIRGen layer. CIRGen widens `bool` to the storage's
+// integer type for the op call and narrows back to `bool` with
+// `int_to_bool` for callers that consume the result.
+
+struct B {
+ bool flag : 1;
+ bool other : 1;
+};
+
+// Store: object expression is unused, so the int_to_bool on the stored
+// value gets DCE'd by the `Pure` trait on `cir.cast`; we just want to
+// see the bool source widened with `bool_to_int` and a `!u8i`-typed
+// `cir.set_bitfield`.
+
+void store_bool_bitfield(B *b) {
+ b->flag = true;
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z19store_bool_bitfieldP1B
+// CIR: %[[TRUE:.+]] = cir.const #true
+// CIR: %[[WIDEN:.+]] = cir.cast bool_to_int %[[TRUE]] : !cir.bool ->
!u8i
+// CIR: cir.set_bitfield{{.*}}(#{{.+}}, %{{.+}} : !cir.ptr<!u8i>,
%[[WIDEN]] : !u8i) -> !u8i
+
+// LLVM-LABEL: define {{.*}} void @_Z19store_bool_bitfieldP1B
+// LLVM: load ptr, ptr %{{.+}}
+// LLVM: load i8, ptr %{{.+}}
+// LLVM: %{{.+}} = and i8 %{{.+}}, -2
+// LLVM: %{{.+}} = or {{.*}}i8 %{{.+}}, 1
+// LLVM: store i8 %{{.+}}, ptr %{{.+}}
+
+// OGCG-LABEL: define {{.*}} void @_Z19store_bool_bitfieldP1B
+// OGCG: load ptr, ptr %{{.+}}
+// OGCG: load i8, ptr %{{.+}}
+// OGCG: %{{.+}} = and i8 %{{.+}}, -2
+// OGCG: %{{.+}} = or {{.*}}i8 %{{.+}}, 1
+// OGCG: store i8 %{{.+}}, ptr %{{.+}}
+
+// Assignment-expression value used: the store result must be narrowed
+// back to bool with int_to_bool so the caller sees a `!cir.bool`.
+
+bool store_bool_bitfield_used(B *b, bool v) {
+ return b->flag = v;
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z24store_bool_bitfield_usedP1Bb
+// CIR: %[[V:.+]] = cir.load{{.*}} %{{.+}} : !cir.ptr<!cir.bool>,
!cir.bool
+// CIR: %[[VINT:.+]] = cir.cast bool_to_int %[[V]] : !cir.bool -> !u8i
+// CIR: %[[STORED:.+]] = cir.set_bitfield {{.*}} %[[VINT]] : !u8i) ->
!u8i
+// CIR: %[[BACK:.+]] = cir.cast int_to_bool %[[STORED]] : !u8i ->
!cir.bool
+
+// LLVM-LABEL: define {{.*}}i1 @_Z24store_bool_bitfield_usedP1Bb
+// LLVM: zext i1 %{{.+}} to i8
+// LLVM: store i8 %{{.+}}, ptr %{{.+}}
+// LLVM: icmp ne i8 %{{.+}}, 0
+
+// OGCG-LABEL: define {{.*}}i1 @_Z24store_bool_bitfield_usedP1Bb
+// OGCG: zext i1 %{{.+}} to i8
+// OGCG: store i8 %{{.+}}, ptr %{{.+}}
+// OGCG: icmp ne i8 %{{.+}}, 0
+
+// Load: read a bool bitfield. The op produces an integer, which CIRGen
+// then narrows to `!cir.bool` with `int_to_bool`.
+
+bool load_bool_bitfield(B *b) {
+ return b->flag;
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z18load_bool_bitfieldP1B
+// CIR: %[[INT:.+]] = cir.get_bitfield{{.*}}(#{{.+}}, %{{.+}} :
!cir.ptr<!u8i>) -> !u8i
+// CIR: %[[BOOL:.+]] = cir.cast int_to_bool %[[INT]] : !u8i ->
!cir.bool
+
+// LLVM-LABEL: define {{.*}}i1 @_Z18load_bool_bitfieldP1B
+// LLVM: load i8, ptr %{{.+}}
+// LLVM: and i8 %{{.+}}, 1
+// LLVM: icmp ne i8 %{{.+}}, 0
+
+// OGCG-LABEL: define {{.*}}i1 @_Z18load_bool_bitfieldP1B
+// OGCG: load i8, ptr %{{.+}}
+// OGCG: and i8 %{{.+}}, 1
+// OGCG: trunc i8 %{{.+}} to i1
+
+// Compound assignment to a `bool` bit-field (e.g. `state.flags |= v;`,
+// common in libcxx `<format>` parsing state). The read-modify-write
+// path goes through both `emitLoadOfBitfieldLValue` and
+// `emitStoreThroughBitfieldLValue` and so exercises both halves of
+// the fix.
+
+void compound_or_bool_bitfield(B *b, bool v) {
+ b->flag |= v;
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z25compound_or_bool_bitfieldP1Bb
+// CIR: cir.get_bitfield{{.*}}-> !u8i
+// CIR: cir.cast int_to_bool %{{.+}} : !u8i -> !cir.bool
+// CIR: %[[WIDEN:.+]] = cir.cast bool_to_int %{{.+}} : !cir.bool ->
!u8i
+// CIR: cir.set_bitfield{{.*}}, %[[WIDEN]] : !u8i) -> !u8i
+
+// Bit-field-to-bit-field copy (e.g. `state.a = state.b;` between two
+// `bool` bit-fields in the same record).
+
+void copy_bool_bitfield(B *b) {
+ b->flag = b->other;
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z18copy_bool_bitfieldP1B
+// CIR: cir.get_bitfield{{.*}}-> !u8i
+// CIR: cir.cast int_to_bool %{{.+}} : !u8i -> !cir.bool
+// CIR: %[[WIDEN:.+]] = cir.cast bool_to_int %{{.+}} : !cir.bool ->
!u8i
+// CIR: cir.set_bitfield{{.*}}, %[[WIDEN]] : !u8i) -> !u8i
+
+// Mix of bool and int bitfields in the same storage word — regression
+// check that ordinary int-typed bitfields keep their previous code
+// shape (no extra bool_to_int / int_to_bool).
+
+struct M {
+ bool b : 1;
+ int n : 7;
+};
+
+int load_int_bitfield(M *m) {
+ return m->n;
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z17load_int_bitfieldP1M
+// CIR: cir.get_bitfield{{.*}}(#{{.+}}, %{{.+}} : !cir.ptr<!u8i>) ->
!s32i
+// CIR-NOT: cir.cast int_to_bool
+
+void store_int_bitfield(M *m) {
+ m->n = 5;
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z18store_int_bitfieldP1M
+// CIR-NOT: cir.cast bool_to_int
+// CIR: cir.set_bitfield{{.*}}(#{{.+}}, %{{.+}} : !cir.ptr<!u8i>,
%{{.+}} : !s32i) -> !s32i
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits