[clang] [clang] Add constexpr support for stdc_memreverse8 (PR #202402)
NagaChaitanya Vellanki via cfe-commits
cfe-commits at lists.llvm.org
Thu Jun 25 17:55:39 PDT 2026
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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 <pnagato at protonmail.com>
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 =
More information about the cfe-commits
mailing list