https://github.com/Nemi26 created https://github.com/llvm/llvm-project/pull/231830
`__builtin_bit_cast` reads its operand through the operand's address (`CK_LValueToRValueBitCast`). Sema materialized prvalue operands, but passed glvalues through unchanged, including ones that have no address of their own: vector elements, ext_vector swizzles, matrix elements, bit-fields and global register variables. CodeGen then took the address of the containing vector, matrix or bit-field storage unit and read from its start, so for example `__builtin_bit_cast(unsigned, v.z)` returned the bits of lane 0, while constant evaluation of the same expression gave the right answer. This patch applies the lvalue-to-rvalue conversion to such operands in `CheckBuiltinBitCast` and materializes the result, as is already done for prvalues (following @efriedma-quic's suggestion in #95673). CodeGen now asserts that the operand lvalue is simple. The constant evaluator's diagnostic for an uninitialized vector element read through a bit_cast changes from "subexpression not valid" to "read of uninitialized object", which updates `constexpr-vectors-access-elements.cpp`. Fixes #95673 Fixes #219531 Fixes #226388 This patch was written with the assistance of an AI tool (Claude Code, Claude Opus); I reviewed it. A note on #219531: there is an earlier PR for it, #219646, but it seems to have stalled, as its author hasn't responded to the review comments for a while. I'm also aware that #219531 is labelled good first issue, and that the AI policy asks that such issues not be fixed with AI tools. If you'd like to keep that rule strictly for this issue, please feel free to close this PR. As the person who filed these issues, though, I'd be grateful if it could still be considered, since it also covers the other cases from #95673 and #226388. >From b59584b7907f8054fedf785e7cda699a63d18f0e Mon Sep 17 00:00:00 2001 From: Nemi26 <[email protected]> Date: Wed, 14 Oct 2026 11:28:07 +0200 Subject: [PATCH] [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 | 12 +++ .../builtin-bit-cast-nonsimple-lvalue.cpp | 92 +++++++++++++++++++ .../SemaCXX/constexpr-builtin-bit-cast.cpp | 14 +++ .../constexpr-vectors-access-elements.cpp | 13 ++- 6 files changed, 132 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 0103c29537920..e3594e4993e8e 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 db2b7a4ad1fce..5dccebcf842a8 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 c797bdb11e0cb..732b994516ca7 100644 --- a/clang/lib/Sema/SemaCast.cpp +++ b/clang/lib/Sema/SemaCast.cpp @@ -3404,6 +3404,18 @@ void CastOperation::CheckBuiltinBitCast() { return; } + // The operand is read through its address. A bit-field, vector element, + // matrix element or global register variable has none of its own, so load + // it and materialize the value instead. + 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 0000000000000..8818c714d73b8 --- /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 ea5333df0b469..db53076891b47 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 481b80ceb9c1e..c6eb66f6b2995 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, ""); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
