[clang] [CIR] Lower bool bit-fields correctly (PR #197085)
via cfe-commits
cfe-commits at lists.llvm.org
Thu May 14 08:46:55 PDT 2026
https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/197085
>From bac1566d9217647e5e8ba699e85138f427f62458 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Mon, 11 May 2026 20:00:44 -0700
Subject: [PATCH 1/3] [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 <cursoragent at cursor.com>
---
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
>From 9c77ccc3e1afa4668ef70b196a17644efe8e1bf9 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Tue, 12 May 2026 15:36:23 -0700
Subject: [PATCH 2/3] [CIR] Address review on #197085: widen set/get_bitfield
to accept bool
@andykaylor pointed out that the right fix is to widen the
SetBitfieldOp / GetBitfieldOp result-type constraint from
CIR_IntType to CIR_AnyIntOrBoolType so a bool bit-field can be
modelled directly, rather than working around the constraint in
CIRGen by widening/narrowing through the storage's integer type.
@erichkeane had flagged the same shape ("set_bitfield should be
able to accept ANY type that can be used in bitfields and handle
this right").
Revert the CIRGenExpr.cpp workaround to its original two-call
form and widen both ops' result type in CIROps.td.
LowerToLLVM's existing `mlir::cast<mlir::IntegerType>(resTy)` and
`createIntCast` already handle the i1 / i8 case correctly (the
narrowing emits `trunc i8 to i1`, matching OGCG).
Test expectations rewritten to pin the new shape (bool-typed
set/get_bitfield, no surrounding bool_to_int / int_to_bool casts
in CIR), and the multi-line motivation preamble at the top of
the test file dropped per the rule documented after the
#197089 / #197094 reviewer feedback.
Int bit-field expectations are unchanged (`load_int_bitfield` /
`store_int_bitfield` regression checks preserved).
---
clang/include/clang/CIR/Dialect/IR/CIROps.td | 4 +-
clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 42 +++------------
clang/test/CIR/CodeGen/bool-bitfield.cpp | 57 +++-----------------
3 files changed, 16 insertions(+), 87 deletions(-)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 9d9aaec1b275a..b7c7469699fac 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3284,7 +3284,7 @@ def CIR_SetBitfieldOp : CIR_Op<"set_bitfield"> {
UnitAttr:$is_volatile
);
- let results = (outs CIR_IntType:$result);
+ let results = (outs CIR_AnyIntOrBoolType:$result);
let assemblyFormat = [{
(`align` `(` $alignment^ `)`)?
@@ -3371,7 +3371,7 @@ def CIR_GetBitfieldOp : CIR_Op<"get_bitfield"> {
UnitAttr:$is_volatile
);
- let results = (outs CIR_IntType:$result);
+ let results = (outs CIR_AnyIntOrBoolType:$result);
let assemblyFormat = [{
(`align` `(` $alignment^ `)`)?
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index e3df2e344358d..34a7e4d655610 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -528,27 +528,9 @@ mlir::Value CIRGenFunction::emitStoreThroughBitfieldLValue(RValue src,
assert(currSrcLoc && "must pass in source location");
- // 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;
+ return builder.createSetBitfield(*currSrcLoc, resLTy, ptr,
+ ptr.getElementType(), src.getValue(), info,
+ dst.isVolatileQualified(), useVoaltile);
}
RValue CIRGenFunction::emitLoadOfBitfieldLValue(LValue lv, SourceLocation loc) {
@@ -561,21 +543,9 @@ RValue CIRGenFunction::emitLoadOfBitfieldLValue(LValue lv, SourceLocation loc) {
bool useVoaltile = lv.isVolatileQualified() && info.volatileOffset != 0 &&
isAAPCS(cgm.getTarget());
- // 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);
-
+ mlir::Value field =
+ builder.createGetBitfield(getLoc(loc), resLTy, ptr, ptr.getElementType(),
+ info, lv.isVolatile(), useVoaltile);
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
index 84a068663369d..6837c10e8d794 100644
--- a/clang/test/CIR/CodeGen/bool-bitfield.cpp
+++ b/clang/test/CIR/CodeGen/bool-bitfield.cpp
@@ -5,31 +5,18 @@
// 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
+// CIR: cir.set_bitfield{{.*}}(#{{.+}}, %{{.+}} : !cir.ptr<!u8i>, %[[TRUE]] : !cir.bool) -> !cir.bool
// LLVM-LABEL: define {{.*}} void @_Z19store_bool_bitfieldP1B
// LLVM: load ptr, ptr %{{.+}}
@@ -45,82 +32,54 @@ void store_bool_bitfield(B *b) {
// 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
+// CIR: cir.set_bitfield{{.*}}, %[[V]] : !cir.bool) -> !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
+// CIR: cir.get_bitfield{{.*}}(#{{.+}}, %{{.+}} : !cir.ptr<!u8i>) -> !cir.bool
// LLVM-LABEL: define {{.*}}i1 @_Z18load_bool_bitfieldP1B
// LLVM: load i8, ptr %{{.+}}
// LLVM: and i8 %{{.+}}, 1
-// LLVM: icmp ne i8 %{{.+}}, 0
+// LLVM: trunc i8 %{{.+}} to i1
// 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).
+// CIR: cir.get_bitfield{{.*}}-> !cir.bool
+// CIR: cir.set_bitfield{{.*}} : !cir.bool) -> !cir.bool
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).
+// CIR: %[[OTHER:.+]] = cir.get_bitfield{{.*}}-> !cir.bool
+// CIR: cir.set_bitfield{{.*}}, %[[OTHER]] : !cir.bool) -> !cir.bool
struct M {
bool b : 1;
>From 277c387d8019bf83c429a125077c931d2c4043d5 Mon Sep 17 00:00:00 2001
From: Adam Smith <adams at nvidia.com>
Date: Thu, 14 May 2026 08:44:34 -0700
Subject: [PATCH 3/3] [CIR] Address review on #197085: combine LLVM/OGCG, fill
coverage, named captures
Three review asks on the May 12 redo (commit 9c77ccc3) of #197085,
all test-side and all on clang/test/CIR/CodeGen/bool-bitfield.cpp.
@andykaylor's first ask, "LLVM and OGCG checks can be combined?":
the CIR-emit-llvm and classic-emit-llvm outputs share the same
instruction shape modulo SSA-name and explicit-GEP differences that
`%{{.+}}` plus sequential `LLVM:` matches absorb. Same simplification
we applied on #197068. Both backend RUN lines now share a single
`LLVM` prefix. His second ask, "Missing LLVM/OGCG checks from here
on", covered the compound-or, copy, load-int, and store-int functions
that had CIR coverage only; LLVM check blocks added for all four.
@erichkeane's ask, "A ton of 'unnamed' wildcards...": swept the CIR
checks for `cir.set_bitfield{{.*}}` / `#{{.+}}` / `%{{.+}}` and
replaced them with explicit qualifiers (`align(1)`), explicit
bitfield-info names (`#bfi_flag`, `#bfi_other`, `#bfi_n`), and named
captures (`%[[B_PTR]]`, `%[[FLAG_PTR]]`, etc.) that show the data
flow from get_member through the bitfield op. LLVM checks pick up
named captures for the load->and->or->store RMW chains where the
value flow is structurally identical across the two backends; the
few places where backend differences (CIR's extra GEP, CIR's
value-from-byte via trunc vs OGCG's via icmp ne, CIR's extra
`and i8 ..., 1` mask before the RMW) make named captures unreliable,
the CHECK keeps a `%{{.+}}` wildcard rather than misleading the
reader.
---
clang/test/CIR/CodeGen/bool-bitfield.cpp | 102 +++++++++++++++--------
1 file changed, 65 insertions(+), 37 deletions(-)
diff --git a/clang/test/CIR/CodeGen/bool-bitfield.cpp b/clang/test/CIR/CodeGen/bool-bitfield.cpp
index 6837c10e8d794..44d90e71cc9c4 100644
--- a/clang/test/CIR/CodeGen/bool-bitfield.cpp
+++ b/clang/test/CIR/CodeGen/bool-bitfield.cpp
@@ -3,7 +3,7 @@
// 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
+// RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM
struct B {
bool flag : 1;
@@ -16,70 +16,84 @@ void store_bool_bitfield(B *b) {
// CIR-LABEL: cir.func{{.*}} @_Z19store_bool_bitfieldP1B
// CIR: %[[TRUE:.+]] = cir.const #true
-// CIR: cir.set_bitfield{{.*}}(#{{.+}}, %{{.+}} : !cir.ptr<!u8i>, %[[TRUE]] : !cir.bool) -> !cir.bool
+// CIR: %[[B_PTR:.+]] = cir.load align(8) %{{.+}} : !cir.ptr<!cir.ptr<!rec_B>>, !cir.ptr<!rec_B>
+// CIR: %[[FLAG_PTR:.+]] = cir.get_member %[[B_PTR]][0] {name = "flag"} : !cir.ptr<!rec_B> -> !cir.ptr<!u8i>
+// CIR: cir.set_bitfield align(1) (#bfi_flag, %[[FLAG_PTR]] : !cir.ptr<!u8i>, %[[TRUE]] : !cir.bool) -> !cir.bool
// 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 %{{.+}}
+// LLVM: %[[OLD:.+]] = load i8, ptr %{{.+}}
+// LLVM: %[[CLEARED:.+]] = and i8 %[[OLD]], -2
+// LLVM: %[[NEW:.+]] = or {{.*}}i8 %[[CLEARED]], 1
+// LLVM: store i8 %[[NEW]], ptr %{{.+}}
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: cir.set_bitfield{{.*}}, %[[V]] : !cir.bool) -> !cir.bool
+// CIR: %[[V:.+]] = cir.load align(1) %{{.+}} : !cir.ptr<!cir.bool>, !cir.bool
+// CIR: %[[B_PTR:.+]] = cir.load align(8) %{{.+}} : !cir.ptr<!cir.ptr<!rec_B>>, !cir.ptr<!rec_B>
+// CIR: %[[FLAG_PTR:.+]] = cir.get_member %[[B_PTR]][0] {name = "flag"} : !cir.ptr<!rec_B> -> !cir.ptr<!u8i>
+// CIR: cir.set_bitfield align(1) (#bfi_flag, %[[FLAG_PTR]] : !cir.ptr<!u8i>, %[[V]] : !cir.bool) -> !cir.bool
// LLVM-LABEL: define {{.*}}i1 @_Z24store_bool_bitfield_usedP1Bb
-// LLVM: zext i1 %{{.+}} to i8
-// LLVM: store i8 %{{.+}}, ptr %{{.+}}
-
-// OGCG-LABEL: define {{.*}}i1 @_Z24store_bool_bitfield_usedP1Bb
-// OGCG: zext i1 %{{.+}} to i8
-// OGCG: store i8 %{{.+}}, ptr %{{.+}}
+// LLVM: %{{.+}} = zext i1 %{{.+}} to i8
+// LLVM: %[[CLEARED:.+]] = and i8 %{{.+}}, -2
+// LLVM: %[[NEW:.+]] = or i8 %[[CLEARED]], %{{.+}}
+// LLVM: store i8 %[[NEW]], ptr %{{.+}}
bool load_bool_bitfield(B *b) {
return b->flag;
}
// CIR-LABEL: cir.func{{.*}} @_Z18load_bool_bitfieldP1B
-// CIR: cir.get_bitfield{{.*}}(#{{.+}}, %{{.+}} : !cir.ptr<!u8i>) -> !cir.bool
+// CIR: %[[B_PTR:.+]] = cir.load align(8) %{{.+}} : !cir.ptr<!cir.ptr<!rec_B>>, !cir.ptr<!rec_B>
+// CIR: %[[FLAG_PTR:.+]] = cir.get_member %[[B_PTR]][0] {name = "flag"} : !cir.ptr<!rec_B> -> !cir.ptr<!u8i>
+// CIR: %[[FLAG:.+]] = cir.get_bitfield align(1) (#bfi_flag, %[[FLAG_PTR]] : !cir.ptr<!u8i>) -> !cir.bool
// LLVM-LABEL: define {{.*}}i1 @_Z18load_bool_bitfieldP1B
-// LLVM: load i8, ptr %{{.+}}
-// LLVM: and i8 %{{.+}}, 1
-// LLVM: trunc i8 %{{.+}} to i1
-
-// OGCG-LABEL: define {{.*}}i1 @_Z18load_bool_bitfieldP1B
-// OGCG: load i8, ptr %{{.+}}
-// OGCG: and i8 %{{.+}}, 1
-// OGCG: trunc i8 %{{.+}} to i1
+// LLVM: %[[OLD:.+]] = load i8, ptr %{{.+}}
+// LLVM: %[[MASKED:.+]] = and i8 %[[OLD]], 1
+// LLVM: %{{.+}} = trunc i8 %[[MASKED]] to i1
void compound_or_bool_bitfield(B *b, bool v) {
b->flag |= v;
}
// CIR-LABEL: cir.func{{.*}} @_Z25compound_or_bool_bitfieldP1Bb
-// CIR: cir.get_bitfield{{.*}}-> !cir.bool
-// CIR: cir.set_bitfield{{.*}} : !cir.bool) -> !cir.bool
+// CIR: %[[V:.+]] = cir.load align(1) %{{.+}} : !cir.ptr<!cir.bool>, !cir.bool
+// CIR: %[[V_I32:.+]] = cir.cast bool_to_int %[[V]] : !cir.bool -> !s32i
+// CIR: %[[FLAG_PTR:.+]] = cir.get_member %{{.+}}[0] {name = "flag"} : !cir.ptr<!rec_B> -> !cir.ptr<!u8i>
+// CIR: %[[OLD:.+]] = cir.get_bitfield align(1) (#bfi_flag, %[[FLAG_PTR]] : !cir.ptr<!u8i>) -> !cir.bool
+// CIR: %[[OLD_I32:.+]] = cir.cast bool_to_int %[[OLD]] : !cir.bool -> !s32i
+// CIR: %[[OR:.+]] = cir.or %[[OLD_I32]], %[[V_I32]] : !s32i
+// CIR: %[[NEW:.+]] = cir.cast int_to_bool %[[OR]] : !s32i -> !cir.bool
+// CIR: cir.set_bitfield align(1) (#bfi_flag, %[[FLAG_PTR]] : !cir.ptr<!u8i>, %[[NEW]] : !cir.bool) -> !cir.bool
+
+// LLVM-LABEL: define {{.*}} void @_Z25compound_or_bool_bitfieldP1Bb
+// LLVM: %[[CLEARED:.+]] = and i8 %{{.+}}, -2
+// LLVM: %[[NEW:.+]] = or i8 %[[CLEARED]], %{{.+}}
+// LLVM: store i8 %[[NEW]], ptr %{{.+}}
void copy_bool_bitfield(B *b) {
b->flag = b->other;
}
// CIR-LABEL: cir.func{{.*}} @_Z18copy_bool_bitfieldP1B
-// CIR: %[[OTHER:.+]] = cir.get_bitfield{{.*}}-> !cir.bool
-// CIR: cir.set_bitfield{{.*}}, %[[OTHER]] : !cir.bool) -> !cir.bool
+// CIR: %[[OTHER_PTR:.+]] = cir.get_member %{{.+}}[0] {name = "other"} : !cir.ptr<!rec_B> -> !cir.ptr<!u8i>
+// CIR: %[[OTHER:.+]] = cir.get_bitfield align(1) (#bfi_other, %[[OTHER_PTR]] : !cir.ptr<!u8i>) -> !cir.bool
+// CIR: %[[FLAG_PTR:.+]] = cir.get_member %{{.+}}[0] {name = "flag"} : !cir.ptr<!rec_B> -> !cir.ptr<!u8i>
+// CIR: cir.set_bitfield align(1) (#bfi_flag, %[[FLAG_PTR]] : !cir.ptr<!u8i>, %[[OTHER]] : !cir.bool) -> !cir.bool
+
+// LLVM-LABEL: define {{.*}} void @_Z18copy_bool_bitfieldP1B
+// LLVM: %[[OTHER_BYTE:.+]] = load i8, ptr %{{.+}}
+// LLVM: %[[OTHER_SHIFTED:.+]] = lshr i8 %[[OTHER_BYTE]], 1
+// LLVM: %[[OTHER_BIT:.+]] = and i8 %[[OTHER_SHIFTED]], 1
+// LLVM: %{{.+}} = trunc i8 %[[OTHER_BIT]] to i1
+// LLVM: %[[FLAG_BYTE:.+]] = load i8, ptr %{{.+}}
+// LLVM: %[[FLAG_CLEARED:.+]] = and i8 %[[FLAG_BYTE]], -2
+// LLVM: %[[NEW:.+]] = or i8 %[[FLAG_CLEARED]], %{{.+}}
+// LLVM: store i8 %[[NEW]], ptr %{{.+}}
struct M {
bool b : 1;
@@ -91,13 +105,27 @@ int load_int_bitfield(M *m) {
}
// CIR-LABEL: cir.func{{.*}} @_Z17load_int_bitfieldP1M
-// CIR: cir.get_bitfield{{.*}}(#{{.+}}, %{{.+}} : !cir.ptr<!u8i>) -> !s32i
+// CIR: %[[N_PTR:.+]] = cir.get_member %{{.+}}[0] {name = "n"} : !cir.ptr<!rec_M> -> !cir.ptr<!u8i>
+// CIR: %[[N:.+]] = cir.get_bitfield align(4) (#bfi_n, %[[N_PTR]] : !cir.ptr<!u8i>) -> !s32i
// CIR-NOT: cir.cast int_to_bool
+// LLVM-LABEL: define {{.*}}i32 @_Z17load_int_bitfieldP1M
+// LLVM: %[[BYTE:.+]] = load i8, ptr %{{.+}}
+// LLVM: %[[SHIFTED:.+]] = ashr i8 %[[BYTE]], 1
+// LLVM: %{{.+}} = sext i8 %[[SHIFTED]] to i32
+
void store_int_bitfield(M *m) {
m->n = 5;
}
// CIR-LABEL: cir.func{{.*}} @_Z18store_int_bitfieldP1M
+// CIR: %[[FIVE:.+]] = cir.const #cir.int<5> : !s32i
+// CIR: %[[N_PTR:.+]] = cir.get_member %{{.+}}[0] {name = "n"} : !cir.ptr<!rec_M> -> !cir.ptr<!u8i>
// CIR-NOT: cir.cast bool_to_int
-// CIR: cir.set_bitfield{{.*}}(#{{.+}}, %{{.+}} : !cir.ptr<!u8i>, %{{.+}} : !s32i) -> !s32i
+// CIR: cir.set_bitfield align(4) (#bfi_n, %[[N_PTR]] : !cir.ptr<!u8i>, %[[FIVE]] : !s32i) -> !s32i
+
+// LLVM-LABEL: define {{.*}} void @_Z18store_int_bitfieldP1M
+// LLVM: %[[OLD:.+]] = load i8, ptr %{{.+}}
+// LLVM: %[[CLEARED:.+]] = and i8 %[[OLD]], 1
+// LLVM: %[[NEW:.+]] = or i8 %[[CLEARED]], 10
+// LLVM: store i8 %[[NEW]], ptr %{{.+}}
More information about the cfe-commits
mailing list