Author: Timm Baeder
Date: 2026-04-06T15:52:17+02:00
New Revision: 59e899e16b7492698bd7673a7cdbe3a54368b703

URL: 
https://github.com/llvm/llvm-project/commit/59e899e16b7492698bd7673a7cdbe3a54368b703
DIFF: 
https://github.com/llvm/llvm-project/commit/59e899e16b7492698bd7673a7cdbe3a54368b703.diff

LOG: [clang][bytecode] Don't unref constexpr-unknown references (#190177)

If the pointer for a reference is constexpr-unknown, use the pointer
itself instead, instead of dereferencing it. Unfortunately, that means
constexpr-unknown pointers to reach a lot more places than before.

Added: 
    

Modified: 
    clang/lib/AST/ByteCode/Compiler.cpp
    clang/lib/AST/ByteCode/EvalEmitter.cpp
    clang/lib/AST/ByteCode/Interp.cpp
    clang/lib/AST/ByteCode/Interp.h
    clang/lib/AST/ByteCode/Opcodes.td
    clang/lib/AST/ByteCode/Pointer.h
    clang/test/AST/ByteCode/cxx26.cpp
    clang/test/SemaCXX/constant-expression-p2280r4.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/AST/ByteCode/Compiler.cpp 
b/clang/lib/AST/ByteCode/Compiler.cpp
index 9d2457a2f35cd..4f517266336f2 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -7491,8 +7491,10 @@ bool Compiler<Emitter>::visitDeclRef(const ValueDecl *D, 
const Expr *E) {
   // Local variables.
   if (auto It = Locals.find(D); It != Locals.end()) {
     const unsigned Offset = It->second.Offset;
-    if (IsReference)
-      return this->emitGetLocal(classifyPrim(E), Offset, E);
+    if (IsReference) {
+      assert(classifyPrim(E) == PT_Ptr);
+      return this->emitGetRefLocal(Offset, E);
+    }
     return this->emitGetPtrLocal(Offset, E);
   }
   // Global variables.
@@ -7561,7 +7563,7 @@ bool Compiler<Emitter>::visitDeclRef(const ValueDecl *D, 
const Expr *E) {
   if (!Ctx.getLangOpts().CPlusPlus) {
     if (VD->getAnyInitializer() && DeclType.isConstant(Ctx.getASTContext()) &&
         !VD->isWeak())
-      return revisit(VD, DeclType->isPointerType());
+      return revisit(VD, /*IsConstexprUnknown=*/false);
     return this->emitDummyPtr(D, E);
   }
 
@@ -7601,8 +7603,8 @@ bool Compiler<Emitter>::visitDeclRef(const ValueDecl *D, 
const Expr *E) {
       // 
diff erent evaluation, so e.g. mutable reads don't work on it.
       EvalIDScope _(Ctx);
       return revisit(VD, IsConstexprUnknown);
-    } else if (Ctx.getLangOpts().CPlusPlus26 && IsReference)
-      return revisit(VD, true);
+    } else if (Ctx.getLangOpts().CPlusPlus23 && IsReference)
+      return revisit(VD, /*IsConstexprUnknown=*/true);
 
     if (IsReference)
       return this->emitInvalidDeclRef(cast<DeclRefExpr>(E),

diff  --git a/clang/lib/AST/ByteCode/EvalEmitter.cpp 
b/clang/lib/AST/ByteCode/EvalEmitter.cpp
index 9cbd786e883a2..036cd2b9a62fa 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.cpp
+++ b/clang/lib/AST/ByteCode/EvalEmitter.cpp
@@ -220,7 +220,7 @@ template <> bool EvalEmitter::emitRet<PT_Ptr>(SourceInfo 
Info) {
 
   // Implicitly convert lvalue to rvalue, if requested.
   if (ConvertResultToRValue) {
-    if (!Ptr.isZero() && !Ptr.isDereferencable())
+    if (Ptr.isPastEnd())
       return false;
 
     if (Ptr.pointsToStringLiteral() && Ptr.isArrayRoot())
@@ -291,6 +291,14 @@ bool EvalEmitter::emitGetPtrLocal(uint32_t I, SourceInfo 
Info) {
   return true;
 }
 
+bool EvalEmitter::emitGetRefLocal(uint32_t I, SourceInfo Info) {
+  if (!isActive())
+    return true;
+
+  Block *B = getLocal(I);
+  return handleReference(S, OpPC, B);
+}
+
 template <PrimType OpType>
 bool EvalEmitter::emitGetLocal(uint32_t I, SourceInfo Info) {
   if (!isActive())

diff  --git a/clang/lib/AST/ByteCode/Interp.cpp 
b/clang/lib/AST/ByteCode/Interp.cpp
index 96a8ad609eaf1..8cc3c9216f7f4 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -259,14 +259,16 @@ void cleanupAfterFunctionCall(InterpState &S, CodePtr 
OpPC,
     S.Stk.discard<Pointer>();
 }
 
+bool isConstexprUnknown(const Block *B) {
+  if (B->isDummy())
+    return isa_and_nonnull<ParmVarDecl>(B->getDescriptor()->asValueDecl());
+  return B->getDescriptor()->IsConstexprUnknown;
+}
+
 bool isConstexprUnknown(const Pointer &P) {
   if (!P.isBlockPointer())
     return false;
-
-  if (P.isDummy())
-    return isa_and_nonnull<ParmVarDecl>(P.getDeclDesc()->asValueDecl());
-
-  return P.getDeclDesc()->IsConstexprUnknown;
+  return isConstexprUnknown(P.block());
 }
 
 bool CheckBCPResult(InterpState &S, const Pointer &Ptr) {
@@ -814,7 +816,7 @@ bool CheckLoad(InterpState &S, CodePtr OpPC, const Pointer 
&Ptr,
     return false;
   if (!CheckVolatile(S, OpPC, Ptr, AK))
     return false;
-  if (!Ptr.isConst() && !S.inConstantContext() && isConstexprUnknown(Ptr))
+  if (isConstexprUnknown(Ptr))
     return false;
   return true;
 }
@@ -849,6 +851,8 @@ bool CheckFinalLoad(InterpState &S, CodePtr OpPC, const 
Pointer &Ptr) {
     return false;
   if (!CheckMutable(S, OpPC, Ptr))
     return false;
+  if (Ptr.isConstexprUnknown())
+    return false;
   return true;
 }
 
@@ -2204,6 +2208,25 @@ bool CheckBitCast(InterpState &S, CodePtr OpPC, bool 
HasIndeterminateBits,
   return false;
 }
 
+bool handleReference(InterpState &S, CodePtr OpPC, Block *B) {
+  if (isConstexprUnknown(B)) {
+    S.Stk.push<Pointer>(B);
+    return true;
+  }
+
+  const auto &ID = B->getBlockDesc<const InlineDescriptor>();
+  if (!ID.IsInitialized) {
+    if (!S.checkingPotentialConstantExpression())
+      S.FFDiag(S.Current->getSource(OpPC),
+               diag::note_constexpr_use_uninit_reference);
+    return false;
+  }
+
+  assert(B->getDescriptor()->getPrimType() == PT_Ptr);
+  S.Stk.push<Pointer>(B->deref<Pointer>());
+  return true;
+}
+
 bool GetTypeid(InterpState &S, CodePtr OpPC, const Type *TypePtr,
                const Type *TypeInfoType) {
   S.Stk.push<Pointer>(TypePtr, TypeInfoType);

diff  --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 2e7045b39c3db..91e9461befcd9 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -133,6 +133,7 @@ bool CheckDestructor(InterpState &S, CodePtr OpPC, const 
Pointer &Ptr);
 bool CheckFunctionDecl(InterpState &S, CodePtr OpPC, const FunctionDecl *FD);
 bool CheckBitCast(InterpState &S, CodePtr OpPC, const Type *TargetType,
                   bool SrcIsVoidPtr);
+bool handleReference(InterpState &S, CodePtr OpPC, Block *B);
 bool InvalidCast(InterpState &S, CodePtr OpPC, CastKind Kind, bool Fatal);
 
 bool handleFixedPointOverflow(InterpState &S, CodePtr OpPC,
@@ -140,6 +141,7 @@ bool handleFixedPointOverflow(InterpState &S, CodePtr OpPC,
 
 bool Destroy(InterpState &S, CodePtr OpPC, uint32_t I);
 bool isConstexprUnknown(const Pointer &P);
+bool isConstexprUnknown(const Block *B);
 
 enum class ShiftDir { Left, Right };
 
@@ -1637,6 +1639,9 @@ bool InitThisField(InterpState &S, CodePtr OpPC, uint32_t 
I) {
   if (!CheckThis(S, OpPC))
     return false;
   const Pointer &This = S.Current->getThis();
+  if (!This.isDereferencable())
+    return false;
+
   const Pointer &Field = This.atField(I);
   assert(Field.canBeInitialized());
   Field.deref<T>() = S.Stk.pop<T>();
@@ -1651,6 +1656,9 @@ bool InitThisFieldActivate(InterpState &S, CodePtr OpPC, 
uint32_t I) {
   if (!CheckThis(S, OpPC))
     return false;
   const Pointer &This = S.Current->getThis();
+  if (!This.isDereferencable())
+    return false;
+
   const Pointer &Field = This.atField(I);
   assert(Field.canBeInitialized());
   Field.deref<T>() = S.Stk.pop<T>();
@@ -1670,6 +1678,9 @@ bool InitThisBitField(InterpState &S, CodePtr OpPC,
   if (!CheckThis(S, OpPC))
     return false;
   const Pointer &This = S.Current->getThis();
+  if (!This.isDereferencable())
+    return false;
+
   const Pointer &Field = This.atField(FieldOffset);
   assert(Field.canBeInitialized());
   const auto &Value = S.Stk.pop<T>();
@@ -1686,6 +1697,9 @@ bool InitThisBitFieldActivate(InterpState &S, CodePtr 
OpPC,
   if (!CheckThis(S, OpPC))
     return false;
   const Pointer &This = S.Current->getThis();
+  if (!This.isDereferencable())
+    return false;
+
   const Pointer &Field = This.atField(FieldOffset);
   assert(Field.canBeInitialized());
   const auto &Value = S.Stk.pop<T>();
@@ -1702,6 +1716,9 @@ template <PrimType Name, class T = typename 
PrimConv<Name>::T>
 bool InitField(InterpState &S, CodePtr OpPC, uint32_t I) {
   const T &Value = S.Stk.pop<T>();
   const Pointer &Ptr = S.Stk.peek<Pointer>();
+  if (!Ptr.isDereferencable())
+    return false;
+
   if (!CheckRange(S, OpPC, Ptr, CSK_Field))
     return false;
   if (!CheckArray(S, OpPC, Ptr))
@@ -1717,6 +1734,9 @@ template <PrimType Name, class T = typename 
PrimConv<Name>::T>
 bool InitFieldActivate(InterpState &S, CodePtr OpPC, uint32_t I) {
   const T &Value = S.Stk.pop<T>();
   const Pointer &Ptr = S.Stk.peek<Pointer>();
+  if (!Ptr.isDereferencable())
+    return false;
+
   if (!CheckRange(S, OpPC, Ptr, CSK_Field))
     return false;
   if (!CheckArray(S, OpPC, Ptr))
@@ -1734,6 +1754,9 @@ bool InitBitField(InterpState &S, CodePtr OpPC, uint32_t 
FieldOffset,
                   uint32_t FieldBitWidth) {
   const T &Value = S.Stk.pop<T>();
   const Pointer &Ptr = S.Stk.peek<Pointer>();
+  if (!Ptr.isDereferencable())
+    return false;
+
   if (!CheckRange(S, OpPC, Ptr, CSK_Field))
     return false;
   if (!CheckArray(S, OpPC, Ptr))
@@ -1764,6 +1787,9 @@ bool InitBitFieldActivate(InterpState &S, CodePtr OpPC, 
uint32_t FieldOffset,
                           uint32_t FieldBitWidth) {
   const T &Value = S.Stk.pop<T>();
   const Pointer &Ptr = S.Stk.peek<Pointer>();
+  if (!Ptr.isDereferencable())
+    return false;
+
   if (!CheckRange(S, OpPC, Ptr, CSK_Field))
     return false;
   if (!CheckArray(S, OpPC, Ptr))
@@ -1799,6 +1825,11 @@ inline bool GetPtrLocal(InterpState &S, CodePtr OpPC, 
uint32_t I) {
   return true;
 }
 
+inline bool GetRefLocal(InterpState &S, CodePtr OpPC, uint32_t I) {
+  Block *LocalBlock = S.Current->getLocalBlock(I);
+  return handleReference(S, OpPC, LocalBlock);
+}
+
 inline bool GetPtrParam(InterpState &S, CodePtr OpPC, uint32_t Index) {
   if (S.Current->isBottomFrame())
     return false;
@@ -1872,6 +1903,9 @@ inline bool GetPtrBase(InterpState &S, CodePtr OpPC, 
uint32_t Off) {
     return true;
   }
 
+  if (isConstexprUnknown(Ptr))
+    return false;
+
   if (!CheckSubobject(S, OpPC, Ptr, CSK_Base))
     return false;
   const Pointer &Result = Ptr.atField(Off);
@@ -1895,6 +1929,9 @@ inline bool GetPtrBasePop(InterpState &S, CodePtr OpPC, 
uint32_t Off,
     return true;
   }
 
+  if (isConstexprUnknown(Ptr))
+    return false;
+
   if (!CheckSubobject(S, OpPC, Ptr, CSK_Base))
     return false;
   const Pointer &Result = Ptr.atField(Off);
@@ -2191,6 +2228,9 @@ bool InitElem(InterpState &S, CodePtr OpPC, uint32_t Idx) 
{
   const T &Value = S.Stk.pop<T>();
   const Pointer &Ptr = S.Stk.peek<Pointer>();
 
+  if (Ptr.isConstexprUnknown())
+    return false;
+
   const Descriptor *Desc = Ptr.getFieldDesc();
   if (Desc->isUnknownSizeArray())
     return false;
@@ -2225,6 +2265,9 @@ bool InitElemPop(InterpState &S, CodePtr OpPC, uint32_t 
Idx) {
   const T &Value = S.Stk.pop<T>();
   const Pointer &Ptr = S.Stk.pop<Pointer>();
 
+  if (Ptr.isConstexprUnknown())
+    return false;
+
   const Descriptor *Desc = Ptr.getFieldDesc();
   if (Desc->isUnknownSizeArray())
     return false;
@@ -2869,7 +2912,8 @@ inline bool This(InterpState &S, CodePtr OpPC) {
       [[maybe_unused]] const Record *R = This.getRecord();
       if (!R)
         R = This.narrow().getRecord();
-      assert(R);
+      if (!R)
+        return false;
       assert(R->getDecl() ==
              cast<CXXMethodDecl>(S.Current->getFunction()->getDecl())
                  ->getParent());

diff  --git a/clang/lib/AST/ByteCode/Opcodes.td 
b/clang/lib/AST/ByteCode/Opcodes.td
index 5e4d0ab2a84af..0215cd92966ee 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -322,6 +322,9 @@ class OffsetOpcode : Opcode {
 def GetPtrLocal : OffsetOpcode {
   bit HasCustomEval = 1;
 }
+def GetRefLocal : OffsetOpcode {
+  bit HasCustomEval = 1;
+}
 // [] -> [Pointer]
 def GetPtrParam : OffsetOpcode;
 // [] -> [Pointer]

diff  --git a/clang/lib/AST/ByteCode/Pointer.h 
b/clang/lib/AST/ByteCode/Pointer.h
index ea9c7d4cb04db..9205862d2f2dc 100644
--- a/clang/lib/AST/ByteCode/Pointer.h
+++ b/clang/lib/AST/ByteCode/Pointer.h
@@ -716,11 +716,21 @@ class Pointer {
     return *reinterpret_cast<T *>(BS.Pointee->rawData() + ReadOffset);
   }
 
+  bool isConstexprUnknown() const {
+    if (!isBlockPointer())
+      return false;
+    return getDeclDesc()->IsConstexprUnknown;
+  }
+
   /// Whether this block can be read from at all. This is only true for
   /// block pointers that point to a valid location inside that block.
   bool isDereferencable() const {
     if (!isBlockPointer())
       return false;
+    if (isDummy())
+      return false;
+    if (isConstexprUnknown())
+      return false;
     if (isPastEnd())
       return false;
 

diff  --git a/clang/test/AST/ByteCode/cxx26.cpp 
b/clang/test/AST/ByteCode/cxx26.cpp
index d4349d84996cc..769deb28cdf50 100644
--- a/clang/test/AST/ByteCode/cxx26.cpp
+++ b/clang/test/AST/ByteCode/cxx26.cpp
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=ref,both      %s
-// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=expected,both %s 
-fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++26 -fsyntax-only -Wunreachable-code 
-verify=ref,both      %s
+// RUN: %clang_cc1 -std=c++26 -fsyntax-only -Wunreachable-code 
-verify=expected,both %s -fexperimental-new-constant-interpreter
 
 namespace std {
   using size_t = decltype(sizeof(0));
@@ -73,3 +73,21 @@ namespace ConstexprUnknownNestedVariables {
 
   static_assert(f() == 42);
 }
+
+namespace ConstexprUnknownReference {
+
+  struct expected {
+    int val;
+  };
+
+  extern void __assert_fail();
+  bool test() {
+    expected e(5);
+
+    const int &x = e.val;
+    /// We used to get a warning for an always-true comparison.
+    &(static_cast<const int&>(x)) == &e.val ? void() : __assert_fail();
+    return true;
+  }
+
+}

diff  --git a/clang/test/SemaCXX/constant-expression-p2280r4.cpp 
b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
index 7c8e3e975f091..f4a8fbcbbe427 100644
--- a/clang/test/SemaCXX/constant-expression-p2280r4.cpp
+++ b/clang/test/SemaCXX/constant-expression-p2280r4.cpp
@@ -211,16 +211,14 @@ namespace uninit_reference_used {
   constexpr int &rr = (rr, y);
   constexpr int &g() {
     int &x = x; // expected-warning {{reference 'x' is not yet bound to a 
value when used within its own initialization}} \
-    // nointerpreter-note {{use of reference outside its lifetime is not 
allowed in a constant expression}} \
-    // interpreter-note {{read of uninitialized object is not allowed in a 
constant expression}}
+                // expected-note {{use of reference outside its lifetime is 
not allowed in a constant expression}}
     return x;
   }
   constexpr int &gg = g(); // expected-error {{must be initialized by a 
constant expression}} \
   // expected-note {{in call to 'g()'}}
   constexpr int g2() {
     int &x = x; // expected-warning {{reference 'x' is not yet bound to a 
value when used within its own initialization}} \
-    // nointerpreter-note {{use of reference outside its lifetime is not 
allowed in a constant expression}} \
-    // interpreter-note {{read of uninitialized object is not allowed in a 
constant expression}}
+                // expected-note {{use of reference outside its lifetime is 
not allowed in a constant expression}}
     return x;
   }
   constexpr int gg2 = g2(); // expected-error {{must be initialized by a 
constant expression}} \
@@ -236,8 +234,7 @@ namespace uninit_reference_used {
   typedef decltype(sizeof(1)) uintptr_t;
   constexpr uintptr_t g4() {
     uintptr_t * &x = x; // expected-warning {{reference 'x' is not yet bound 
to a value when used within its own initialization}} \
-    // nointerpreter-note {{use of reference outside its lifetime is not 
allowed in a constant expression}} \
-    // interpreter-note {{read of uninitialized object is not allowed in a 
constant expression}}
+                        // expected-note {{use of reference outside its 
lifetime is not allowed in a constant expression}}
     *(uintptr_t*)x = 10;
     return 3;
   }
@@ -245,8 +242,7 @@ namespace uninit_reference_used {
   // expected-note {{in call to 'g4()'}}
   constexpr int g5() {
     int &x = x; // expected-warning {{reference 'x' is not yet bound to a 
value when used within its own initialization}} \
-    // nointerpreter-note {{use of reference outside its lifetime is not 
allowed in a constant expression}} \
-    // interpreter-note {{read of uninitialized object is not allowed in a 
constant expression}}
+                // expected-note {{use of reference outside its lifetime is 
not allowed in a constant expression}}
     return 3;
   }
   constexpr uintptr_t gg5 = g5(); // expected-error {{must be initialized by a 
constant expression}} \


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to