https://github.com/Nemi26 updated https://github.com/llvm/llvm-project/pull/231830
>From 8d9036d2eca6528ac473479d8eccaa55a31c48e9 Mon Sep 17 00:00:00 2001 From: Nemi26 <[email protected]> Date: Wed, 14 Oct 2026 11:28:07 +0200 Subject: [PATCH 1/3] [clang] Load non-simple lvalue operands of __builtin_bit_cast CK_LValueToRValueBitCast reads its operand through the operand's address. Sema only materialized prvalue operands, so a glvalue without an address of its own reached CodeGen as is: a vector element, ext_vector swizzle, matrix element, bit-field or global register variable. CodeGen took the address of the containing vector, matrix or bit-field storage unit and read from its start, so `__builtin_bit_cast(unsigned, v.z)` returned the bits of lane 0, while constant evaluation of the same expression gave the right answer. Apply the lvalue-to-rvalue conversion to such operands in Sema and materialize the result, as is already done for prvalues, and assert in CodeGen that the operand lvalue is simple. The constant evaluator now reports an uninitialized vector element read through a bit_cast as a read of an uninitialized object, rather than as an invalid subexpression. Fixes #95673 Fixes #219531 Fixes #226388 This patch was written with the assistance of an AI tool (Claude Code, Claude Opus); the submitter reviewed it. Assisted-by: Claude Code (Claude Opus) --- clang/docs/ReleaseNotes.md | 4 + clang/lib/CodeGen/CGExprScalar.cpp | 1 + clang/lib/Sema/SemaCast.cpp | 9 ++ .../builtin-bit-cast-nonsimple-lvalue.cpp | 92 +++++++++++++++++++ .../SemaCXX/constexpr-builtin-bit-cast.cpp | 14 +++ .../constexpr-vectors-access-elements.cpp | 13 ++- 6 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 clang/test/CodeGenCXX/builtin-bit-cast-nonsimple-lvalue.cpp diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 0103c295379207..e3594e4993e8e5 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -756,6 +756,10 @@ features cannot lower the translation-unit ABI level; - Fixed an assertion failure when `__builtin_object_size` or `__builtin_dynamic_object_size` is called with an out-of-range type argument during constant evaluation. (#GH225041) +- Fixed wrong code generation for `__builtin_bit_cast` of a vector element, + matrix element, bit-field or global register variable, which read from the + start of the containing object instead of the element or field. + (#GH95673), (#GH219531), (#GH226388) #### Bug Fixes to Attribute Support diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp index db2b7a4ad1fce2..5dccebcf842a87 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -2655,6 +2655,7 @@ Value *ScalarExprEmitter::VisitCastExpr(CastExpr *CE) { case CK_LValueToRValueBitCast: { LValue SourceLVal = CGF.EmitLValue(E); + assert(SourceLVal.isSimple() && "bit_cast operand has no address"); Address Addr = SourceLVal.getAddress().withElementType(CGF.ConvertTypeForMem(DestTy)); LValue DestLV = CGF.MakeAddrLValue(Addr, DestTy); diff --git a/clang/lib/Sema/SemaCast.cpp b/clang/lib/Sema/SemaCast.cpp index c797bdb11e0cbd..7ebb2deb4ad48c 100644 --- a/clang/lib/Sema/SemaCast.cpp +++ b/clang/lib/Sema/SemaCast.cpp @@ -3404,6 +3404,15 @@ void CastOperation::CheckBuiltinBitCast() { return; } + if (SrcExpr.get()->isGLValue() && + (SrcExpr.get()->getObjectKind() != OK_Ordinary || + SrcExpr.get()->refersToGlobalRegisterVar())) { + SrcExpr = Self.DefaultLvalueConversion(SrcExpr.get()); + if (SrcExpr.isInvalid()) + return; + SrcType = SrcExpr.get()->getType(); + } + if (SrcExpr.get()->isPRValue()) SrcExpr = Self.CreateMaterializeTemporaryExpr(SrcType, SrcExpr.get(), /*IsLValueReference=*/false); diff --git a/clang/test/CodeGenCXX/builtin-bit-cast-nonsimple-lvalue.cpp b/clang/test/CodeGenCXX/builtin-bit-cast-nonsimple-lvalue.cpp new file mode 100644 index 00000000000000..8818c714d73b84 --- /dev/null +++ b/clang/test/CodeGenCXX/builtin-bit-cast-nonsimple-lvalue.cpp @@ -0,0 +1,92 @@ +// RUN: %clang_cc1 -std=c++20 -fenable-matrix -emit-llvm -disable-llvm-passes -triple x86_64-unknown-linux-gnu %s -o - | FileCheck %s + +// An operand without an address of its own is loaded through the lvalue and +// materialized, rather than read from the start of the containing object. + +typedef float v4f __attribute__((ext_vector_type(4))); +typedef float gv4f __attribute__((vector_size(16))); +typedef float m2x2f __attribute__((matrix_type(2, 2))); + +// CHECK-LABEL: define{{.*}} i32 @_Z18ext_vector_elementDv4_f( +// CHECK: [[TMP:%.*]] = alloca float +// CHECK: [[VEC:%.*]] = load <4 x float>, ptr %v.addr +// CHECK-NEXT: [[ELT:%.*]] = extractelement <4 x float> [[VEC]], i64 2 +// CHECK-NEXT: store float [[ELT]], ptr [[TMP]] +// CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[TMP]] +// CHECK-NEXT: ret i32 [[RES]] +unsigned ext_vector_element(v4f v) { + return __builtin_bit_cast(unsigned, v.z); +} + +// CHECK-LABEL: define{{.*}} i32 @_Z20ext_vector_subscriptDv4_f( +// CHECK: [[TMP:%.*]] = alloca float +// CHECK: [[VEC:%.*]] = load <4 x float>, ptr %v.addr +// CHECK-NEXT: [[ELT:%.*]] = extractelement <4 x float> [[VEC]], i32 1 +// CHECK-NEXT: store float [[ELT]], ptr [[TMP]] +// CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[TMP]] +// CHECK-NEXT: ret i32 [[RES]] +unsigned ext_vector_subscript(v4f v) { + return __builtin_bit_cast(unsigned, v[1]); +} + +// CHECK-LABEL: define{{.*}} i64 @_Z18ext_vector_swizzleDv4_f( +// CHECK: [[TMP:%.*]] = alloca <2 x float> +// CHECK: [[VEC:%.*]] = load <4 x float>, ptr %v.addr +// CHECK-NEXT: [[ELTS:%.*]] = shufflevector <4 x float> [[VEC]], <4 x float> poison, <2 x i32> <i32 2, i32 3> +// CHECK-NEXT: store <2 x float> [[ELTS]], ptr [[TMP]] +// CHECK-NEXT: [[RES:%.*]] = load i64, ptr [[TMP]] +// CHECK-NEXT: ret i64 [[RES]] +unsigned long long ext_vector_swizzle(v4f v) { + return __builtin_bit_cast(unsigned long long, v.zw); +} + +// CHECK-LABEL: define{{.*}} i32 @_Z20gnu_vector_subscriptDv4_fi( +// CHECK: [[TMP:%.*]] = alloca float +// CHECK: [[VEC:%.*]] = load <4 x float>, ptr %v.addr +// CHECK-NEXT: [[IDX:%.*]] = load i32, ptr %i.addr +// CHECK-NEXT: [[ELT:%.*]] = extractelement <4 x float> [[VEC]], i32 [[IDX]] +// CHECK-NEXT: store float [[ELT]], ptr [[TMP]] +// CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[TMP]] +// CHECK-NEXT: ret i32 [[RES]] +unsigned gnu_vector_subscript(gv4f v, int i) { + return __builtin_bit_cast(unsigned, v[i]); +} + +// CHECK-LABEL: define{{.*}} i32 @_Z14matrix_elementu11matrix_typeILm2ELm2EfE( +// CHECK: [[TMP:%.*]] = alloca float +// CHECK: [[MAT:%.*]] = load <4 x float>, ptr %m.addr +// CHECK-NEXT: [[ELT:%.*]] = extractelement <4 x float> [[MAT]], i64 3 +// CHECK-NEXT: store float [[ELT]], ptr [[TMP]] +// CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[TMP]] +// CHECK-NEXT: ret i32 [[RES]] +unsigned matrix_element(m2x2f m) { + return __builtin_bit_cast(unsigned, m[1][1]); +} + +struct S { + unsigned a : 4, b : 12; +}; + +// CHECK-LABEL: define{{.*}} i32 @_Z9bit_field1S( +// CHECK: [[TMP:%.*]] = alloca i32 +// CHECK: [[LOAD:%.*]] = load i16, ptr %s +// CHECK-NEXT: [[SHR:%.*]] = lshr i16 [[LOAD]], 4 +// CHECK-NEXT: [[EXT:%.*]] = zext i16 [[SHR]] to i32 +// CHECK-NEXT: store i32 [[EXT]], ptr [[TMP]] +// CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[TMP]] +// CHECK-NEXT: ret i32 [[RES]] +unsigned bit_field(S s) { + return __builtin_bit_cast(unsigned, s.b); +} + +register unsigned long stack_pointer asm("rsp"); + +// CHECK-LABEL: define{{.*}} i64 @_Z15global_registerv( +// CHECK: [[TMP:%.*]] = alloca i64 +// CHECK: [[REG:%.*]] = call i64 @llvm.read_register.i64( +// CHECK-NEXT: store i64 [[REG]], ptr [[TMP]] +// CHECK-NEXT: [[RES:%.*]] = load i64, ptr [[TMP]] +// CHECK-NEXT: ret i64 [[RES]] +long global_register() { + return __builtin_bit_cast(long, stack_pointer); +} diff --git a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp index ea5333df0b4698..db53076891b47b 100644 --- a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp +++ b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp @@ -491,6 +491,20 @@ static_assert(bit_cast<unsigned long long>(test_vector) == (LITTLE_END static_assert(round_trip<uint2>(0xCAFEBABE0C05FEFEULL), ""); static_assert(round_trip<byte8>(0xCAFEBABE0C05FEFEULL), ""); +typedef unsigned uint2e __attribute__((ext_vector_type(2))); +constexpr uint2e test_ext_vector = { 0x0C05FEFE, 0xCAFEBABE }; + +// Elements are read by value, not from the start of the vector. +static_assert(__builtin_bit_cast(unsigned, test_vector[1]) == 0xCAFEBABE, ""); +static_assert(__builtin_bit_cast(unsigned, test_ext_vector[1]) == 0xCAFEBABE, ""); +static_assert(__builtin_bit_cast(unsigned, test_ext_vector.y) == 0xCAFEBABE, ""); + +struct BitField { + unsigned a : 4, b : 12; +}; +constexpr BitField test_bit_field = { 0xF, 0x123 }; +static_assert(__builtin_bit_cast(unsigned, test_bit_field.b) == 0x123, ""); + typedef bool bool8 __attribute__((ext_vector_type(8))); typedef bool bool9 __attribute__((ext_vector_type(9))); typedef bool bool16 __attribute__((ext_vector_type(16))); diff --git a/clang/test/SemaCXX/constexpr-vectors-access-elements.cpp b/clang/test/SemaCXX/constexpr-vectors-access-elements.cpp index 481b80ceb9c1e0..c6eb66f6b2995a 100644 --- a/clang/test/SemaCXX/constexpr-vectors-access-elements.cpp +++ b/clang/test/SemaCXX/constexpr-vectors-access-elements.cpp @@ -65,17 +65,22 @@ static_assert(t2[15] == -2); using I = int __attribute__((vector_size(16))); -// expected-error@+1 {{constexpr function never produces a constant expression}} constexpr unsigned test2() { - // cpp17-warning@+1 {{uninitialized variable in a constexpr function is a C++20 extension}} + // cpp17-warning@+2 {{uninitialized variable in a constexpr function is a C++20 extension}} + // expected-note@+1 {{declared here}} I v; - // expected-note@+2 {{subexpression not valid in a constant expression}} - // expected-note@+1 {{subexpression not valid in a constant expression}} + // expected-note@+1 {{read of uninitialized object is not allowed in a constant expression}} return __builtin_bit_cast(unsigned, v[0]); } // expected-error@+2 {{static assertion expression is not an integral constant expression}} // expected-note@+1 {{in call to 'test2()'}} static_assert(test2(), ""); + +constexpr unsigned test3() { + I v = {1, 2, 3, 4}; + return __builtin_bit_cast(unsigned, v[1]); +} +static_assert(test3() == 2, ""); } >From aa3d803912acc60b3490a5fe2514b1f6ebb97e83 Mon Sep 17 00:00:00 2001 From: Nemi26 <[email protected]> Date: Sun, 18 Oct 2026 21:31:50 +0200 Subject: [PATCH 2/3] [clang] Bind the __builtin_bit_cast operand as a const reference Initialize the operand as std::bit_cast initializes its 'const From &' parameter, where From keeps the qualifiers of a glvalue operand. Vector elements, matrix elements and bit-fields are copied into a temporary, prvalues are materialized as before, and volatile operands are still accepted; the load from a volatile operand is now volatile. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> --- clang/docs/ReleaseNotes.md | 8 ++-- clang/lib/AST/ExprConstant.cpp | 2 +- clang/lib/CodeGen/CGExprScalar.cpp | 3 +- clang/lib/Sema/SemaCast.cpp | 22 ++++----- clang/test/AST/ByteCode/builtin-bit-cast.cpp | 2 +- clang/test/CXX/drs/cwg722.cpp | 10 ++-- .../builtin-bit-cast-nonsimple-lvalue.cpp | 46 +++++++++++-------- clang/test/SemaCXX/builtin-bit-cast.cpp | 11 +++++ .../SemaCXX/constexpr-builtin-bit-cast.cpp | 2 +- 9 files changed, 63 insertions(+), 43 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index e3594e4993e8e5..0ca5c29f0cd0a4 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -756,9 +756,11 @@ features cannot lower the translation-unit ABI level; - Fixed an assertion failure when `__builtin_object_size` or `__builtin_dynamic_object_size` is called with an out-of-range type argument during constant evaluation. (#GH225041) -- Fixed wrong code generation for `__builtin_bit_cast` of a vector element, - matrix element, bit-field or global register variable, which read from the - start of the containing object instead of the element or field. +- `__builtin_bit_cast` now binds its operand as `std::bit_cast` binds its + `const From &` parameter. A vector element, matrix element or bit-field is + copied into a temporary, which is then read; previously the read started at + the containing vector, matrix or bit-field storage unit, which gave the wrong + value. A volatile operand is now read with a volatile load. (#GH95673), (#GH219531), (#GH226388) #### Bug Fixes to Attribute Support diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 87909992a46d51..dafdf5a85cf592 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -8552,7 +8552,7 @@ static bool checkBitCastConstexprEligibilityType(SourceLocation Loc, auto note = [&](int Construct, QualType NoteTy, SourceLocation NoteLoc) { if (Info) Info->Note(NoteLoc, diag::note_constexpr_bit_cast_invalid_subtype) - << NoteTy << Construct << Ty; + << NoteTy << Construct << Ty.getUnqualifiedType(); return false; }; diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp index 5dccebcf842a87..f56abaa60efd7c 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -2655,10 +2655,11 @@ Value *ScalarExprEmitter::VisitCastExpr(CastExpr *CE) { case CK_LValueToRValueBitCast: { LValue SourceLVal = CGF.EmitLValue(E); - assert(SourceLVal.isSimple() && "bit_cast operand has no address"); Address Addr = SourceLVal.getAddress().withElementType(CGF.ConvertTypeForMem(DestTy)); LValue DestLV = CGF.MakeAddrLValue(Addr, DestTy); + if (SourceLVal.isVolatileQualified()) + DestLV.getQuals().addVolatile(); DestLV.setTBAAInfo(TBAAAccessInfo::getMayAliasInfo()); return EmitLoadOfLValue(DestLV, CE->getExprLoc()); } diff --git a/clang/lib/Sema/SemaCast.cpp b/clang/lib/Sema/SemaCast.cpp index 7ebb2deb4ad48c..0539146ff1c856 100644 --- a/clang/lib/Sema/SemaCast.cpp +++ b/clang/lib/Sema/SemaCast.cpp @@ -3404,19 +3404,6 @@ void CastOperation::CheckBuiltinBitCast() { return; } - if (SrcExpr.get()->isGLValue() && - (SrcExpr.get()->getObjectKind() != OK_Ordinary || - SrcExpr.get()->refersToGlobalRegisterVar())) { - SrcExpr = Self.DefaultLvalueConversion(SrcExpr.get()); - if (SrcExpr.isInvalid()) - return; - SrcType = SrcExpr.get()->getType(); - } - - if (SrcExpr.get()->isPRValue()) - SrcExpr = Self.CreateMaterializeTemporaryExpr(SrcType, SrcExpr.get(), - /*IsLValueReference=*/false); - CharUnits DestSize = Self.Context.getTypeSizeInChars(DestType); CharUnits SourceSize = Self.Context.getTypeSizeInChars(SrcType); if (DestSize != SourceSize) { @@ -3441,6 +3428,15 @@ void CastOperation::CheckBuiltinBitCast() { return; } + // The operand binds as it would to the 'const From &' of std::bit_cast. + QualType RefType = Self.Context.getLValueReferenceType(SrcType.withConst()); + SrcExpr = Self.PerformCopyInitialization( + InitializedEntity::InitializeParameter(Self.Context, RefType, + /*Consumed=*/false), + SrcExpr.get()->getBeginLoc(), SrcExpr); + if (SrcExpr.isInvalid()) + return; + Kind = CK_LValueToRValueBitCast; } diff --git a/clang/test/AST/ByteCode/builtin-bit-cast.cpp b/clang/test/AST/ByteCode/builtin-bit-cast.cpp index 368728d00a8121..b548b5290d25f3 100644 --- a/clang/test/AST/ByteCode/builtin-bit-cast.cpp +++ b/clang/test/AST/ByteCode/builtin-bit-cast.cpp @@ -428,7 +428,7 @@ struct vol_mem { }; // both-error@+2 {{constexpr variable 'run_vol_mem' must be initialized by a constant expression}} -// both-note@+1 {{non-literal type 'vol_mem' cannot be used in a constant expression}} +// both-note-re@+1 {{non-literal type '{{(const )?}}vol_mem' cannot be used in a constant expression}} constexpr int run_vol_mem = __builtin_bit_cast(int, vol_mem{43}); struct mem_ptr { diff --git a/clang/test/CXX/drs/cwg722.cpp b/clang/test/CXX/drs/cwg722.cpp index 23f90a5601274c..ad323a1f7b8e84 100644 --- a/clang/test/CXX/drs/cwg722.cpp +++ b/clang/test/CXX/drs/cwg722.cpp @@ -47,10 +47,10 @@ void h() { // CHECK-NEXT: | `-DeclRefExpr {{.+}} 'std::nullptr_t ()' lvalue Function {{.+}} 'g' 'std::nullptr_t ()' // CHECK-NEXT: `-ImplicitCastExpr {{.+}} 'void *' <NullToPointer> // CHECK-NEXT: `-BuiltinBitCastExpr {{.+}} 'std::nullptr_t' <LValueToRValueBitCast> -// CHECK-NEXT: `-MaterializeTemporaryExpr {{.+}} 'void *' xvalue -// CHECK-NEXT: `-CXXStaticCastExpr {{.+}} 'void *' static_cast<void *> <NoOp> -// CHECK-NEXT: `-ImplicitCastExpr {{.+}} 'void *' <BitCast> part_of_explicit_cast -// CHECK-NEXT: `-UnaryOperator {{.+}} 'int *' prefix '&' cannot overflow -// CHECK-NEXT: `-DeclRefExpr {{.+}} 'int' lvalue Var {{.+}} 'i' 'int' +// CHECK-NEXT: `-MaterializeTemporaryExpr {{.+}} 'void *const' lvalue +// CHECK: `-CXXStaticCastExpr {{.+}} 'void *' static_cast<void *> <NoOp> +// CHECK-NEXT: `-ImplicitCastExpr {{.+}} 'void *' <BitCast> part_of_explicit_cast +// CHECK-NEXT: `-UnaryOperator {{.+}} 'int *' prefix '&' cannot overflow +// CHECK-NEXT: `-DeclRefExpr {{.+}} 'int' lvalue Var {{.+}} 'i' 'int' } #endif diff --git a/clang/test/CodeGenCXX/builtin-bit-cast-nonsimple-lvalue.cpp b/clang/test/CodeGenCXX/builtin-bit-cast-nonsimple-lvalue.cpp index 8818c714d73b84..f58d25412f686b 100644 --- a/clang/test/CodeGenCXX/builtin-bit-cast-nonsimple-lvalue.cpp +++ b/clang/test/CodeGenCXX/builtin-bit-cast-nonsimple-lvalue.cpp @@ -1,7 +1,7 @@ -// RUN: %clang_cc1 -std=c++20 -fenable-matrix -emit-llvm -disable-llvm-passes -triple x86_64-unknown-linux-gnu %s -o - | FileCheck %s +// RUN: %clang_cc1 -std=c++20 -fenable-matrix -O -emit-llvm -disable-llvm-passes -triple x86_64-unknown-linux-gnu %s -o - | FileCheck %s -// An operand without an address of its own is loaded through the lvalue and -// materialized, rather than read from the start of the containing object. +// An operand without an address of its own is copied into a temporary, which +// is then read. typedef float v4f __attribute__((ext_vector_type(4))); typedef float gv4f __attribute__((vector_size(16))); @@ -9,10 +9,12 @@ typedef float m2x2f __attribute__((matrix_type(2, 2))); // CHECK-LABEL: define{{.*}} i32 @_Z18ext_vector_elementDv4_f( // CHECK: [[TMP:%.*]] = alloca float -// CHECK: [[VEC:%.*]] = load <4 x float>, ptr %v.addr +// CHECK: call void @llvm.lifetime.start.p0(ptr [[TMP]]) +// CHECK-NEXT: [[VEC:%.*]] = load <4 x float>, ptr %v.addr // CHECK-NEXT: [[ELT:%.*]] = extractelement <4 x float> [[VEC]], i64 2 // CHECK-NEXT: store float [[ELT]], ptr [[TMP]] // CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[TMP]] +// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP]]) // CHECK-NEXT: ret i32 [[RES]] unsigned ext_vector_element(v4f v) { return __builtin_bit_cast(unsigned, v.z); @@ -20,10 +22,12 @@ unsigned ext_vector_element(v4f v) { // CHECK-LABEL: define{{.*}} i32 @_Z20ext_vector_subscriptDv4_f( // CHECK: [[TMP:%.*]] = alloca float -// CHECK: [[VEC:%.*]] = load <4 x float>, ptr %v.addr +// CHECK: call void @llvm.lifetime.start.p0(ptr [[TMP]]) +// CHECK-NEXT: [[VEC:%.*]] = load <4 x float>, ptr %v.addr // CHECK-NEXT: [[ELT:%.*]] = extractelement <4 x float> [[VEC]], i32 1 // CHECK-NEXT: store float [[ELT]], ptr [[TMP]] // CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[TMP]] +// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP]]) // CHECK-NEXT: ret i32 [[RES]] unsigned ext_vector_subscript(v4f v) { return __builtin_bit_cast(unsigned, v[1]); @@ -31,10 +35,12 @@ unsigned ext_vector_subscript(v4f v) { // CHECK-LABEL: define{{.*}} i64 @_Z18ext_vector_swizzleDv4_f( // CHECK: [[TMP:%.*]] = alloca <2 x float> -// CHECK: [[VEC:%.*]] = load <4 x float>, ptr %v.addr +// CHECK: call void @llvm.lifetime.start.p0(ptr [[TMP]]) +// CHECK-NEXT: [[VEC:%.*]] = load <4 x float>, ptr %v.addr // CHECK-NEXT: [[ELTS:%.*]] = shufflevector <4 x float> [[VEC]], <4 x float> poison, <2 x i32> <i32 2, i32 3> // CHECK-NEXT: store <2 x float> [[ELTS]], ptr [[TMP]] // CHECK-NEXT: [[RES:%.*]] = load i64, ptr [[TMP]] +// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP]]) // CHECK-NEXT: ret i64 [[RES]] unsigned long long ext_vector_swizzle(v4f v) { return __builtin_bit_cast(unsigned long long, v.zw); @@ -42,11 +48,13 @@ unsigned long long ext_vector_swizzle(v4f v) { // CHECK-LABEL: define{{.*}} i32 @_Z20gnu_vector_subscriptDv4_fi( // CHECK: [[TMP:%.*]] = alloca float -// CHECK: [[VEC:%.*]] = load <4 x float>, ptr %v.addr +// CHECK: call void @llvm.lifetime.start.p0(ptr [[TMP]]) +// CHECK-NEXT: [[VEC:%.*]] = load <4 x float>, ptr %v.addr // CHECK-NEXT: [[IDX:%.*]] = load i32, ptr %i.addr // CHECK-NEXT: [[ELT:%.*]] = extractelement <4 x float> [[VEC]], i32 [[IDX]] // CHECK-NEXT: store float [[ELT]], ptr [[TMP]] // CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[TMP]] +// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP]]) // CHECK-NEXT: ret i32 [[RES]] unsigned gnu_vector_subscript(gv4f v, int i) { return __builtin_bit_cast(unsigned, v[i]); @@ -54,10 +62,12 @@ unsigned gnu_vector_subscript(gv4f v, int i) { // CHECK-LABEL: define{{.*}} i32 @_Z14matrix_elementu11matrix_typeILm2ELm2EfE( // CHECK: [[TMP:%.*]] = alloca float -// CHECK: [[MAT:%.*]] = load <4 x float>, ptr %m.addr +// CHECK: call void @llvm.lifetime.start.p0(ptr [[TMP]]) +// CHECK-NEXT: [[MAT:%.*]] = load <4 x float>, ptr %m.addr // CHECK-NEXT: [[ELT:%.*]] = extractelement <4 x float> [[MAT]], i64 3 // CHECK-NEXT: store float [[ELT]], ptr [[TMP]] // CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[TMP]] +// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP]]) // CHECK-NEXT: ret i32 [[RES]] unsigned matrix_element(m2x2f m) { return __builtin_bit_cast(unsigned, m[1][1]); @@ -69,24 +79,24 @@ struct S { // CHECK-LABEL: define{{.*}} i32 @_Z9bit_field1S( // CHECK: [[TMP:%.*]] = alloca i32 -// CHECK: [[LOAD:%.*]] = load i16, ptr %s +// CHECK: call void @llvm.lifetime.start.p0(ptr [[TMP]]) +// CHECK-NEXT: [[LOAD:%.*]] = load i16, ptr %s // CHECK-NEXT: [[SHR:%.*]] = lshr i16 [[LOAD]], 4 // CHECK-NEXT: [[EXT:%.*]] = zext i16 [[SHR]] to i32 // CHECK-NEXT: store i32 [[EXT]], ptr [[TMP]] // CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[TMP]] +// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP]]) // CHECK-NEXT: ret i32 [[RES]] unsigned bit_field(S s) { return __builtin_bit_cast(unsigned, s.b); } -register unsigned long stack_pointer asm("rsp"); +volatile float vf; -// CHECK-LABEL: define{{.*}} i64 @_Z15global_registerv( -// CHECK: [[TMP:%.*]] = alloca i64 -// CHECK: [[REG:%.*]] = call i64 @llvm.read_register.i64( -// CHECK-NEXT: store i64 [[REG]], ptr [[TMP]] -// CHECK-NEXT: [[RES:%.*]] = load i64, ptr [[TMP]] -// CHECK-NEXT: ret i64 [[RES]] -long global_register() { - return __builtin_bit_cast(long, stack_pointer); +// CHECK-LABEL: define{{.*}} i32 @_Z15volatile_lvaluev( +// CHECK-NOT: alloca +// CHECK: [[RES:%.*]] = load volatile i32, ptr @vf +// CHECK-NEXT: ret i32 [[RES]] +unsigned volatile_lvalue() { + return __builtin_bit_cast(unsigned, vf); } diff --git a/clang/test/SemaCXX/builtin-bit-cast.cpp b/clang/test/SemaCXX/builtin-bit-cast.cpp index 626065d5bdc000..57ae691263db63 100644 --- a/clang/test/SemaCXX/builtin-bit-cast.cpp +++ b/clang/test/SemaCXX/builtin-bit-cast.cpp @@ -56,3 +56,14 @@ constexpr To bit_cast(const From &from) { constexpr __int128_t foo = bit_cast<__int128_t>((long double)0); // expected-error@-1{{constexpr variable 'foo' must be initialized by a constant expression}} // expected-note@-2{{in call to 'bit_cast<__int128, long double>((long double)0)'}} + +volatile int volatile_int; +int from_volatile = __builtin_bit_cast(int, volatile_int); + +struct volatile_bit_field { + volatile unsigned b : 8; // expected-note {{bit-field is declared here}} +}; +unsigned from_volatile_bit_field(volatile_bit_field &v) { + // expected-error@+1 {{non-const reference cannot bind to bit-field 'b'}} + return __builtin_bit_cast(unsigned, v.b); +} diff --git a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp index db53076891b47b..c2cefb7ef45bb4 100644 --- a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp +++ b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp @@ -332,7 +332,7 @@ struct vol_mem { }; // expected-error@+2 {{constexpr variable 'run_vol_mem' must be initialized by a constant expression}} -// expected-note@+1 {{non-literal type 'vol_mem' cannot be used in a constant expression}} +// expected-note-re@+1 {{non-literal type '{{(const )?}}vol_mem' cannot be used in a constant expression}} constexpr int run_vol_mem = __builtin_bit_cast(int, vol_mem{43}); struct mem_ptr { >From 1fd0dae1e8f9f7014032e08d492486cf37bf4e2a Mon Sep 17 00:00:00 2001 From: Nemi26 <[email protected]> Date: Mon, 19 Oct 2026 00:50:02 +0200 Subject: [PATCH 3/3] [clang] Name the unqualified type in the bytecode non-literal note; test C The bytecode interpreter now names the unqualified type in note_constexpr_nonliteral, as the current interpreter does, so the const that binding the bit_cast operand adds does not show. cwg722 checks the NoOp cast to 'void *const' that the binding adds from C++17 on, and a new CodeGen test covers __builtin_bit_cast in C. Assisted-by: Claude Code (Claude Opus) --- clang/lib/AST/ByteCode/Interp.cpp | 3 +- clang/test/AST/ByteCode/builtin-bit-cast.cpp | 2 +- clang/test/CXX/drs/cwg722.cpp | 11 ++-- .../builtin-bit-cast-nonsimple-lvalue.c | 57 +++++++++++++++++++ .../SemaCXX/constexpr-builtin-bit-cast.cpp | 2 +- 5 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 clang/test/CodeGen/builtin-bit-cast-nonsimple-lvalue.c diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index b217c14988f9cf..fc07c528863944 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -1654,7 +1654,8 @@ bool CheckLiteralType(InterpState &S, CodePtr OpPC, const Type *T) { const Expr *E = S.Current->getExpr(OpPC); if (S.getLangOpts().CPlusPlus11) - S.FFDiag(E, diag::note_constexpr_nonliteral) << E->getType(); + S.FFDiag(E, diag::note_constexpr_nonliteral) + << E->getType().getUnqualifiedType(); else S.FFDiag(E, diag::note_invalid_subexpr_in_const_expr); return false; diff --git a/clang/test/AST/ByteCode/builtin-bit-cast.cpp b/clang/test/AST/ByteCode/builtin-bit-cast.cpp index b548b5290d25f3..368728d00a8121 100644 --- a/clang/test/AST/ByteCode/builtin-bit-cast.cpp +++ b/clang/test/AST/ByteCode/builtin-bit-cast.cpp @@ -428,7 +428,7 @@ struct vol_mem { }; // both-error@+2 {{constexpr variable 'run_vol_mem' must be initialized by a constant expression}} -// both-note-re@+1 {{non-literal type '{{(const )?}}vol_mem' cannot be used in a constant expression}} +// both-note@+1 {{non-literal type 'vol_mem' cannot be used in a constant expression}} constexpr int run_vol_mem = __builtin_bit_cast(int, vol_mem{43}); struct mem_ptr { diff --git a/clang/test/CXX/drs/cwg722.cpp b/clang/test/CXX/drs/cwg722.cpp index ad323a1f7b8e84..c413fe2a28ef0b 100644 --- a/clang/test/CXX/drs/cwg722.cpp +++ b/clang/test/CXX/drs/cwg722.cpp @@ -1,10 +1,10 @@ // RUN: %clang_cc1 -std=c++98 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors // RUN: %clang_cc1 -std=c++11 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors -ast-dump | FileCheck %s // RUN: %clang_cc1 -std=c++14 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors -ast-dump | FileCheck %s -// RUN: %clang_cc1 -std=c++17 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors -ast-dump | FileCheck %s -// RUN: %clang_cc1 -std=c++20 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors -ast-dump | FileCheck %s -// RUN: %clang_cc1 -std=c++23 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors -ast-dump | FileCheck %s -// RUN: %clang_cc1 -std=c++26 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors -ast-dump | FileCheck %s +// RUN: %clang_cc1 -std=c++17 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors -ast-dump | FileCheck %s --check-prefixes CHECK,SINCE-CXX17 +// RUN: %clang_cc1 -std=c++20 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors -ast-dump | FileCheck %s --check-prefixes CHECK,SINCE-CXX17 +// RUN: %clang_cc1 -std=c++23 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors -ast-dump | FileCheck %s --check-prefixes CHECK,SINCE-CXX17 +// RUN: %clang_cc1 -std=c++26 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors -ast-dump | FileCheck %s --check-prefixes CHECK,SINCE-CXX17 // expected-no-diagnostics // cwg722: 20 @@ -48,7 +48,8 @@ void h() { // CHECK-NEXT: `-ImplicitCastExpr {{.+}} 'void *' <NullToPointer> // CHECK-NEXT: `-BuiltinBitCastExpr {{.+}} 'std::nullptr_t' <LValueToRValueBitCast> // CHECK-NEXT: `-MaterializeTemporaryExpr {{.+}} 'void *const' lvalue -// CHECK: `-CXXStaticCastExpr {{.+}} 'void *' static_cast<void *> <NoOp> +// SINCE-CXX17-NEXT: `-ImplicitCastExpr {{.+}} 'void *const' <NoOp> +// CHECK-NEXT: `-CXXStaticCastExpr {{.+}} 'void *' static_cast<void *> <NoOp> // CHECK-NEXT: `-ImplicitCastExpr {{.+}} 'void *' <BitCast> part_of_explicit_cast // CHECK-NEXT: `-UnaryOperator {{.+}} 'int *' prefix '&' cannot overflow // CHECK-NEXT: `-DeclRefExpr {{.+}} 'int' lvalue Var {{.+}} 'i' 'int' diff --git a/clang/test/CodeGen/builtin-bit-cast-nonsimple-lvalue.c b/clang/test/CodeGen/builtin-bit-cast-nonsimple-lvalue.c new file mode 100644 index 00000000000000..fdd365a1bc6ad7 --- /dev/null +++ b/clang/test/CodeGen/builtin-bit-cast-nonsimple-lvalue.c @@ -0,0 +1,57 @@ +// RUN: %clang_cc1 -O -emit-llvm -disable-llvm-passes -triple x86_64-unknown-linux-gnu %s -o - | FileCheck %s + +typedef float v4f __attribute__((ext_vector_type(4))); + +// CHECK-LABEL: define{{.*}} i32 @ext_vector_element( +// CHECK: [[TMP:%ref.tmp.*]] = alloca float +// CHECK: call void @llvm.lifetime.start.p0(ptr [[TMP]]) +// CHECK-NEXT: [[VEC:%.*]] = load <4 x float>, ptr %v.addr +// CHECK-NEXT: [[ELT:%.*]] = extractelement <4 x float> [[VEC]], i64 2 +// CHECK-NEXT: store float [[ELT]], ptr [[TMP]] +// CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[TMP]] +// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP]]) +// CHECK-NEXT: ret i32 [[RES]] +unsigned ext_vector_element(v4f v) { + return __builtin_bit_cast(unsigned, v.z); +} + +struct S { + unsigned a : 4, b : 12; +}; + +// CHECK-LABEL: define{{.*}} i32 @bit_field( +// CHECK: [[TMP:%ref.tmp.*]] = alloca i32 +// CHECK: call void @llvm.lifetime.start.p0(ptr [[TMP]]) +// CHECK-NEXT: [[LOAD:%.*]] = load i16, ptr %s +// CHECK-NEXT: [[SHR:%.*]] = lshr i16 [[LOAD]], 4 +// CHECK-NEXT: [[EXT:%.*]] = zext i16 [[SHR]] to i32 +// CHECK-NEXT: store i32 [[EXT]], ptr [[TMP]] +// CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[TMP]] +// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP]]) +// CHECK-NEXT: ret i32 [[RES]] +unsigned bit_field(struct S s) { + return __builtin_bit_cast(unsigned, s.b); +} + +// CHECK-LABEL: define{{.*}} i32 @prvalue( +// CHECK: [[TMP:%ref.tmp.*]] = alloca float +// CHECK: call void @llvm.lifetime.start.p0(ptr [[TMP]]) +// CHECK-NEXT: [[F:%.*]] = load float, ptr %f.addr +// CHECK-NEXT: [[ADD:%.*]] = fadd float [[F]], 1.000000e+00 +// CHECK-NEXT: store float [[ADD]], ptr [[TMP]] +// CHECK-NEXT: [[RES:%.*]] = load i32, ptr [[TMP]] +// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP]]) +// CHECK-NEXT: ret i32 [[RES]] +unsigned prvalue(float f) { + return __builtin_bit_cast(unsigned, f + 1.0f); +} + +volatile float vf; + +// CHECK-LABEL: define{{.*}} i32 @volatile_lvalue( +// CHECK-NOT: alloca +// CHECK: [[RES:%.*]] = load volatile i32, ptr @vf +// CHECK-NEXT: ret i32 [[RES]] +unsigned volatile_lvalue(void) { + return __builtin_bit_cast(unsigned, vf); +} diff --git a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp index c2cefb7ef45bb4..db53076891b47b 100644 --- a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp +++ b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp @@ -332,7 +332,7 @@ struct vol_mem { }; // expected-error@+2 {{constexpr variable 'run_vol_mem' must be initialized by a constant expression}} -// expected-note-re@+1 {{non-literal type '{{(const )?}}vol_mem' cannot be used in a constant expression}} +// expected-note@+1 {{non-literal type 'vol_mem' cannot be used in a constant expression}} constexpr int run_vol_mem = __builtin_bit_cast(int, vol_mem{43}); struct mem_ptr { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
