[clang] [clang][Interp] Handle variadic functions (PR #67814)

Timm Baeder via cfe-commits cfe-commits at lists.llvm.org
Sat Sep 30 11:07:59 PDT 2023


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

>From 64aae1cdf960f3edb34f1cf82ae3e66eb692247a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Fri, 29 Sep 2023 16:43:59 +0200
Subject: [PATCH] [clang][Interp] Handle variadic functions

Similarly to the code we already had for builtin functions, we need to
check the call expression for the arguments passed.
---
 clang/lib/AST/Interp/Function.cpp   |  2 +-
 clang/lib/AST/Interp/Function.h     |  3 ++
 clang/lib/AST/Interp/Interp.cpp     | 49 +++++++++++++++++++++++------
 clang/lib/AST/Interp/Interp.h       | 18 +++--------
 clang/test/AST/Interp/functions.cpp | 21 +++++++++++++
 5 files changed, 69 insertions(+), 24 deletions(-)

diff --git a/clang/lib/AST/Interp/Function.cpp b/clang/lib/AST/Interp/Function.cpp
index 0b7cfc4e28883f0..357aff7fe6229b9 100644
--- a/clang/lib/AST/Interp/Function.cpp
+++ b/clang/lib/AST/Interp/Function.cpp
@@ -24,7 +24,7 @@ Function::Function(Program &P, const FunctionDecl *F, unsigned ArgSize,
     : P(P), Loc(F->getBeginLoc()), F(F), ArgSize(ArgSize),
       ParamTypes(std::move(ParamTypes)), Params(std::move(Params)),
       ParamOffsets(std::move(ParamOffsets)), HasThisPointer(HasThisPointer),
-      HasRVO(HasRVO) {}
+      HasRVO(HasRVO), Variadic(F->isVariadic()) {}
 
 Function::ParamDescriptor Function::getParamDescriptor(unsigned Offset) const {
   auto It = Params.find(Offset);
diff --git a/clang/lib/AST/Interp/Function.h b/clang/lib/AST/Interp/Function.h
index 0bae314e97701d9..15bdd39dfde9d99 100644
--- a/clang/lib/AST/Interp/Function.h
+++ b/clang/lib/AST/Interp/Function.h
@@ -172,6 +172,8 @@ class Function final {
   /// Checks if the function is defined.
   bool isDefined() const { return Defined; }
 
+  bool isVariadic() const { return Variadic; }
+
   unsigned getBuiltinID() const { return F->getBuiltinID(); }
 
   bool isBuiltin() const { return F->getBuiltinID() != 0; }
@@ -250,6 +252,7 @@ class Function final {
   /// If we've already compiled the function's body.
   bool HasBody = false;
   bool Defined = false;
+  bool Variadic = false;
 
 public:
   /// Dumps the disassembled bytecode to \c llvm::errs().
diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp
index e1951574edb6288..639ceb140e99de3 100644
--- a/clang/lib/AST/Interp/Interp.cpp
+++ b/clang/lib/AST/Interp/Interp.cpp
@@ -121,18 +121,47 @@ static bool CheckGlobal(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
 
 namespace clang {
 namespace interp {
+static void popArg(InterpState &S, const Expr *Arg) {
+  PrimType Ty = S.getContext().classify(Arg->getType()).value_or(PT_Ptr);
+  TYPE_SWITCH(Ty, S.Stk.discard<T>());
+}
+
+void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC) {
+  assert(S.Current);
+  const Function *CurFunc = S.Current->getFunction();
+  assert(CurFunc);
+
+  // Certain builtin functions are declared as func-name(...), so the
+  // parameters are checked in Sema and only available through the CallExpr.
+  // The interp::Function we create for them has 0 parameters, so we need to
+  // remove them from the stack by checking the CallExpr.
+  // FIXME: This is potentially just a special case and could be handled more
+  // generally with the code just below?
+  if (CurFunc->needsRuntimeArgPop(S.getCtx())) {
+    const CallExpr *CE = cast<CallExpr>(S.Current->getExpr(OpPC));
+    for (int32_t I = CE->getNumArgs() - 1; I >= 0; --I) {
+      popArg(S, CE->getArg(I));
+    }
+    return;
+  }
 
-bool popBuiltinArgs(InterpState &S, CodePtr OpPC) {
-  assert(S.Current && S.Current->getFunction()->needsRuntimeArgPop(S.getCtx()));
-  const Expr *E = S.Current->getExpr(OpPC);
-  assert(isa<CallExpr>(E));
-  const CallExpr *CE = cast<CallExpr>(E);
-  for (int32_t I = CE->getNumArgs() - 1; I >= 0; --I) {
-    const Expr *A = CE->getArg(I);
-    PrimType Ty = S.getContext().classify(A->getType()).value_or(PT_Ptr);
-    TYPE_SWITCH(Ty, S.Stk.discard<T>());
+  if (S.Current->Caller && CurFunc->isVariadic()) {
+    // CallExpr we're look for is at the return PC of the current function, i.e.
+    // in the caller.
+    // This code path should be executed very rarely.
+    const CallExpr *CE =
+        cast<CallExpr>(S.Current->Caller->getExpr(S.Current->getRetPC()));
+    unsigned FixedParams = CurFunc->getNumParams();
+    int32_t ArgsToPop = CE->getNumArgs() - FixedParams;
+    assert(ArgsToPop >= 0);
+    for (int32_t I = ArgsToPop - 1; I >= 0; --I) {
+      const Expr *A = CE->getArg(FixedParams + I);
+      popArg(S, A);
+    }
   }
-  return true;
+  // And in any case, remove the fixed parameters (the non-variadic ones)
+  // at the end.
+  S.Current->popArgs();
 }
 
 bool CheckExtern(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h
index dd37150b63f6db0..d67b8b978031aac 100644
--- a/clang/lib/AST/Interp/Interp.h
+++ b/clang/lib/AST/Interp/Interp.h
@@ -200,8 +200,7 @@ enum class ArithOp { Add, Sub };
 // Returning values
 //===----------------------------------------------------------------------===//
 
-/// Pop arguments of builtins defined as func-name(...).
-bool popBuiltinArgs(InterpState &S, CodePtr OpPC);
+void cleanupAfterFunctionCall(InterpState &S, CodePtr OpPC);
 
 template <PrimType Name, class T = typename PrimConv<Name>::T>
 bool Ret(InterpState &S, CodePtr &PC, APValue &Result) {
@@ -220,16 +219,8 @@ bool Ret(InterpState &S, CodePtr &PC, APValue &Result) {
 
   assert(S.Current);
   assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame");
-  if (!S.checkingPotentialConstantExpression() || S.Current->Caller) {
-    // Certain builtin functions are declared as func-name(...), so the
-    // parameters are checked in Sema and only available through the CallExpr.
-    // The interp::Function we create for them has 0 parameters, so we need to
-    // remove them from the stack by checking the CallExpr.
-    if (S.Current->getFunction()->needsRuntimeArgPop(S.getCtx()))
-      popBuiltinArgs(S, PC);
-    else
-      S.Current->popArgs();
-  }
+  if (!S.checkingPotentialConstantExpression() || S.Current->Caller)
+    cleanupAfterFunctionCall(S, PC);
 
   if (InterpFrame *Caller = S.Current->Caller) {
     PC = S.Current->getRetPC();
@@ -247,8 +238,9 @@ bool Ret(InterpState &S, CodePtr &PC, APValue &Result) {
 
 inline bool RetVoid(InterpState &S, CodePtr &PC, APValue &Result) {
   assert(S.Current->getFrameOffset() == S.Stk.size() && "Invalid frame");
+
   if (!S.checkingPotentialConstantExpression() || S.Current->Caller)
-    S.Current->popArgs();
+    cleanupAfterFunctionCall(S, PC);
 
   if (InterpFrame *Caller = S.Current->Caller) {
     PC = S.Current->getRetPC();
diff --git a/clang/test/AST/Interp/functions.cpp b/clang/test/AST/Interp/functions.cpp
index 5cdecbff1e9d3d4..f28a525e2b4f008 100644
--- a/clang/test/AST/Interp/functions.cpp
+++ b/clang/test/AST/Interp/functions.cpp
@@ -343,3 +343,24 @@ namespace TemplateUndefined {
   constexpr int l = consume(0);
   static_assert(l == 0, "");
 }
+
+namespace Variadic {
+  struct S { int a; bool b; };
+
+  constexpr void variadic_function(int a, ...) {}
+  constexpr int f1() {
+    variadic_function(1, S{'a', false});
+    return 1;
+  }
+  static_assert(f1() == 1, "");
+
+  constexpr int variadic_function2(...) {
+    return 12;
+  }
+  static_assert(variadic_function2() == 12, "");
+  static_assert(variadic_function2(1, 2, 3, 4, 5) == 12, "");
+  static_assert(variadic_function2(1, variadic_function2()) == 12, "");
+
+  constexpr int (*VFP)(...) = variadic_function2;
+  static_assert(VFP() == 12, "");
+}



More information about the cfe-commits mailing list