https://github.com/aditya-chaudhary1 updated https://github.com/llvm/llvm-project/pull/231256
>From e87c10ed80320e315e4862e16368a6d2daf46de4 Mon Sep 17 00:00:00 2001 From: Aditya Chaudhary <[email protected]> Date: Fri, 9 Oct 2026 14:51:28 +0530 Subject: [PATCH 1/3] handle rvalue ref of a bit-field in TryStaticImplicitCast --- clang/lib/Sema/SemaCast.cpp | 6 ++++ clang/lib/Sema/SemaInit.cpp | 5 +++- clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp | 28 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp diff --git a/clang/lib/Sema/SemaCast.cpp b/clang/lib/Sema/SemaCast.cpp index c797bdb11e0cb..e97dbd8ecd0f7 100644 --- a/clang/lib/Sema/SemaCast.cpp +++ b/clang/lib/Sema/SemaCast.cpp @@ -1633,6 +1633,12 @@ TryCastResult TryLValueToRValueCast(Sema &Self, Expr *SrcExpr, if (!SrcExpr->isGLValue()) return TC_NotApplicable; + // A bit-field lvalue cannot be directly bound to an rvalue reference; it + // has no address. Fall through to TryStaticImplicitCast so the initializer + // sequence materializes a temporary and binds to that instead. + if (SrcExpr->refersToBitField()) + return TC_NotApplicable; + // Because we try the reference downcast before this function, from now on // this is the only cast possibility, so we issue an error if we fail now. QualType FromType = SrcExpr->getType(); diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 3f30e94aa8976..6b02c943c343f 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -5800,8 +5800,11 @@ static void TryReferenceInitializationCore(Sema &S, // [...] If T1 is reference-related to T2 and the reference is an rvalue // reference, the initializer expression shall not be an lvalue. + // However, a non-referenceable glvalue (bit-field, vector element, matrix + // element) cannot be bound directly, so it is materialized into a + // temporary regardless of value category — don't block it here. if (RefRelationship >= Sema::Ref_Related && !isLValueRef && - InitCategory.isLValue()) { + InitCategory.isLValue() && !isNonReferenceableGLValue(Initializer)) { Sequence.SetFailed( InitializationSequence::FK_RValueReferenceBindingToLValue); return; diff --git a/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp b/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp new file mode 100644 index 0000000000000..6a248132bc302 --- /dev/null +++ b/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp @@ -0,0 +1,28 @@ +// REQUIRES: asserts +// RUN: not --crash %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown \ +// RUN: -emit-llvm -o - %s -DNONCONST 2>&1 | FileCheck %s --check-prefix=CHECK-NONCONST +// RUN: not --crash %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown \ +// RUN: -emit-llvm -o - %s -DCONST 2>&1 | FileCheck %s --check-prefix=CHECK-CONST + +// static_cast<T&&> of a bit-field lvalue should materialize a temporary. +// FIXME: Sema binds the reference directly to the bit-field, so CodeGen +// receives a bit-field LValue and asserts in EmitReferenceBindingToExpr. +// Each case is guarded by its own macro because the crash aborts the whole +// compilation. + +struct A { + int a : 4; +}; + +// CHECK-LABEL: define {{.*}} @_Z4test1A( +void test(A obj) { + #ifdef NONCONST + // CHECK-NONCONST: {{Assertion.*LV.isSimple\(\)}} + int &&ri = static_cast<int&&>(obj.a); + #endif + + #ifdef CONST + // CHECK-CONST: {{Assertion.*LV.isSimple\(\)}} + const int &&cri = static_cast<const int &&>(obj.a); + #endif +} >From 46391d301e178c4fec8ea2dc4800b5c923ab8801 Mon Sep 17 00:00:00 2001 From: Aditya Chaudhary <[email protected]> Date: Mon, 12 Oct 2026 18:45:54 +0530 Subject: [PATCH 2/3] updated the test file and added test to check AST dump --- .../test/AST/ast-dump-rvalue-ref-bitfield.cpp | 32 +++++++++++++++ clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp | 39 +++++++++++-------- 2 files changed, 55 insertions(+), 16 deletions(-) create mode 100644 clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp diff --git a/clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp b/clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp new file mode 100644 index 0000000000000..7d8ecec393d0a --- /dev/null +++ b/clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp @@ -0,0 +1,32 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++11 -fsyntax-only \ +// RUN: -ast-dump -ast-dump-filter test %s | FileCheck %s + +// static_cast<T&&> applied to a bit-field lvalue. A bit-field has no address, +// so the reference binds to a materialized temporary initialized from an +// LValueToRValue conversion of the bit-field. + +struct A { + int a : 4; + int b : 1; +}; + +void test(A obj) { + int &&r = static_cast<int &&>(obj.a); + const int &&cr = static_cast<const int &&>(obj.a); +} + +// CHECK-LABEL: FunctionDecl {{.*}} test 'void (A)' +// CHECK: VarDecl {{.*}} r 'int &&' cinit +// CHECK-NEXT: `-ExprWithCleanups {{.*}} 'int' xvalue +// CHECK-NEXT: `-CXXStaticCastExpr {{.*}} 'int' xvalue static_cast<int &&> <NoOp> +// CHECK-NEXT: `-MaterializeTemporaryExpr {{.*}} 'int' xvalue extended by Var {{.*}} 'r' 'int &&' +// CHECK-NEXT: `-ImplicitCastExpr {{.*}} 'int' <LValueToRValue> +// CHECK-NEXT: `-MemberExpr {{.*}} 'int' lvalue bitfield .a +// CHECK-NEXT: `-DeclRefExpr {{.*}} 'A' lvalue ParmVar {{.*}} 'obj' 'A' +// CHECK: VarDecl {{.*}} cr 'const int &&' cinit +// CHECK-NEXT: `-ExprWithCleanups {{.*}} 'const int' xvalue +// CHECK-NEXT: `-CXXStaticCastExpr {{.*}} 'const int' xvalue static_cast<const int &&> <NoOp> +// CHECK-NEXT: `-MaterializeTemporaryExpr {{.*}} 'const int' xvalue extended by Var {{.*}} 'cr' 'const int &&' +// CHECK-NEXT: `-ImplicitCastExpr {{.*}} 'int' <LValueToRValue> +// CHECK-NEXT: `-MemberExpr {{.*}} 'int' lvalue bitfield .a +// CHECK-NEXT: `-DeclRefExpr {{.*}} 'A' lvalue ParmVar {{.*}} 'obj' 'A' diff --git a/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp b/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp index 6a248132bc302..7144d9c845a9d 100644 --- a/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp +++ b/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp @@ -1,14 +1,8 @@ -// REQUIRES: asserts -// RUN: not --crash %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown \ -// RUN: -emit-llvm -o - %s -DNONCONST 2>&1 | FileCheck %s --check-prefix=CHECK-NONCONST -// RUN: not --crash %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown \ -// RUN: -emit-llvm -o - %s -DCONST 2>&1 | FileCheck %s --check-prefix=CHECK-CONST +// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown \ +// RUN: -emit-llvm -o - %s | FileCheck %s -// static_cast<T&&> of a bit-field lvalue should materialize a temporary. -// FIXME: Sema binds the reference directly to the bit-field, so CodeGen -// receives a bit-field LValue and asserts in EmitReferenceBindingToExpr. -// Each case is guarded by its own macro because the crash aborts the whole -// compilation. +// static_cast<T&&> of a bit-field lvalue materializes a temporary holding the +// loaded bit-field value, and the reference binds to that temporary. struct A { int a : 4; @@ -16,13 +10,26 @@ struct A { // CHECK-LABEL: define {{.*}} @_Z4test1A( void test(A obj) { - #ifdef NONCONST - // CHECK-NONCONST: {{Assertion.*LV.isSimple\(\)}} + // CHECK: %[[OBJ:.*]] = alloca %struct.A + // CHECK-NEXT: %[[RI:.*]] = alloca ptr + // CHECK-NEXT: %[[TMP1:.*]] = alloca i32 + // CHECK-NEXT: %[[CRI:.*]] = alloca ptr + // CHECK-NEXT: %[[TMP2:.*]] = alloca i32 + // CHECK-NEXT: store i32 %obj.coerce, ptr %[[OBJ]] + + // CHECK-NEXT: %[[LD1:.*]] = load i8, ptr %[[OBJ]] + // CHECK-NEXT: %[[SHL1:.*]] = shl i8 %[[LD1]], 4 + // CHECK-NEXT: %[[ASHR1:.*]] = ashr i8 %[[SHL1]], 4 + // CHECK-NEXT: %[[CAST1:.*]] = sext i8 %[[ASHR1]] to i32 + // CHECK-NEXT: store i32 %[[CAST1]], ptr %[[TMP1]] + // CHECK-NEXT: store ptr %[[TMP1]], ptr %[[RI]] int &&ri = static_cast<int&&>(obj.a); - #endif - #ifdef CONST - // CHECK-CONST: {{Assertion.*LV.isSimple\(\)}} + // CHECK-NEXT: %[[LD2:.*]] = load i8, ptr %[[OBJ]] + // CHECK-NEXT: %[[SHL2:.*]] = shl i8 %[[LD2]], 4 + // CHECK-NEXT: %[[ASHR2:.*]] = ashr i8 %[[SHL2]], 4 + // CHECK-NEXT: %[[CAST2:.*]] = sext i8 %[[ASHR2]] to i32 + // CHECK-NEXT: store i32 %[[CAST2]], ptr %[[TMP2]] + // CHECK-NEXT: store ptr %[[TMP2]], ptr %[[CRI]] const int &&cri = static_cast<const int &&>(obj.a); - #endif } >From 19c12610211f15536ed8b20c70b3ae5d19e323ca Mon Sep 17 00:00:00 2001 From: Aditya Chaudhary <[email protected]> Date: Mon, 19 Oct 2026 14:53:19 +0530 Subject: [PATCH 3/3] added test file --- clang/lib/Sema/SemaCast.cpp | 34 ++++++++++------- clang/lib/Sema/SemaInit.cpp | 5 +-- .../test/AST/ast-dump-rvalue-ref-bitfield.cpp | 21 +++++----- .../dcl.decl/dcl.init/dcl.init.ref/p5-0x.cpp | 1 + clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp | 4 +- .../static-cast-bitfield-temporary.cpp | 38 +++++++++++++++++++ 6 files changed, 72 insertions(+), 31 deletions(-) create mode 100644 clang/test/SemaCXX/static-cast-bitfield-temporary.cpp diff --git a/clang/lib/Sema/SemaCast.cpp b/clang/lib/Sema/SemaCast.cpp index e97dbd8ecd0f7..924acc65e76bf 100644 --- a/clang/lib/Sema/SemaCast.cpp +++ b/clang/lib/Sema/SemaCast.cpp @@ -262,11 +262,10 @@ static void DiagnoseCastQual(Sema &Self, const ExprResult &SrcExpr, // %0: Cast Type (a value from the CastType enumeration) // %1: Source Type // %2: Destination Type -static TryCastResult TryLValueToRValueCast(Sema &Self, Expr *SrcExpr, - QualType DestType, bool CStyle, - SourceRange OpRange, CastKind &Kind, - CXXCastPath &BasePath, - unsigned &msg); +static TryCastResult +TryLValueToRValueCast(Sema &Self, ExprResult &SrcExprResult, QualType DestType, + bool CStyle, SourceRange OpRange, CastKind &Kind, + CXXCastPath &BasePath, unsigned &msg); static TryCastResult TryStaticReferenceDowncast(Sema &Self, Expr *SrcExpr, QualType DestType, bool CStyle, CastOperation::OpRangeType OpRange, @@ -1431,8 +1430,10 @@ static TryCastResult TryStaticCast(Sema &Self, ExprResult &SrcExpr, // C++11 [expr.static.cast]p3: // A glvalue of type "cv1 T1" can be cast to type "rvalue reference to cv2 // T2" if "cv2 T2" is reference-compatible with "cv1 T1". - tcr = TryLValueToRValueCast(Self, SrcExpr.get(), DestType, CStyle, OpRange, - Kind, BasePath, msg); + tcr = TryLValueToRValueCast(Self, SrcExpr, DestType, CStyle, OpRange, Kind, + BasePath, msg); + if (SrcExpr.isInvalid()) + return TC_Failed; if (tcr != TC_NotApplicable) return tcr; @@ -1619,10 +1620,11 @@ static TryCastResult TryStaticCast(Sema &Self, ExprResult &SrcExpr, } /// Tests whether a conversion according to N2844 is valid. -TryCastResult TryLValueToRValueCast(Sema &Self, Expr *SrcExpr, +TryCastResult TryLValueToRValueCast(Sema &Self, ExprResult &SrcExprResult, QualType DestType, bool CStyle, SourceRange OpRange, CastKind &Kind, CXXCastPath &BasePath, unsigned &msg) { + Expr *SrcExpr = SrcExprResult.get(); // C++11 [expr.static.cast]p3: // A glvalue of type "cv1 T1" can be cast to type "rvalue reference to // cv2 T2" if "cv2 T2" is reference-compatible with "cv1 T1". @@ -1633,12 +1635,6 @@ TryCastResult TryLValueToRValueCast(Sema &Self, Expr *SrcExpr, if (!SrcExpr->isGLValue()) return TC_NotApplicable; - // A bit-field lvalue cannot be directly bound to an rvalue reference; it - // has no address. Fall through to TryStaticImplicitCast so the initializer - // sequence materializes a temporary and binds to that instead. - if (SrcExpr->refersToBitField()) - return TC_NotApplicable; - // Because we try the reference downcast before this function, from now on // this is the only cast possibility, so we issue an error if we fail now. QualType FromType = SrcExpr->getType(); @@ -1662,6 +1658,16 @@ TryCastResult TryLValueToRValueCast(Sema &Self, Expr *SrcExpr, return TC_Failed; } + // C++ [expr.static.cast]p3: + // If the value is not a bit-field, the result refers to the object or the + // specified base class subobject thereof; otherwise, the lvalue-to-rvalue + // conversion is applied to the bit-field and the resulting prvalue is used + // as the operand of the static_cast for the remainder of this subclause. + if (SrcExpr->refersToBitField()) { + SrcExprResult = Self.DefaultLvalueConversion(SrcExpr); + return TC_NotApplicable; + } + if (RefConv & Sema::ReferenceConversions::DerivedToBase) { Kind = CK_DerivedToBase; if (Self.CheckDerivedToBaseConversion(FromType, ToType, diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 6b02c943c343f..3f30e94aa8976 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -5800,11 +5800,8 @@ static void TryReferenceInitializationCore(Sema &S, // [...] If T1 is reference-related to T2 and the reference is an rvalue // reference, the initializer expression shall not be an lvalue. - // However, a non-referenceable glvalue (bit-field, vector element, matrix - // element) cannot be bound directly, so it is materialized into a - // temporary regardless of value category — don't block it here. if (RefRelationship >= Sema::Ref_Related && !isLValueRef && - InitCategory.isLValue() && !isNonReferenceableGLValue(Initializer)) { + InitCategory.isLValue()) { Sequence.SetFailed( InitializationSequence::FK_RValueReferenceBindingToLValue); return; diff --git a/clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp b/clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp index 7d8ecec393d0a..5e2b994c3b239 100644 --- a/clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp +++ b/clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++11 -fsyntax-only \ +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++17 -fsyntax-only \ // RUN: -ast-dump -ast-dump-filter test %s | FileCheck %s // static_cast<T&&> applied to a bit-field lvalue. A bit-field has no address, @@ -7,15 +7,9 @@ struct A { int a : 4; - int b : 1; }; - -void test(A obj) { - int &&r = static_cast<int &&>(obj.a); - const int &&cr = static_cast<const int &&>(obj.a); -} - // CHECK-LABEL: FunctionDecl {{.*}} test 'void (A)' +void test(A obj) { // CHECK: VarDecl {{.*}} r 'int &&' cinit // CHECK-NEXT: `-ExprWithCleanups {{.*}} 'int' xvalue // CHECK-NEXT: `-CXXStaticCastExpr {{.*}} 'int' xvalue static_cast<int &&> <NoOp> @@ -23,10 +17,15 @@ void test(A obj) { // CHECK-NEXT: `-ImplicitCastExpr {{.*}} 'int' <LValueToRValue> // CHECK-NEXT: `-MemberExpr {{.*}} 'int' lvalue bitfield .a // CHECK-NEXT: `-DeclRefExpr {{.*}} 'A' lvalue ParmVar {{.*}} 'obj' 'A' + int &&r = static_cast<int &&>(obj.a); + // CHECK: VarDecl {{.*}} cr 'const int &&' cinit // CHECK-NEXT: `-ExprWithCleanups {{.*}} 'const int' xvalue // CHECK-NEXT: `-CXXStaticCastExpr {{.*}} 'const int' xvalue static_cast<const int &&> <NoOp> // CHECK-NEXT: `-MaterializeTemporaryExpr {{.*}} 'const int' xvalue extended by Var {{.*}} 'cr' 'const int &&' -// CHECK-NEXT: `-ImplicitCastExpr {{.*}} 'int' <LValueToRValue> -// CHECK-NEXT: `-MemberExpr {{.*}} 'int' lvalue bitfield .a -// CHECK-NEXT: `-DeclRefExpr {{.*}} 'A' lvalue ParmVar {{.*}} 'obj' 'A' +// CHECK-NEXT: `-ImplicitCastExpr {{.*}} 'const int' <NoOp> +// CHECK-NEXT: `-ImplicitCastExpr {{.*}} 'int' <LValueToRValue> +// CHECK-NEXT: `-MemberExpr {{.*}} 'int' lvalue bitfield .a +// CHECK-NEXT: `-DeclRefExpr {{.*}} 'A' lvalue ParmVar {{.*}} 'obj' 'A' + const int &&cr = static_cast<const int &&>(obj.a); +} diff --git a/clang/test/CXX/dcl.decl/dcl.init/dcl.init.ref/p5-0x.cpp b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.ref/p5-0x.cpp index e2349774ed73c..4b7bf31773c96 100644 --- a/clang/test/CXX/dcl.decl/dcl.init/dcl.init.ref/p5-0x.cpp +++ b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.ref/p5-0x.cpp @@ -227,6 +227,7 @@ namespace bitfields { const int & cir2 = (xvalue<IntBitfield>().i); // no-warning const int && cir3 = (xvalue<IntBitfield>().i); // no-warning const int && cir4 = move(lvalue<IntBitfield>()).i; // no-warning + const int && cir5 = (lvalue<IntBitfield>().i); // expected-error{{rvalue reference to type 'const int' cannot bind to lvalue of type 'int'}} const volatile int & cvir1 = (lvalue<IntBitfield>().i); // expected-error{{non-const reference cannot bind to bit-field 'i'}} const volatile int & cvir2 = (xvalue<IntBitfield>().i); // expected-error{{volatile lvalue reference to type 'const volatile int' cannot bind to a temporary of type 'int'}} diff --git a/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp b/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp index 7144d9c845a9d..7b25994b06d6a 100644 --- a/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp +++ b/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp @@ -1,5 +1,5 @@ -// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown \ -// RUN: -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-unknown \ +// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s // static_cast<T&&> of a bit-field lvalue materializes a temporary holding the // loaded bit-field value, and the reference binds to that temporary. diff --git a/clang/test/SemaCXX/static-cast-bitfield-temporary.cpp b/clang/test/SemaCXX/static-cast-bitfield-temporary.cpp new file mode 100644 index 0000000000000..847c53bed06fd --- /dev/null +++ b/clang/test/SemaCXX/static-cast-bitfield-temporary.cpp @@ -0,0 +1,38 @@ +// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-unknown -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-unknown -fsyntax-only -verify -fexperimental-new-constant-interpreter %s + +struct A { + unsigned x : 1; +}; + +constexpr unsigned f1() { + A a{0}; + unsigned &&r = static_cast<unsigned &&>(a.x); + r = 7; + a.x = 0; + return r; +} +static_assert(f1() == 7); + +// FIXME: the temporary's lifetime should expire at the end of the +// full-expression; reading through p after that point must be rejected. +// Currently a different Sema error fires due to the direct bit-field binding. +constexpr auto &q(auto &&a) { return a; } // expected-error {{non-const lvalue reference to type 'unsigned int' cannot bind to a temporary of type 'unsigned int'}} +constexpr unsigned f2() { + A a{0}; + unsigned *p; + *(p = &q(static_cast<unsigned &&>(a.x))) = 1; // expected-note {{in instantiation of function template specialization 'q<unsigned int>' requested here}} + // expected-note@-1 {{subexpression not valid in a constant expression}} + return *p; +} +static_assert(f2() == 1); // expected-error {{static assertion expression is not an integral constant expression}} + // expected-note@-1 {{in call to 'f2()'}} + +constexpr unsigned f3() { + A a{0}; + unsigned &&r = const_cast<unsigned &&>(static_cast<const unsigned &&>(a.x)); + r = 0; // expected-note {{modification of object of const-qualified type 'const unsigned int' is not allowed in a constant expression}} + return a.x; +} +static_assert(f3() == 0); // expected-error {{static assertion expression is not an integral constant expression}} + // expected-note@-1 {{in call to 'f3()'}} \ No newline at end of file _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
