Timm =?utf-8?q?Bäder?= <[email protected]>,
Timm =?utf-8?q?Bäder?= <[email protected]>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/[email protected]>


https://github.com/tbaederr updated 
https://github.com/llvm/llvm-project/pull/204781

>From f7e738898d940b538eff47a7467f807047507eb0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
Date: Thu, 18 Jun 2026 17:57:09 +0200
Subject: [PATCH 1/3] puh

---
 clang/lib/AST/ByteCode/ByteCodeEmitter.h      |   4 +
 clang/lib/AST/ByteCode/Compiler.cpp           | 117 +++++++++-
 clang/lib/AST/ByteCode/Compiler.h             |   3 +
 clang/lib/AST/ByteCode/Context.cpp            |  27 +++
 clang/lib/AST/ByteCode/Context.h              |   6 +
 clang/lib/AST/ByteCode/EvalEmitter.cpp        |  17 ++
 clang/lib/AST/ByteCode/EvalEmitter.h          |   9 +
 clang/lib/AST/ExprConstant.cpp                |  11 +
 .../test/AST/ByteCode/builtin-constant-p.cpp  |   5 +-
 clang/test/AST/ByteCode/enable_if.c           | 202 ++++++++++++++++++
 ...-nested-call-with-valuedependent-param.cpp |   1 +
 clang/test/SemaCXX/enable_if.cpp              |   2 +
 12 files changed, 399 insertions(+), 5 deletions(-)
 create mode 100644 clang/test/AST/ByteCode/enable_if.c

diff --git a/clang/lib/AST/ByteCode/ByteCodeEmitter.h 
b/clang/lib/AST/ByteCode/ByteCodeEmitter.h
index 4b42b7eb4063b..34342e53837b9 100644
--- a/clang/lib/AST/ByteCode/ByteCodeEmitter.h
+++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.h
@@ -50,6 +50,10 @@ class ByteCodeEmitter {
   virtual bool visitDeclAndReturn(const VarDecl *VD, const Expr *Init,
                                   bool ConstantContext) = 0;
   virtual bool visitDtorCall(const VarDecl *VD, const APValue &) = 0;
+  virtual bool visitWithSubstitutions(const FunctionDecl *Callee,
+                                      ArrayRef<const Expr *> Args,
+                                      const Expr *This,
+                                      const Expr *Condition) = 0;
   virtual bool visit(const Expr *E) = 0;
   virtual bool emitBool(bool V, const Expr *E) = 0;
 
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp 
b/clang/lib/AST/ByteCode/Compiler.cpp
index e8572afe8f69c..fa2a68e6edd19 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -5416,6 +5416,115 @@ bool Compiler<Emitter>::visitDtorCall(const VarDecl 
*VD, const APValue &Value) {
   return this->emitDestructionPop(D, VD);
 }
 
+class ParamFinder : public DynamicRecursiveASTVisitorBase<true> {
+public:
+  llvm::SmallPtrSet<const ParmVarDecl *, 1> FoundParams;
+  explicit ParamFinder() {}
+
+  bool VisitDeclRefExpr(const DeclRefExpr *E) override {
+    if (const auto *P = dyn_cast<ParmVarDecl>(E->getDecl()))
+      FoundParams.insert(P);
+    return true;
+  }
+};
+
+/// Evaluate the \p Condition as if it was in the body of \p Callee.
+/// Specifically, all the parameters of the callee are available to use
+/// for the condition, and their values are given by \p Args (and \p This).
+///
+// Since this is a somewhat niche feature, we're abusing a few other mechanisms
+// to implement this.
+//
+// We don't create an actual function frame but instead register the parameters
+// as local variables.
+//
+// So we evaluate something like:
+//
+// bool thisfunc() {
+//    auto Arg0 = Args[0];
+//    ...
+//    return Condition;
+// }
+//
+template <class Emitter>
+bool Compiler<Emitter>::visitWithSubstitutions(const FunctionDecl *Callee,
+                                               ArrayRef<const Expr *> Args,
+                                               const Expr *This,
+                                               const Expr *Condition) {
+  // Instead of evaluating all parameters and trying to ignore failure,
+  // we collect all the parameters used in the condition and only evaluate
+  // those. Note that we still ignore failure in the loop below because the
+  // failure might be inconsequential in the end,
+  // e.g. in the case of `true || x`.
+  ParamFinder PF;
+  PF.TraverseStmt(Condition);
+
+  LocalScope<Emitter> ArgScope(this);
+  for (const ParmVarDecl *PVD : PF.FoundParams) {
+    unsigned ParamIndex = 0;
+    for (const ParmVarDecl *P : Callee->parameters()) {
+      if (P == PVD)
+        break;
+      ++ParamIndex;
+    }
+
+    const Expr *Arg = Args[ParamIndex];
+    const ParmVarDecl *Param = Callee->getParamDecl(ParamIndex);
+    if (OptPrimType ParamT = classify(Param->getType())) {
+      unsigned ArgOffset =
+          allocateLocalPrimitive(Param, *ParamT, /*IsConst=*/true);
+      if (!this->visit(Arg))
+        continue;
+      if (!this->emitSetLocal(*ParamT, ArgOffset, Arg))
+        return false;
+    } else {
+      UnsignedOrNone ArgOffset = this->allocateLocal(Param, Param->getType());
+      if (!ArgOffset)
+        return false;
+      if (!this->emitGetPtrLocal(*ArgOffset, Arg))
+        return false;
+      if (!this->visitInitializerPop(Arg))
+        continue;
+    }
+  }
+
+  if (This) {
+    // We abuse the init stack for this and tell it to use
+    // either a local variable or another decl for the This pointer.
+    this->InitStackActive = true;
+
+    if (This->getType()->isPointerType()) {
+      // Nothing to do here, the evaluation will fail if the instance
+      // pointer is used.
+    } else if (const auto *DRE = dyn_cast<DeclRefExpr>(This)) {
+      InitStack.push_back(InitLink::Decl(DRE->getDecl()));
+    } else {
+      assert(!canClassify(This->getType()));
+      UnsignedOrNone ArgOffset = this->allocateLocal(This, This->getType());
+      if (!ArgOffset)
+        return false;
+      if (!this->emitGetPtrLocal(*ArgOffset, This))
+        return false;
+      if (!this->visitInitializerPop(This))
+        return false;
+      this->InitStack.push_back(InitLink::Temp(*ArgOffset));
+    }
+  }
+
+  // Destruction of the argument values is part of the callee frame,
+  // so we simply ignore them here.
+  this->VarScope = nullptr;
+
+  LocalScope<Emitter> RetScope(this);
+  if (!this->visit(Condition))
+    return false;
+  if (!RetScope.destroyLocals())
+    return false;
+
+  // Result of the condition should be on the stack.
+  return this->emitRet(PT_Bool, Condition);
+}
+
 template <class Emitter>
 bool Compiler<Emitter>::visitAPValue(const APValue &Val, PrimType ValType,
                                      const Expr *E) {
@@ -6042,7 +6151,10 @@ bool Compiler<Emitter>::VisitCXXThisExpr(const 
CXXThisExpr *E) {
   if (StartIndex == 0 && EndIndex == 0)
     EndIndex = InitStack.size() - 1;
 
-  assert(StartIndex < EndIndex);
+  // NOTE: This could be StartIndex < EndIndex, but we're also abusing the
+  // InitStack mechanism in visitWithSubstitutions to have the This pointer
+  // _just_ be a local variable.
+  assert(StartIndex <= EndIndex);
 
   // Emit the instructions.
   for (unsigned I = StartIndex; I != (EndIndex + 1); ++I) {
@@ -7702,10 +7814,11 @@ bool Compiler<Emitter>::visitDeclRef(const ValueDecl 
*D, const Expr *E) {
       return this->emitGetPtrParam(It->second.Index, E);
     }
 
-    if (!Ctx.getLangOpts().CPlusPlus23 && IsReference)
+    if (!Ctx.getLangOpts().CPlusPlus23 && IsReference && !Locals.contains(D))
       return this->emitInvalidDeclRef(cast<DeclRefExpr>(E),
                                       /*InitializerFailed=*/false, E);
   }
+
   // Local variables.
   if (auto It = Locals.find(D); It != Locals.end()) {
     const unsigned Offset = It->second.Offset;
diff --git a/clang/lib/AST/ByteCode/Compiler.h 
b/clang/lib/AST/ByteCode/Compiler.h
index e0008e4eeebc4..741e619230e16 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -260,6 +260,9 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, 
bool>,
   bool visitDeclAndReturn(const VarDecl *VD, const Expr *Init,
                           bool ConstantContext) override;
   bool visitDtorCall(const VarDecl *VD, const APValue &Value) override;
+  bool visitWithSubstitutions(const FunctionDecl *Callee,
+                              ArrayRef<const Expr *> Args, const Expr *This,
+                              const Expr *Condition) override;
 
 protected:
   /// Emits scope cleanup instructions.
diff --git a/clang/lib/AST/ByteCode/Context.cpp 
b/clang/lib/AST/ByteCode/Context.cpp
index 4beb35a9a7b43..10cafd4b47934 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -387,6 +387,33 @@ Context::tryEvaluateObjectSize(State &Parent, const Expr 
*E, unsigned Kind) {
   return Result;
 }
 
+std::optional<bool>
+Context::evaluateWithSubstitution(State &Parent, const FunctionDecl *Callee,
+                                  ArrayRef<const Expr *> Args, const Expr 
*This,
+                                  const Expr *Condition) {
+  if (OptPrimType ConditionT = classify(Condition);
+      !ConditionT || ConditionT != PT_Bool) {
+    return std::nullopt;
+  }
+
+  assert(Stk.empty());
+  Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
+  std::optional<bool> Result =
+      C.interpretWithSubstitutions(Callee, Args, This, Condition);
+
+  // This is somewhat of a special case here. We don't allow
+  // evaluateWithSubstitution to recurse (see the Stk.empty() assertion above),
+  // BUT we allow the args to fail evaluation, which means they can leave some
+  // garbage on the stack. So we always clear() here, not only if the 
evaluation
+  // failed.
+  Stk.clear();
+  if (!Result) {
+    C.cleanup();
+    return std::nullopt;
+  }
+  return Result;
+}
+
 const LangOptions &Context::getLangOpts() const { return Ctx.getLangOpts(); }
 
 static PrimType integralTypeToPrimTypeS(unsigned BitWidth) {
diff --git a/clang/lib/AST/ByteCode/Context.h b/clang/lib/AST/ByteCode/Context.h
index 789f72ae34f73..47821a3e3a7f3 100644
--- a/clang/lib/AST/ByteCode/Context.h
+++ b/clang/lib/AST/ByteCode/Context.h
@@ -97,6 +97,12 @@ class Context final {
   std::optional<uint64_t> tryEvaluateObjectSize(State &Parent, const Expr *E,
                                                 unsigned Kind);
 
+  std::optional<bool> evaluateWithSubstitution(State &Parent,
+                                               const FunctionDecl *Callee,
+                                               ArrayRef<const Expr *> Args,
+                                               const Expr *This,
+                                               const Expr *Condition);
+
   /// Returns the AST context.
   ASTContext &getASTContext() const { return Ctx; }
   /// Returns the language options.
diff --git a/clang/lib/AST/ByteCode/EvalEmitter.cpp 
b/clang/lib/AST/ByteCode/EvalEmitter.cpp
index 7aeac16b9639e..3f0cbf04ee232 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.cpp
+++ b/clang/lib/AST/ByteCode/EvalEmitter.cpp
@@ -126,6 +126,23 @@ bool EvalEmitter::interpretCall(const FunctionDecl *FD, 
const Expr *E) {
   return this->visitExpr(E, /*DestroyToplevelScope=*/false);
 }
 
+std::optional<bool> EvalEmitter::interpretWithSubstitutions(
+    const FunctionDecl *Callee, ArrayRef<const Expr *> Args, const Expr *This,
+    const Expr *Condition) {
+
+  if (!this->visitWithSubstitutions(Callee, Args, This, Condition))
+    return std::nullopt;
+
+  if (EvalResult.empty() || EvalResult.isInvalid())
+    return false;
+
+  assert(!EvalResult.empty());
+  APValue Result = EvalResult.stealAPValue();
+
+  assert(Result.isInt());
+  return Result.getInt().getBoolValue();
+}
+
 void EvalEmitter::emitLabel(LabelTy Label) { CurrentLabel = Label; }
 
 EvalEmitter::LabelTy EvalEmitter::getLabel() { return NextLabel++; }
diff --git a/clang/lib/AST/ByteCode/EvalEmitter.h 
b/clang/lib/AST/ByteCode/EvalEmitter.h
index 6fd50da8cad76..b1080c993ab8c 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.h
+++ b/clang/lib/AST/ByteCode/EvalEmitter.h
@@ -48,6 +48,11 @@ class EvalEmitter : public SourceMapper {
   /// function, i.e. the parameters of the function are available for use.
   bool interpretCall(const FunctionDecl *FD, const Expr *E);
 
+  std::optional<bool> interpretWithSubstitutions(const FunctionDecl *Callee,
+                                                 ArrayRef<const Expr *> Args,
+                                                 const Expr *This,
+                                                 const Expr *Condition);
+
   /// Clean up all resources.
   void cleanup();
 
@@ -67,6 +72,10 @@ class EvalEmitter : public SourceMapper {
   virtual bool visitDeclAndReturn(const VarDecl *VD, const Expr *Init,
                                   bool ConstantContext) = 0;
   virtual bool visitDtorCall(const VarDecl *VD, const APValue &Value) = 0;
+  virtual bool visitWithSubstitutions(const FunctionDecl *Callee,
+                                      ArrayRef<const Expr *> Args,
+                                      const Expr *This,
+                                      const Expr *Condition) = 0;
   virtual bool visitFunc(const FunctionDecl *F) = 0;
   virtual bool visit(const Expr *E) = 0;
   virtual bool emitBool(bool V, const Expr *E) = 0;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 6ac16c2b831d2..347a527855c9c 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -16303,6 +16303,7 @@ static bool determineEndOffset(EvalInfo &Info, 
SourceLocation ExprLoc,
 static std::optional<uint64_t>
 tryEvaluateBuiltinObjectSize(const Expr *E, unsigned Type, EvalInfo &Info,
                              bool IsDynamic = false) {
+
   // Determine the denoted object.
   LValue LVal;
   {
@@ -22323,6 +22324,16 @@ bool Expr::EvaluateWithSubstitution(APValue &Value, 
ASTContext &Ctx,
   EvalInfo Info(Ctx, Status, EvaluationMode::ConstantExpressionUnevaluated);
   Info.InConstantContext = true;
 
+  if (Info.EnableNewConstInterp) {
+    if (std::optional<bool> BoolResult =
+            Info.Ctx.getInterpContext().evaluateWithSubstitution(
+                Info, Callee, Args, This, this)) {
+      Value = APValue(APSInt(APInt(1, static_cast<uint64_t>(*BoolResult))));
+      return true;
+    }
+    return false;
+  }
+
   LValue ThisVal;
   const LValue *ThisPtr = nullptr;
   if (This) {
diff --git a/clang/test/AST/ByteCode/builtin-constant-p.cpp 
b/clang/test/AST/ByteCode/builtin-constant-p.cpp
index c6b074e403bed..1738ed1e9328d 100644
--- a/clang/test/AST/ByteCode/builtin-constant-p.cpp
+++ b/clang/test/AST/ByteCode/builtin-constant-p.cpp
@@ -141,12 +141,11 @@ void test17(void) {
                            // both-note {{use array indexing}}
 }
 
-/// FIXME
-static void foo(int i) 
__attribute__((__diagnose_if__(!__builtin_constant_p(i), "not constant", 
"error"))) // expected-note {{from}}
+static void foo(int i) 
__attribute__((__diagnose_if__(!__builtin_constant_p(i), "not constant", 
"error")))
 {
 }
 static void bar(int i) {
-  foo(15); // expected-error {{not constant}}
+  foo(15);
 }
 
 namespace Inactive {
diff --git a/clang/test/AST/ByteCode/enable_if.c 
b/clang/test/AST/ByteCode/enable_if.c
new file mode 100644
index 0000000000000..8148db2719449
--- /dev/null
+++ b/clang/test/AST/ByteCode/enable_if.c
@@ -0,0 +1,202 @@
+// RUN: %clang_cc1 -verify=ref,both      %s
+// RUN: %clang_cc1 -verify=expected,both %s 
-fexperimental-new-constant-interpreter
+
+// %clang_cc1 %s -DCODEGEN -emit-llvm -o - | FileCheck %s
+// %clang_cc1 %s -DCODEGEN -emit-llvm -o - 
-fexperimental-new-constant-interpreter | FileCheck %s
+
+/// This is the same file we have in test/Sema/, but there is one test that 
doesn't yet pass with the bytecode interpreter.
+/// TODO: Delete this file and add an appropriate RUN line to the file in 
test/Sema/ instead.
+///
+/// The problem is related to a wrongly computed value in the 
__builtin_object_size implementation.
+
+#define O_CREAT 0x100
+typedef int mode_t;
+typedef unsigned long size_t;
+
+enum { TRUE = 1 };
+
+int open(const char *pathname, int flags) __attribute__((enable_if(!(flags & 
O_CREAT), "must specify mode when using O_CREAT"))) 
__attribute__((overloadable));  // both-note{{candidate disabled: must specify 
mode when using O_CREAT}}
+int open(const char *pathname, int flags, mode_t mode) 
__attribute__((overloadable));  // both-note{{candidate function not viable: 
requires 3 arguments, but 2 were provided}}
+
+void test1(void) {
+#ifndef CODEGEN
+  open("path", O_CREAT);  // both-error{{no matching function for call to 
'open'}}
+#endif
+  open("path", O_CREAT, 0660);
+  open("path", 0);
+  open("path", 0, 0);
+}
+
+size_t __strnlen_chk(const char *s, size_t requested_amount, size_t s_len);
+
+size_t strnlen(const char *s, size_t maxlen)
+  __attribute__((overloadable))
+  __asm__("strnlen_real1");
+
+__attribute__((always_inline))
+inline size_t strnlen(const char *s, size_t maxlen)
+  __attribute__((overloadable))
+  __attribute__((enable_if(__builtin_object_size(s, 0) != -1,
+                           "chosen when target buffer size is known")))
+{
+  return __strnlen_chk(s, maxlen, __builtin_object_size(s, 0));
+}
+
+size_t strnlen(const char *s, size_t maxlen)
+  __attribute__((overloadable))
+  __attribute__((enable_if(__builtin_object_size(s, 0) != -1,
+                           "chosen when target buffer size is known")))
+  __attribute__((enable_if(maxlen <= __builtin_object_size(s, 0),
+                           "chosen when 'maxlen' is known to be less than or 
equal to the buffer size")))
+  __asm__("strnlen_real2");
+
+size_t strnlen(const char *s, size_t maxlen) // ref-note {{'strnlen' has been 
explicitly marked unavailable here}}
+  __attribute__((overloadable))
+  __attribute__((enable_if(__builtin_object_size(s, 0) != -1,
+                           "chosen when target buffer size is known")))
+  __attribute__((enable_if(maxlen > __builtin_object_size(s, 0),
+                           "chosen when 'maxlen' is larger than the buffer 
size")))
+  __attribute__((unavailable("'maxlen' is larger than the buffer size")));
+
+void test2(const char *s, int i) {
+// CHECK: define {{.*}}void @test2
+  const char c[123] = { 0 };
+  strnlen(s, i);
+// CHECK: call {{.*}}strnlen_real1
+  strnlen(s, 999);
+// CHECK: call {{.*}}strnlen_real1
+  strnlen(c, 1);
+// CHECK: call {{.*}}strnlen_real2
+  strnlen(c, i);
+// CHECK: call {{.*}}strnlen_chk
+#ifndef CODEGEN
+  strnlen(c, 999);  // ref-error{{'strnlen' is unavailable: 'maxlen' is larger 
than the buffer size}}
+#endif
+}
+
+int isdigit(int c) __attribute__((overloadable));
+int isdigit(int c) __attribute__((overloadable)) // both-note {{'isdigit' has 
been explicitly marked unavailable here}}
+  __attribute__((enable_if(c <= -1 || c > 255, "'c' must have the value of an 
unsigned char or EOF")))
+  __attribute__((unavailable("'c' must have the value of an unsigned char or 
EOF")));
+
+void test3(int c) {
+  isdigit(c); // both-warning{{ignoring return value of function declared with 
pure attribute}}
+  isdigit(10); // both-warning{{ignoring return value of function declared 
with pure attribute}}
+#ifndef CODEGEN
+  isdigit(-10);  // both-error{{'isdigit' is unavailable: 'c' must have the 
value of an unsigned char or EOF}}
+#endif
+}
+
+// Verify that the alternate spelling __enable_if__ works as well.
+int isdigit2(int c) __attribute__((overloadable));
+int isdigit2(int c) __attribute__((overloadable)) // both-note {{'isdigit2' 
has been explicitly marked unavailable here}}
+  __attribute__((__enable_if__(c <= -1 || c > 255, "'c' must have the value of 
an unsigned char or EOF")))
+  __attribute__((unavailable("'c' must have the value of an unsigned char or 
EOF")));
+
+void test4(int c) {
+  isdigit2(c);
+  isdigit2(10);
+#ifndef CODEGEN
+  isdigit2(-10);  // both-error{{'isdigit2' is unavailable: 'c' must have the 
value of an unsigned char or EOF}}
+#endif
+}
+
+void test5(void) {
+  int (*p1)(int) = &isdigit2;
+  int (*p2)(int) = isdigit2;
+  void *p3 = (void *)&isdigit2;
+  void *p4 = (void *)isdigit2;
+}
+
+#ifndef CODEGEN
+__attribute__((enable_if(n == 0, "chosen when 'n' is zero"))) void f1(int n); 
// both-error{{use of undeclared identifier 'n'}}
+
+int n __attribute__((enable_if(1, "always chosen"))); // 
both-warning{{'enable_if' attribute only applies to functions}}
+
+void f(int n) __attribute__((enable_if("chosen when 'n' is zero", n == 0)));  
// both-error{{expected string literal as argument of 'enable_if' attribute}}
+
+void f(int n) __attribute__((enable_if()));  // both-error{{'enable_if' 
attribute requires exactly 2 arguments}}
+
+void f(int n) __attribute__((enable_if(unresolvedid, "chosen when 
'unresolvedid' is non-zero")));  // both-error{{use of undeclared identifier 
'unresolvedid'}}
+
+int global;
+void f(int n) __attribute__((enable_if(global == 0, "chosen when 'global' is 
zero")));  // both-error{{'enable_if' attribute expression never produces a 
constant expression}} \
+                                                                               
         // both-note{{subexpression not valid in a constant expression}}
+
+enum { cst = 7 };
+void return_cst(void) __attribute__((overloadable)) 
__attribute__((enable_if(cst == 7, "chosen when 'cst' is 7")));
+void test_return_cst(void) { return_cst(); }
+
+void f2(void) __attribute__((overloadable)) __attribute__((enable_if(1, 
"always chosen")));       // #f2_1
+void f2(void) __attribute__((overloadable)) __attribute__((enable_if(0, "never 
chosen")));        // #f2_2
+void f2(void) __attribute__((overloadable)) __attribute__((enable_if(TRUE, 
"always chosen #2"))); // #f2_3
+void test6(void) {
+  void (*p1)(void) = &f2; // both-error {{initializing 'void (*)(void)' with 
an expression of incompatible type '<overloaded function type>'}} \
+                          // both-note@#f2_1 {{candidate function}} \
+                          // both-note@#f2_2 {{candidate function made 
ineligible by enable_if}} \
+                          // both-note@#f2_3 {{candidate function}}
+  void (*p2)(void) = f2; // both-error {{initializing 'void (*)(void)' with an 
expression of incompatible type '<overloaded function type>'}} \
+                         // both-note@#f2_1 {{candidate function}} \
+                         // both-note@#f2_2 {{candidate function made 
ineligible by enable_if}} \
+                         // both-note@#f2_3 {{candidate function}}
+  void *p3 = (void*)&f2; // both-error {{address of overloaded function 'f2' 
is ambiguous}} \
+                         // both-note@#f2_1 {{candidate function}} \
+                         // both-note@#f2_2 {{candidate function made 
ineligible by enable_if}} \
+                         // both-note@#f2_3 {{candidate function}}
+  void *p4 = (void*)f2; // both-error {{address of overloaded function 'f2' is 
ambiguous}} \
+                        // both-note@#f2_1 {{candidate function}} \
+                        // both-note@#f2_2 {{candidate function made 
ineligible by enable_if}} \
+                        // both-note@#f2_3 {{candidate function}}
+}
+
+void f3(int m) __attribute__((overloadable)) __attribute__((enable_if(m >= 0, 
"positive"))); // #f3_1
+void f3(int m) __attribute__((overloadable)) __attribute__((enable_if(m < 0, 
"negative")));  // #f3_2
+void test7(void) {
+  void (*p1)(int) = &f3; // both-error {{initializing 'void (*)(int)' with an 
expression of incompatible type '<overloaded function type>'}} \
+                         // both-note@#f3_1 {{candidate function made 
ineligible by enable_if}} \
+                         // both-note@#f3_2 {{candidate function made 
ineligible by enable_if}}
+  void (*p2)(int) = f3; // both-error {{initializing 'void (*)(int)' with an 
expression of incompatible type '<overloaded function type>'}} \
+                        // both-note@#f3_1 {{candidate function made 
ineligible by enable_if}} \
+                        // both-note@#f3_2 {{candidate function made 
ineligible by enable_if}}
+  void *p3 = (void*)&f3; // both-error {{address of overloaded function 'f3' 
does not match required type 'void'}} \
+                         // both-note@#f3_1 {{candidate function made 
ineligible by enable_if}} \
+                         // both-note@#f3_2 {{candidate function made 
ineligible by enable_if}}
+  void *p4 = (void*)f3; // both-error {{address of overloaded function 'f3' 
does not match required type 'void'}} \
+                        // both-note@#f3_1 {{candidate function made 
ineligible by enable_if}} \
+                        // both-note@#f3_2 {{candidate function made 
ineligible by enable_if}}
+}
+
+void f4(int m) __attribute__((enable_if(0, "")));
+void test8(void) {
+  void (*p1)(int) = &f4; // both-error{{cannot take address of function 'f4' 
because it has one or more non-tautological enable_if conditions}}
+  void (*p2)(int) = f4; // both-error{{cannot take address of function 'f4' 
because it has one or more non-tautological enable_if conditions}}
+}
+
+void regular_enable_if(int a) __attribute__((enable_if(a, ""))); // both-note 
3{{declared here}}
+void PR27122_ext(void) {
+  regular_enable_if(0, 2); // both-error{{too many arguments}}
+  regular_enable_if(1, 2); // both-error{{too many arguments}}
+  regular_enable_if(); // both-error{{too few arguments}}
+}
+
+// We had a bug where we'd crash upon trying to evaluate varargs.
+void variadic_enable_if(int a, ...) __attribute__((enable_if(a, ""))); // 
both-note 6 {{disabled}}
+void variadic_test(void) {
+  variadic_enable_if(1);
+  variadic_enable_if(1, 2);
+  variadic_enable_if(1, "c", 3);
+
+  variadic_enable_if(0); // both-error{{no matching}}
+  variadic_enable_if(0, 2); // both-error{{no matching}}
+  variadic_enable_if(0, "c", 3); // both-error{{no matching}}
+
+  int m;
+  variadic_enable_if(1);
+  variadic_enable_if(1, m);
+  variadic_enable_if(1, m, "c");
+
+  variadic_enable_if(0); // both-error{{no matching}}
+  variadic_enable_if(0, m); // both-error{{no matching}}
+  variadic_enable_if(0, m, 3); // both-error{{no matching}}
+}
+#endif
diff --git 
a/clang/test/SemaCXX/enable_if-nested-call-with-valuedependent-param.cpp 
b/clang/test/SemaCXX/enable_if-nested-call-with-valuedependent-param.cpp
index 998f2ccf92534..31a49e9ae3d91 100644
--- a/clang/test/SemaCXX/enable_if-nested-call-with-valuedependent-param.cpp
+++ b/clang/test/SemaCXX/enable_if-nested-call-with-valuedependent-param.cpp
@@ -1,4 +1,5 @@
 // RUN: %clang_cc1 -fsyntax-only %s -std=c++14
+// RUN: %clang_cc1 -fsyntax-only %s -std=c++14 
-fexperimental-new-constant-interpreter
 
 // Checks that Clang doesn't crash/assert on the nested call to "kaboom"
 // in "bar()".
diff --git a/clang/test/SemaCXX/enable_if.cpp b/clang/test/SemaCXX/enable_if.cpp
index 9b35bf2ac0c8d..a34b87064b49d 100644
--- a/clang/test/SemaCXX/enable_if.cpp
+++ b/clang/test/SemaCXX/enable_if.cpp
@@ -1,5 +1,7 @@
 // RUN: %clang_cc1 -std=c++11 -verify %s
+// RUN: %clang_cc1 -std=c++11 -verify %s 
-fexperimental-new-constant-interpreter
 // RUN: %clang_cc1 -std=c++2a -verify %s
+// RUN: %clang_cc1 -std=c++2a -verify %s 
-fexperimental-new-constant-interpreter
 
 typedef int (*fp)(int);
 int surrogate(int);

>From d255d07325af417af12315bb2584471c9abf8f2a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
Date: Fri, 19 Jun 2026 10:50:43 +0200
Subject: [PATCH 2/3] ConstDynamicRecursiveASTVisitor

---
 clang/lib/AST/ByteCode/Compiler.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp 
b/clang/lib/AST/ByteCode/Compiler.cpp
index fa2a68e6edd19..e354f882e1376 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -5416,7 +5416,7 @@ bool Compiler<Emitter>::visitDtorCall(const VarDecl *VD, 
const APValue &Value) {
   return this->emitDestructionPop(D, VD);
 }
 
-class ParamFinder : public DynamicRecursiveASTVisitorBase<true> {
+class ParamFinder : public ConstDynamicRecursiveASTVisitor {
 public:
   llvm::SmallPtrSet<const ParmVarDecl *, 1> FoundParams;
   explicit ParamFinder() {}

>From 0fddba62f676da5fd10acfb718d01e3cdcb93911 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
Date: Fri, 19 Jun 2026 12:32:27 +0200
Subject: [PATCH 3/3] Missing include?

---
 clang/lib/AST/ByteCode/Compiler.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp 
b/clang/lib/AST/ByteCode/Compiler.cpp
index e354f882e1376..31452b19633f8 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -16,6 +16,7 @@
 #include "PrimType.h"
 #include "Program.h"
 #include "clang/AST/Attr.h"
+#include "clang/AST/DynamicRecursiveASTVisitor.h"
 #include "llvm/Support/SaveAndRestore.h"
 
 using namespace clang;

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

Reply via email to