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 <[email protected]> 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 <[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 >From 9c77ccc3e1afa4668ef70b196a17644efe8e1bf9 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> 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 <[email protected]> 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 %{{.+}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
