https://github.com/chaitanyav updated https://github.com/llvm/llvm-project/pull/202402
>From bfb45397062138511f9eff3af171361eea4a7f27 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Mon, 8 Jun 2026 10:00:52 -0700 Subject: [PATCH 1/7] [clang] Add constexpr support for stdc_memreverse8 Implements constant expression evaluation in both the AST interpreter (ExprConstant.cpp) and the bytecode interpreter (InterpBuiltin.cpp). Adds constexpr tests for the typed variants stdc_memreverse8u{8,16,32,64} Follow up:#197358. --- clang/include/clang/Basic/Builtins.td | 2 +- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 74 +++++ clang/lib/AST/ExprConstant.cpp | 59 ++++ ...constexpr-builtin-stdc-memreverse8-typed.c | 37 +++ .../constexpr-builtin-stdc-memreverse8.cpp | 273 ++++++++++++++++++ 5 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 clang/test/Sema/constexpr-builtin-stdc-memreverse8-typed.c create mode 100644 clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 63cdb787bea16..d65bb1047c1ad 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -992,7 +992,7 @@ def StdcRotateRightTyped : LibBuiltin<"stdbit.h", "C2Y_LANG">, IntBitUtilTemplat def StdcMemReverse8: LibBuiltin<"stdbit.h", "C2Y_LANG"> { let Spellings = ["stdc_memreverse8"]; - let Attributes = [NoThrow, NonNull<NonOptimizing, [1]>]; + let Attributes = [NoThrow, Constexpr, NonNull<NonOptimizing, [1]>]; let Prototype = "void(size_t, unsigned char*)"; let AddBuiltinPrefixedAlias = 1; } diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index b76f13833da14..951dab5efb4e3 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2058,6 +2058,76 @@ static bool isOneByteCharacterType(QualType T) { return T->isCharType() || T->isChar8Type(); } +// stdc_memreverse8(size_t N, unsigned char *P) +static bool interp__builtin_stdc_memreverse8(InterpState &S, CodePtr OpPC, + const InterpFrame *Frame, + const CallExpr *Call) { + Pointer Ptr = S.Stk.pop<Pointer>().expand(); + + uint64_t NElems; + if (!popToUInt64(S, Call->getArg(0), NElems)) + return false; + + if (Ptr.isZero()) { + S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_null) + << AK_Assign; + return false; + } + + if (Ptr.isDummy() || !Ptr.isBlockPointer() || !Ptr.isLive()) + return false; + + QualType ElemTy = Ptr.getFieldDesc()->isArray() + ? Ptr.getFieldDesc()->getElemQualType() + : Ptr.getFieldDesc()->getType(); + if (ElemTy->isIncompleteType()) { + S.FFDiag(S.Current->getSource(OpPC), + diag::note_constexpr_ltor_incomplete_type) + << ElemTy; + return false; + } + if (!isOneByteCharacterType(ElemTy)) { + S.FFDiag(S.Current->getSource(OpPC), + diag::note_constexpr_memchr_unsupported) + << S.getASTContext().BuiltinInfo.getQuotedName(Call->getBuiltinCallee()) + << ElemTy; + return false; + } + + size_t BaseIdx = Ptr.getIndex(); + size_t ArraySize = Ptr.getNumElems(); + size_t RemainingElems = ArraySize - BaseIdx; + if (NElems > RemainingElems) { + S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index) + << (int64_t)(BaseIdx + NElems - 1) << /*isArray=*/0 + << (uint64_t)ArraySize; + return false; + } + + if (NElems <= 1) + return true; + + PrimType ElemT = *S.getContext().classify(ElemTy); + + for (uint64_t I = 0, Half = NElems / 2; I < Half; ++I) { + Pointer LoPtr = Ptr.atIndex(BaseIdx + I); + Pointer HiPtr = Ptr.atIndex(BaseIdx + NElems - 1 - I); + + if (!CheckLoad(S, OpPC, LoPtr, AK_Read) || + !CheckLoad(S, OpPC, HiPtr, AK_Read)) + return false; + + INT_TYPE_SWITCH_NO_BOOL(ElemT, { + T LoVal = LoPtr.deref<T>(); + LoPtr.deref<T>() = HiPtr.deref<T>(); + HiPtr.deref<T>() = LoVal; + LoPtr.initialize(); + HiPtr.initialize(); + }); + } + return true; +} + static bool interp__builtin_memcmp(InterpState &S, CodePtr OpPC, const InterpFrame *Frame, const CallExpr *Call, unsigned ID) { @@ -5085,6 +5155,10 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call, case Builtin::BIstdc_memreverse8u64: return interp__builtin_bswap(S, OpPC, Frame, Call); + case Builtin::BIstdc_memreverse8: + case Builtin::BI__builtin_stdc_memreverse8: + return interp__builtin_stdc_memreverse8(S, OpPC, Frame, Call); + case Builtin::BI__atomic_always_lock_free: case Builtin::BI__atomic_is_lock_free: return interp__builtin_atomic_lock_free(S, OpPC, Frame, Call, BuiltinID); diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 1d359339b9104..314429b949c8b 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -21102,6 +21102,65 @@ class VoidExprEvaluator case Builtin::BI__builtin_operator_delete: return HandleOperatorDeleteCall(Info, E); + case Builtin::BIstdc_memreverse8: + case Builtin::BI__builtin_stdc_memreverse8: { + APSInt N; + if (!EvaluateInteger(E->getArg(0), N, Info)) + return false; + uint64_t NElems = N.getZExtValue(); + + LValue Ptr; + if (!EvaluatePointer(E->getArg(1), Ptr, Info)) + return false; + + if (!Ptr.checkNullPointerForFoldAccess(Info, E, AK_Assign) || + Ptr.Designator.Invalid) + return false; + + QualType CharTy = Ptr.Designator.getType(Info.Ctx); + if (CharTy->isIncompleteType()) { + Info.FFDiag(E, diag::note_constexpr_ltor_incomplete_type) << CharTy; + return false; + } + if (!isOneByteCharacterType(CharTy)) { + Info.FFDiag(E, diag::note_constexpr_memchr_unsupported) + << Info.Ctx.BuiltinInfo.getQuotedName(E->getBuiltinCallee()) + << CharTy; + return false; + } + + uint64_t RemainingElems = Ptr.Designator.validIndexAdjustments().second; + if (NElems > RemainingElems) { + uint64_t ArrayIndex = + Ptr.Designator.MostDerivedIsArrayElement + ? Ptr.Designator.Entries.back().getAsArrayIndex() + : (uint64_t)Ptr.Designator.IsOnePastTheEnd; + APSInt Index = APSInt::get(ArrayIndex + NElems - 1); + Ptr.Designator.diagnosePointerArithmetic(Info, E, Index); + return false; + } + + if (NElems <= 1) + return true; + + LValue Lo = Ptr; + LValue Hi = Ptr; + if (!HandleLValueArrayAdjustment(Info, E, Hi, CharTy, NElems - 1)) + return false; + + for (uint64_t I = 0, Half = NElems / 2; I < Half; ++I) { + APValue LoVal, HiVal; + if (!handleLValueToRValueConversion(Info, E, CharTy, Lo, LoVal) || + !handleLValueToRValueConversion(Info, E, CharTy, Hi, HiVal) || + !handleAssignment(Info, E, Lo, CharTy, HiVal) || + !handleAssignment(Info, E, Hi, CharTy, LoVal) || + !HandleLValueArrayAdjustment(Info, E, Lo, CharTy, 1) || + !HandleLValueArrayAdjustment(Info, E, Hi, CharTy, -1)) + return false; + } + return true; + } + default: return false; } diff --git a/clang/test/Sema/constexpr-builtin-stdc-memreverse8-typed.c b/clang/test/Sema/constexpr-builtin-stdc-memreverse8-typed.c new file mode 100644 index 0000000000000..d6779f74b9620 --- /dev/null +++ b/clang/test/Sema/constexpr-builtin-stdc-memreverse8-typed.c @@ -0,0 +1,37 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c2y -isystem %S/Inputs -fsyntax-only %s +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c2y -isystem %S/Inputs -fsyntax-only %s -fexperimental-new-constant-interpreter + +#include <stdbit.h> + +constexpr __UINT8_TYPE__ u8_0xAB = stdc_memreverse8u8((__UINT8_TYPE__)0xAB); +_Static_assert(u8_0xAB == (__UINT8_TYPE__)0xAB, ""); +constexpr __UINT8_TYPE__ u8_0x00 = stdc_memreverse8u8((__UINT8_TYPE__)0x00); +_Static_assert(u8_0x00 == (__UINT8_TYPE__)0x00, ""); +constexpr __UINT8_TYPE__ u8_0xFF = stdc_memreverse8u8((__UINT8_TYPE__)0xFF); +_Static_assert(u8_0xFF == (__UINT8_TYPE__)0xFF, ""); + +constexpr __UINT16_TYPE__ u16_0x1234 = stdc_memreverse8u16((__UINT16_TYPE__)0x1234); +_Static_assert(u16_0x1234 == (__UINT16_TYPE__)0x3412, ""); +constexpr __UINT16_TYPE__ u16_0x0000 = stdc_memreverse8u16((__UINT16_TYPE__)0x0000); +_Static_assert(u16_0x0000 == (__UINT16_TYPE__)0x0000, ""); +constexpr __UINT16_TYPE__ u16_0xAAAA = stdc_memreverse8u16((__UINT16_TYPE__)0xAAAA); +_Static_assert(u16_0xAAAA == (__UINT16_TYPE__)0xAAAA, ""); + +constexpr __UINT32_TYPE__ u32_0x12345678 = stdc_memreverse8u32((__UINT32_TYPE__)0x12345678); +_Static_assert(u32_0x12345678 == (__UINT32_TYPE__)0x78563412, ""); +constexpr __UINT32_TYPE__ u32_0x00000000 = stdc_memreverse8u32((__UINT32_TYPE__)0x00000000); +_Static_assert(u32_0x00000000 == (__UINT32_TYPE__)0x00000000, ""); +constexpr __UINT32_TYPE__ u32_0xDEADBEEF = stdc_memreverse8u32((__UINT32_TYPE__)0xDEADBEEF); +_Static_assert(u32_0xDEADBEEF == (__UINT32_TYPE__)0xEFBEADDE, ""); + +constexpr __UINT64_TYPE__ u64_0x0102030405060708 = stdc_memreverse8u64((__UINT64_TYPE__)0x0102030405060708ULL); +_Static_assert(u64_0x0102030405060708 == (__UINT64_TYPE__)0x0807060504030201ULL, ""); +constexpr __UINT64_TYPE__ u64_0x0000000000000000 = stdc_memreverse8u64((__UINT64_TYPE__)0x0000000000000000ULL); +_Static_assert(u64_0x0000000000000000 == (__UINT64_TYPE__)0x0000000000000000ULL, ""); + +constexpr __UINT16_TYPE__ u16_rt = stdc_memreverse8u16(stdc_memreverse8u16((__UINT16_TYPE__)0xABCD)); +_Static_assert(u16_rt == (__UINT16_TYPE__)0xABCD, ""); +constexpr __UINT32_TYPE__ u32_rt = stdc_memreverse8u32(stdc_memreverse8u32((__UINT32_TYPE__)0xDEADBEEF)); +_Static_assert(u32_rt == (__UINT32_TYPE__)0xDEADBEEF, ""); +constexpr __UINT64_TYPE__ u64_rt = stdc_memreverse8u64(stdc_memreverse8u64((__UINT64_TYPE__)0xCAFEBABEDEADBEEFULL)); +_Static_assert(u64_rt == (__UINT64_TYPE__)0xCAFEBABEDEADBEEFULL, ""); diff --git a/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp b/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp new file mode 100644 index 0000000000000..8064b74fc2b85 --- /dev/null +++ b/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp @@ -0,0 +1,273 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -fsyntax-only -verify=ref,both -std=c++20 %s +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -fsyntax-only -verify=expected,both -std=c++20 %s -fexperimental-new-constant-interpreter + +namespace test_noop { + +// N=0: no bytes touched. +constexpr unsigned char test_n0() { + unsigned char buf[4] = {0x12, 0x34, 0x56, 0x78}; + __builtin_stdc_memreverse8(0, buf); + return buf[0]; +} +static_assert(test_n0() == 0x12, ""); + +// N=1: single byte is its own reverse. +constexpr unsigned char test_n1() { + unsigned char buf[4] = {0x12, 0x34, 0x56, 0x78}; + __builtin_stdc_memreverse8(1, buf); + return buf[0]; +} +static_assert(test_n1() == 0x12, ""); + +} // namespace test_noop + +namespace test_basic { + +// N=2: swap two bytes. +constexpr bool test_n2() { + unsigned char buf[2] = {0x12, 0x34}; + __builtin_stdc_memreverse8(2, buf); + return buf[0] == 0x34 && buf[1] == 0x12; +} +static_assert(test_n2(), ""); + +// N=4: full 4-byte reversal. +constexpr bool test_n4() { + unsigned char buf[4] = {0x12, 0x34, 0x56, 0x78}; + __builtin_stdc_memreverse8(4, buf); + return buf[0] == 0x78 && buf[1] == 0x56 && + buf[2] == 0x34 && buf[3] == 0x12; +} +static_assert(test_n4(), ""); + +// N=8: 8-byte reversal. +constexpr bool test_n8() { + unsigned char buf[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + __builtin_stdc_memreverse8(8, buf); + return buf[0] == 0x08 && buf[1] == 0x07 && buf[2] == 0x06 && + buf[3] == 0x05 && buf[4] == 0x04 && buf[5] == 0x03 && + buf[6] == 0x02 && buf[7] == 0x01; +} +static_assert(test_n8(), ""); + +} // namespace test_basic + +namespace test_odd_length { + +// N=3: odd-length reversal, middle byte unchanged. +constexpr bool test_n3() { + unsigned char buf[3] = {0xAA, 0xBB, 0xCC}; + __builtin_stdc_memreverse8(3, buf); + return buf[0] == 0xCC && buf[1] == 0xBB && buf[2] == 0xAA; +} +static_assert(test_n3(), ""); + +// N=5: odd-length, middle byte stays. +constexpr bool test_n5() { + unsigned char buf[5] = {0x01, 0x02, 0x03, 0x04, 0x05}; + __builtin_stdc_memreverse8(5, buf); + return buf[0] == 0x05 && buf[1] == 0x04 && buf[2] == 0x03 && + buf[3] == 0x02 && buf[4] == 0x01; +} +static_assert(test_n5(), ""); + +} // namespace test_odd_length + +namespace test_idempotent { + +// All same bytes: reversal is a no-op. +constexpr bool test_all_same() { + unsigned char buf[4] = {0xAB, 0xAB, 0xAB, 0xAB}; + __builtin_stdc_memreverse8(4, buf); + return buf[0] == 0xAB && buf[1] == 0xAB && + buf[2] == 0xAB && buf[3] == 0xAB; +} +static_assert(test_all_same(), ""); + +// Palindrome: reversal produces same sequence. +constexpr bool test_palindrome() { + unsigned char buf[4] = {0x12, 0x34, 0x34, 0x12}; + __builtin_stdc_memreverse8(4, buf); + return buf[0] == 0x12 && buf[1] == 0x34 && + buf[2] == 0x34 && buf[3] == 0x12; +} +static_assert(test_palindrome(), ""); + +} // namespace test_idempotent + +namespace test_double_reverse { + +// Reversing twice restores the original. +constexpr bool test_round_trip_4() { + unsigned char buf[4] = {0x12, 0x34, 0x56, 0x78}; + __builtin_stdc_memreverse8(4, buf); + __builtin_stdc_memreverse8(4, buf); + return buf[0] == 0x12 && buf[1] == 0x34 && + buf[2] == 0x56 && buf[3] == 0x78; +} +static_assert(test_round_trip_4(), ""); + +constexpr bool test_round_trip_3() { + unsigned char buf[3] = {0xDE, 0xAD, 0xBE}; + __builtin_stdc_memreverse8(3, buf); + __builtin_stdc_memreverse8(3, buf); + return buf[0] == 0xDE && buf[1] == 0xAD && buf[2] == 0xBE; +} +static_assert(test_round_trip_3(), ""); + +} // namespace test_double_reverse + +namespace test_partial { + +// Reverse only part of a larger buffer; rest is untouched. +constexpr bool test_partial_reverse() { + unsigned char buf[6] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; + __builtin_stdc_memreverse8(4, buf); + return buf[0] == 0x04 && buf[1] == 0x03 && buf[2] == 0x02 && + buf[3] == 0x01 && buf[4] == 0x05 && buf[5] == 0x06; +} +static_assert(test_partial_reverse(), ""); + +// ptr points into the middle of a larger buffer; bytes outside the range are untouched. +constexpr bool test_mid_array() { + unsigned char buf[6] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; + __builtin_stdc_memreverse8(4, buf + 1); + return buf[0] == 0x01 && buf[1] == 0x05 && buf[2] == 0x04 && + buf[3] == 0x03 && buf[4] == 0x02 && buf[5] == 0x06; +} +static_assert(test_mid_array(), ""); + +// N=1 with a pointer-to-scalar (not an array): single byte is a no-op. +constexpr bool test_scalar_n1() { + unsigned char c = 0x42; + __builtin_stdc_memreverse8(1, &c); + return c == 0x42; +} +static_assert(test_scalar_n1(), ""); + +// N=0 with a pointer-to-scalar: no-op. +constexpr bool test_scalar_n0() { + unsigned char c = 0x42; + __builtin_stdc_memreverse8(0, &c); + return c == 0x42; +} +static_assert(test_scalar_n0(), ""); + +} // namespace test_partial + +namespace test_side_effects { + +// n++ passes n=4 to the reversal, then increments n to 5. +constexpr bool test_n_postincrement() { + unsigned char buf[4] = {0x01, 0x02, 0x03, 0x04}; + __SIZE_TYPE__ n = 4; + __builtin_stdc_memreverse8(n++, buf); + return n == 5 && + buf[0] == 0x04 && buf[1] == 0x03 && buf[2] == 0x02 && buf[3] == 0x01; +} +static_assert(test_n_postincrement(), ""); + +// ++n increments n to 4 first, then passes n=4 to the reversal. +constexpr bool test_n_preincrement() { + unsigned char buf[4] = {0x01, 0x02, 0x03, 0x04}; + __SIZE_TYPE__ n = 3; + __builtin_stdc_memreverse8(++n, buf); + return n == 4 && + buf[0] == 0x04 && buf[1] == 0x03 && buf[2] == 0x02 && buf[3] == 0x01; +} +static_assert(test_n_preincrement(), ""); + +// p++ passes buf to the reversal, then advances p. +constexpr bool test_p_postincrement() { + unsigned char buf[4] = {0x01, 0x02, 0x03, 0x04}; + unsigned char *p = buf; + __builtin_stdc_memreverse8(4, p++); + return buf[0] == 0x04 && buf[1] == 0x03 && buf[2] == 0x02 && buf[3] == 0x01 && + p == buf + 1; +} +static_assert(test_p_postincrement(), ""); + +// Both arguments have side effects; each is evaluated once before the call. +constexpr bool test_both_postincrement() { + unsigned char buf[5] = {0x01, 0x02, 0x03, 0x04, 0x05}; + unsigned char *p = buf; + __SIZE_TYPE__ n = 4; + __builtin_stdc_memreverse8(n++, p++); + return n == 5 && p == buf + 1 && + buf[0] == 0x04 && buf[1] == 0x03 && buf[2] == 0x02 && buf[3] == 0x01 && + buf[4] == 0x05; +} +static_assert(test_both_postincrement(), ""); + +} // namespace test_side_effects + +namespace test_side_effects_zero_or_one { + +// Side effects in the second argument must be evaluated even if N == 0. +constexpr bool test_n0_side_effect() { + unsigned char buf[4] = {0x12, 0x34, 0x56, 0x78}; + unsigned char *p = buf; + __builtin_stdc_memreverse8(0, p++); + return p == buf + 1; +} +static_assert(test_n0_side_effect(), ""); + +// Side effects in the second argument must be evaluated even if N == 1. +constexpr bool test_n1_side_effect() { + unsigned char buf[4] = {0x12, 0x34, 0x56, 0x78}; + unsigned char *p = buf; + __builtin_stdc_memreverse8(1, p++); + return p == buf + 1; +} +static_assert(test_n1_side_effect(), ""); + +} // namespace test_side_effects_zero_or_one + +namespace test_negative { + +// N exceeds the array size: out-of-bounds access. +constexpr bool test_oob() { // both-error{{constexpr function never produces a constant expression}} + unsigned char buf[2] = {0x01, 0x02}; + __builtin_stdc_memreverse8(4, buf); // both-note 2{{cannot refer to element 3 of array of 2 elements in a constant expression}} + return true; +} +static_assert(test_oob(), ""); // both-error{{not an integral constant expression}} both-note{{in call to 'test_oob()'}} + +// Null pointer: assignment through null is not allowed in a constant expression. +constexpr bool test_null_ptr() { // both-error{{constexpr function never produces a constant expression}} + __builtin_stdc_memreverse8(4, (unsigned char *)0); // both-warning{{null passed to a callee that requires a non-null argument}} both-note 2{{assignment to dereferenced null pointer is not allowed in a constant expression}} + return true; +} +static_assert(test_null_ptr(), ""); // both-error{{not an integral constant expression}} both-note{{in call to 'test_null_ptr()'}} + +// N=0 but null pointer passed. +constexpr bool test_null_ptr_n0() { // both-error{{constexpr function never produces a constant expression}} + __builtin_stdc_memreverse8(0, (unsigned char *)0); // both-warning{{null passed to a callee that requires a non-null argument}} both-note 2{{assignment to dereferenced null pointer is not allowed in a constant expression}} + return true; +} +static_assert(test_null_ptr_n0(), ""); // both-error{{not an integral constant expression}} both-note{{in call to 'test_null_ptr_n0()'}} + +// N=1 but null pointer passed. +constexpr bool test_null_ptr_n1() { // both-error{{constexpr function never produces a constant expression}} + __builtin_stdc_memreverse8(1, (unsigned char *)0); // both-warning{{null passed to a callee that requires a non-null argument}} both-note 2{{assignment to dereferenced null pointer is not allowed in a constant expression}} + return true; +} +static_assert(test_null_ptr_n1(), ""); // both-error{{not an integral constant expression}} both-note{{in call to 'test_null_ptr_n1()'}} + +// N=1 but pointer is one-past-the-end. +constexpr bool test_oob_n1() { // both-error{{constexpr function never produces a constant expression}} + unsigned char buf[2] = {0x01, 0x02}; + __builtin_stdc_memreverse8(1, buf + 2); // both-note 2{{cannot refer to element 2 of array of 2 elements in a constant expression}} + return true; +} +static_assert(test_oob_n1(), ""); // both-error{{not an integral constant expression}} both-note{{in call to 'test_oob_n1()'}} + +// Swapping uninitialized memory. +constexpr bool test_uninit() { + unsigned char buf[2]; // both-note {{declared here}} + __builtin_stdc_memreverse8(2, buf); // both-note {{read of uninitialized object is not allowed in a constant expression}} + return true; +} +static_assert(test_uninit(), ""); // both-error{{not an integral constant expression}} both-note{{in call to 'test_uninit()'}} + +} // namespace test_negative >From 72a3e918710d8a73d2389a64afa80a2ba00f97cc Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Tue, 9 Jun 2026 08:32:08 -0700 Subject: [PATCH 2/7] Address review feedback for stdc_memreverse8 constexpr support - Use isReadable() with isOnePastEnd() instead of the isDummy/isBlockPointer/isLive checks - Use std::swap instead of manual three-line swap - Move initialize() calls outside INT_TYPE_SWITCH_NO_BOOL - Add test_n0_one_past_end to cover N=0 with a one-past-end pointer --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 51 ++++++++++--------- .../constexpr-builtin-stdc-memreverse8.cpp | 20 +++++--- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 951dab5efb4e3..9a5c7b142b400 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2062,7 +2062,7 @@ static bool isOneByteCharacterType(QualType T) { static bool interp__builtin_stdc_memreverse8(InterpState &S, CodePtr OpPC, const InterpFrame *Frame, const CallExpr *Call) { - Pointer Ptr = S.Stk.pop<Pointer>().expand(); + Pointer Ptr = S.Stk.pop<Pointer>(); uint64_t NElems; if (!popToUInt64(S, Call->getArg(0), NElems)) @@ -2074,25 +2074,15 @@ static bool interp__builtin_stdc_memreverse8(InterpState &S, CodePtr OpPC, return false; } - if (Ptr.isDummy() || !Ptr.isBlockPointer() || !Ptr.isLive()) + if (!isReadable(Ptr) && !Ptr.isOnePastEnd()) return false; - QualType ElemTy = Ptr.getFieldDesc()->isArray() - ? Ptr.getFieldDesc()->getElemQualType() - : Ptr.getFieldDesc()->getType(); - if (ElemTy->isIncompleteType()) { - S.FFDiag(S.Current->getSource(OpPC), - diag::note_constexpr_ltor_incomplete_type) - << ElemTy; - return false; - } - if (!isOneByteCharacterType(ElemTy)) { - S.FFDiag(S.Current->getSource(OpPC), - diag::note_constexpr_memchr_unsupported) - << S.getASTContext().BuiltinInfo.getQuotedName(Call->getBuiltinCallee()) - << ElemTy; - return false; - } + const Descriptor *Desc = Ptr.getFieldDesc(); + bool IsArray = Desc->isArray(); + QualType ElemTy = IsArray ? Desc->getElemQualType() : Desc->getType(); + + if (IsArray) + Ptr = Ptr.expand(); size_t BaseIdx = Ptr.getIndex(); size_t ArraySize = Ptr.getNumElems(); @@ -2107,6 +2097,20 @@ static bool interp__builtin_stdc_memreverse8(InterpState &S, CodePtr OpPC, if (NElems <= 1) return true; + if (ElemTy->isIncompleteType()) { + S.FFDiag(S.Current->getSource(OpPC), + diag::note_constexpr_ltor_incomplete_type) + << ElemTy; + return false; + } + if (!isOneByteCharacterType(ElemTy)) { + S.FFDiag(S.Current->getSource(OpPC), + diag::note_constexpr_memchr_unsupported) + << S.getASTContext().BuiltinInfo.getQuotedName(Call->getBuiltinCallee()) + << ElemTy; + return false; + } + PrimType ElemT = *S.getContext().classify(ElemTy); for (uint64_t I = 0, Half = NElems / 2; I < Half; ++I) { @@ -2117,13 +2121,10 @@ static bool interp__builtin_stdc_memreverse8(InterpState &S, CodePtr OpPC, !CheckLoad(S, OpPC, HiPtr, AK_Read)) return false; - INT_TYPE_SWITCH_NO_BOOL(ElemT, { - T LoVal = LoPtr.deref<T>(); - LoPtr.deref<T>() = HiPtr.deref<T>(); - HiPtr.deref<T>() = LoVal; - LoPtr.initialize(); - HiPtr.initialize(); - }); + INT_TYPE_SWITCH_NO_BOOL(ElemT, + { std::swap(LoPtr.deref<T>(), HiPtr.deref<T>()); }); + LoPtr.initialize(); + HiPtr.initialize(); } return true; } diff --git a/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp b/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp index 8064b74fc2b85..c1c2f1a6a0dea 100644 --- a/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp +++ b/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp @@ -4,20 +4,28 @@ namespace test_noop { // N=0: no bytes touched. -constexpr unsigned char test_n0() { +constexpr bool test_n0() { unsigned char buf[4] = {0x12, 0x34, 0x56, 0x78}; __builtin_stdc_memreverse8(0, buf); - return buf[0]; + return buf[0] == 0x12; } -static_assert(test_n0() == 0x12, ""); +static_assert(test_n0(), ""); // N=1: single byte is its own reverse. -constexpr unsigned char test_n1() { +constexpr bool test_n1() { unsigned char buf[4] = {0x12, 0x34, 0x56, 0x78}; __builtin_stdc_memreverse8(1, buf); - return buf[0]; + return buf[0] == 0x12; } -static_assert(test_n1() == 0x12, ""); +static_assert(test_n1(), ""); + +// N=0 with a one-past-the-end pointer: valid since no bytes are accessed. +constexpr bool test_n0_one_past_end() { + unsigned char buf[2] = {0x12, 0x34}; + __builtin_stdc_memreverse8(0, buf + 2); + return buf[0] == 0x12 && buf[1] == 0x34; +} +static_assert(test_n0_one_past_end(), ""); } // namespace test_noop >From 3eb5c71817cb13736cedfb9c15979acf847fd0e8 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Tue, 9 Jun 2026 19:28:50 -0700 Subject: [PATCH 3/7] Make sure interp__builtin_stdc_memreverse8 consistent with ExprConstant --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 36 +++++++++++++----------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 9a5c7b142b400..0cbb4dfb2b22b 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2081,22 +2081,6 @@ static bool interp__builtin_stdc_memreverse8(InterpState &S, CodePtr OpPC, bool IsArray = Desc->isArray(); QualType ElemTy = IsArray ? Desc->getElemQualType() : Desc->getType(); - if (IsArray) - Ptr = Ptr.expand(); - - size_t BaseIdx = Ptr.getIndex(); - size_t ArraySize = Ptr.getNumElems(); - size_t RemainingElems = ArraySize - BaseIdx; - if (NElems > RemainingElems) { - S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index) - << (int64_t)(BaseIdx + NElems - 1) << /*isArray=*/0 - << (uint64_t)ArraySize; - return false; - } - - if (NElems <= 1) - return true; - if (ElemTy->isIncompleteType()) { S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_ltor_incomplete_type) @@ -2111,6 +2095,26 @@ static bool interp__builtin_stdc_memreverse8(InterpState &S, CodePtr OpPC, return false; } + if (IsArray) + Ptr = Ptr.expand(); + + size_t BaseIdx = Ptr.getIndex(); + size_t ArraySize = Ptr.getNumElems(); + size_t RemainingElems = ArraySize - BaseIdx; + if (NElems > RemainingElems) { + if (IsArray) + S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index) + << (uint64_t)(BaseIdx + NElems - 1) << /*array*/ 0 + << (uint64_t)ArraySize; + else + S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index) + << (uint64_t)(BaseIdx + NElems - 1) << /*non-array*/ 1; + return false; + } + + if (NElems <= 1) + return true; + PrimType ElemT = *S.getContext().classify(ElemTy); for (uint64_t I = 0, Half = NElems / 2; I < Half; ++I) { >From 4b93f08d6a72f9e52098d34ad3ad62ea6ef09218 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Wed, 10 Jun 2026 10:39:29 -0700 Subject: [PATCH 4/7] Remove dead isIncompleteType check The argument to stdc_memreverse8 is unsigned char *, it cannot be incompletetype. --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 6 ------ clang/lib/AST/ExprConstant.cpp | 4 ---- 2 files changed, 10 deletions(-) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 0cbb4dfb2b22b..d4bcbb0dc7f2a 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2081,12 +2081,6 @@ static bool interp__builtin_stdc_memreverse8(InterpState &S, CodePtr OpPC, bool IsArray = Desc->isArray(); QualType ElemTy = IsArray ? Desc->getElemQualType() : Desc->getType(); - if (ElemTy->isIncompleteType()) { - S.FFDiag(S.Current->getSource(OpPC), - diag::note_constexpr_ltor_incomplete_type) - << ElemTy; - return false; - } if (!isOneByteCharacterType(ElemTy)) { S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_memchr_unsupported) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 314429b949c8b..7c54e77ef0317 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -21118,10 +21118,6 @@ class VoidExprEvaluator return false; QualType CharTy = Ptr.Designator.getType(Info.Ctx); - if (CharTy->isIncompleteType()) { - Info.FFDiag(E, diag::note_constexpr_ltor_incomplete_type) << CharTy; - return false; - } if (!isOneByteCharacterType(CharTy)) { Info.FFDiag(E, diag::note_constexpr_memchr_unsupported) << Info.Ctx.BuiltinInfo.getQuotedName(E->getBuiltinCallee()) >From 177565f45b6db0eef98e58a8ca5b54bd8d0d0d03 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Wed, 10 Jun 2026 10:51:36 -0700 Subject: [PATCH 5/7] Add test for case when N exceeds the size of scalar type --- clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp b/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp index c1c2f1a6a0dea..3b0bc02b37dd9 100644 --- a/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp +++ b/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp @@ -270,6 +270,14 @@ constexpr bool test_oob_n1() { // both-error{{constexpr function never produces } static_assert(test_oob_n1(), ""); // both-error{{not an integral constant expression}} both-note{{in call to 'test_oob_n1()'}} +// N exceeds the size of a scalar (non-array) object. +constexpr bool test_scalar_oob() { // both-error{{constexpr function never produces a constant expression}} + unsigned char c = 0x42; + __builtin_stdc_memreverse8(4, &c); // both-note 2{{cannot refer to element 3 of non-array object in a constant expression}} + return true; +} +static_assert(test_scalar_oob(), ""); // both-error{{not an integral constant expression}} both-note{{in call to 'test_scalar_oob()'}} + // Swapping uninitialized memory. constexpr bool test_uninit() { unsigned char buf[2]; // both-note {{declared here}} >From 78892c3a905608b800b4bf130dd5c7b8fe0c018c Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Wed, 10 Jun 2026 12:42:50 -0700 Subject: [PATCH 6/7] Add check in Interp to detect if the ptr is a const --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 7 +++++++ clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index d4bcbb0dc7f2a..9f29860739425 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2109,6 +2109,13 @@ static bool interp__builtin_stdc_memreverse8(InterpState &S, CodePtr OpPC, if (NElems <= 1) return true; + Pointer FirstPtr = Ptr.atIndex(BaseIdx); + if (FirstPtr.isConst()) { + S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_modify_const_type) + << FirstPtr.getType(); + return false; + } + PrimType ElemT = *S.getContext().classify(ElemTy); for (uint64_t I = 0, Half = NElems / 2; I < Half; ++I) { diff --git a/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp b/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp index 3b0bc02b37dd9..25ebb919497c3 100644 --- a/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp +++ b/clang/test/SemaCXX/constexpr-builtin-stdc-memreverse8.cpp @@ -286,4 +286,12 @@ constexpr bool test_uninit() { } static_assert(test_uninit(), ""); // both-error{{not an integral constant expression}} both-note{{in call to 'test_uninit()'}} +// Reversing const-qualified array: must be rejected in a constant expression. +constexpr bool test_const_violation_array() { // both-error{{constexpr function never produces a constant expression}} + const unsigned char buf[4] = {0x01, 0x02, 0x03, 0x04}; + __builtin_stdc_memreverse8(4, const_cast<unsigned char *>(buf)); // both-note 2{{modification of object of const-qualified type 'const unsigned char' is not allowed in a constant expression}} + return true; +} +static_assert(test_const_violation_array(), ""); // both-error{{not an integral constant expression}} both-note{{in call to 'test_const_violation_array()'}} + } // namespace test_negative >From 9eedc9460505ed67ad5daaafa22f4281c2f7ad61 Mon Sep 17 00:00:00 2001 From: NagaChaitanya Vellanki <[email protected]> Date: Thu, 25 Jun 2026 17:48:44 -0700 Subject: [PATCH 7/7] Remove redundant isOneByteCharacterType check from stdc_memreverse8 evaluators The parameter type is unsigned char *, so the type system already rejects any other pointer type at the call site. The check in the constexpr evaluators is dead code. --- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 8 -------- clang/lib/AST/ExprConstant.cpp | 7 ------- 2 files changed, 15 deletions(-) diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index 9f29860739425..3c469fd4f2b70 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -2081,14 +2081,6 @@ static bool interp__builtin_stdc_memreverse8(InterpState &S, CodePtr OpPC, bool IsArray = Desc->isArray(); QualType ElemTy = IsArray ? Desc->getElemQualType() : Desc->getType(); - if (!isOneByteCharacterType(ElemTy)) { - S.FFDiag(S.Current->getSource(OpPC), - diag::note_constexpr_memchr_unsupported) - << S.getASTContext().BuiltinInfo.getQuotedName(Call->getBuiltinCallee()) - << ElemTy; - return false; - } - if (IsArray) Ptr = Ptr.expand(); diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 7c54e77ef0317..4b6a8263748e7 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -21118,13 +21118,6 @@ class VoidExprEvaluator return false; QualType CharTy = Ptr.Designator.getType(Info.Ctx); - if (!isOneByteCharacterType(CharTy)) { - Info.FFDiag(E, diag::note_constexpr_memchr_unsupported) - << Info.Ctx.BuiltinInfo.getQuotedName(E->getBuiltinCallee()) - << CharTy; - return false; - } - uint64_t RemainingElems = Ptr.Designator.validIndexAdjustments().second; if (NElems > RemainingElems) { uint64_t ArrayIndex = _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
