[clang] Add `EvalASTMutator` interface (PR #115168)

Vlad Serebrennikov via cfe-commits cfe-commits at lists.llvm.org
Wed Nov 6 07:15:06 PST 2024


https://github.com/Endilll updated https://github.com/llvm/llvm-project/pull/115168

>From 5ca48e03412b1b8e9253f13356b9cc957f6fd9e5 Mon Sep 17 00:00:00 2001
From: Vlad Serebrennikov <serebrennikov.vladislav at gmail.com>
Date: Wed, 6 Nov 2024 17:58:43 +0300
Subject: [PATCH 1/3] Add EvalASTMutator interface with
 `InstantiateFunctionDefinition` function

---
 clang/include/clang/AST/ASTContext.h          |  2 +-
 clang/include/clang/AST/Decl.h                | 22 ++++++++++---
 clang/include/clang/AST/Expr.h                |  3 +-
 clang/include/clang/Sema/Sema.h               | 17 ++++++++++
 clang/lib/AST/ASTContext.cpp                  |  4 +--
 clang/lib/AST/Decl.cpp                        | 16 +++++-----
 clang/lib/AST/ExprConstant.cpp                | 24 +++++++++++---
 clang/lib/Sema/Sema.cpp                       | 11 ++++++-
 clang/lib/Sema/SemaDecl.cpp                   |  5 +--
 .../constexpr-function-instantiation.cpp      | 31 +++++++++++++++++++
 10 files changed, 113 insertions(+), 22 deletions(-)
 create mode 100644 clang/test/SemaCXX/constexpr-function-instantiation.cpp

diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index a4d36f2eacd5d1..d143591de9f2cd 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3258,7 +3258,7 @@ class ASTContext : public RefCountedBase<ASTContext> {
   ///
   /// \returns true if the function/var must be CodeGen'ed/deserialized even if
   /// it is not used.
-  bool DeclMustBeEmitted(const Decl *D);
+  bool DeclMustBeEmitted(const Decl *D, EvalASTMutator *ASTMutator = nullptr);
 
   /// Visits all versions of a multiversioned function with the passed
   /// predicate.
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 7ff35d73df5997..89a2833c82194d 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -78,6 +78,18 @@ class UnresolvedSetImpl;
 class VarTemplateDecl;
 enum class ImplicitParamKind;
 
+/// Interface that allows constant evaluator to mutate AST.
+/// When constant evaluation is triggered by Sema, it can supply a proper
+/// implementation of this interface.
+struct EvalASTMutator {
+  virtual ~EvalASTMutator() = default;
+
+  virtual void
+  InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
+                                FunctionDecl *Function, bool Recursive,
+                                bool DefinitionRequired, bool AtEndOfTU) = 0;
+};
+
 /// The top declaration context.
 class TranslationUnitDecl : public Decl,
                             public DeclContext,
@@ -1355,11 +1367,12 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
   /// Attempt to evaluate the value of the initializer attached to this
   /// declaration, and produce notes explaining why it cannot be evaluated.
   /// Returns a pointer to the value if evaluation succeeded, 0 otherwise.
-  APValue *evaluateValue() const;
+  APValue *evaluateValue(EvalASTMutator *ASTMutator = nullptr) const;
 
 private:
   APValue *evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes,
-                             bool IsConstantInitialization) const;
+                             bool IsConstantInitialization,
+                             EvalASTMutator *ASTMutator = nullptr) const;
 
 public:
   /// Return the already-evaluated value of this variable's
@@ -1391,8 +1404,9 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
   /// Evaluate the initializer of this variable to determine whether it's a
   /// constant initializer. Should only be called once, after completing the
   /// definition of the variable.
-  bool checkForConstantInitialization(
-      SmallVectorImpl<PartialDiagnosticAt> &Notes) const;
+  bool
+  checkForConstantInitialization(SmallVectorImpl<PartialDiagnosticAt> &Notes,
+                                 EvalASTMutator *ASTMutator = nullptr) const;
 
   void setInitStyle(InitializationStyle Style) {
     VarDeclBits.InitStyle = Style;
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 466c65a9685ad3..ccf7fd226b5a8f 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -734,7 +734,8 @@ class Expr : public ValueStmt {
   bool EvaluateAsInitializer(APValue &Result, const ASTContext &Ctx,
                              const VarDecl *VD,
                              SmallVectorImpl<PartialDiagnosticAt> &Notes,
-                             bool IsConstantInitializer) const;
+                             bool IsConstantInitializer,
+                             EvalASTMutator *ASTMutator = nullptr) const;
 
   /// EvaluateWithSubstitution - Evaluate an expression as if from the context
   /// of a call to the given function with the given arguments, inside an
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 93d98e1cbb9c81..1c0c6949573335 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -153,6 +153,7 @@ enum class OverloadCandidateParamOrder : char;
 enum OverloadCandidateRewriteKind : unsigned;
 class OverloadCandidateSet;
 class Preprocessor;
+class Sema;
 class SemaAMDGPU;
 class SemaARM;
 class SemaAVR;
@@ -352,6 +353,15 @@ struct SkipBodyInfo {
   NamedDecl *New = nullptr;
 };
 
+/// Implementation of EvalASTMutator interface that enables constant evaluator
+/// to modify AST, e.g. to instantiate templates.
+struct SemaASTMutator : EvalASTMutator {
+  Sema &SemaRef;
+  SemaASTMutator(Sema &SemaRef) void InstantiateFunctionDefinition(
+      SourceLocation PointOfInstantiation, FunctionDecl *Function,
+      bool Recursive, bool DefinitionRequired, bool AtEndOfTU) override;
+};
+
 /// Describes the result of template argument deduction.
 ///
 /// The TemplateDeductionResult enumeration describes the result of
@@ -1042,6 +1052,9 @@ class Sema final : public SemaBase {
   /// CurContext - This is the current declaration context of parsing.
   DeclContext *CurContext;
 
+  /// Get a Sema implementation of EvalASTMutator interface.
+  SemaASTMutator *getASTMutator() { return &ASTMutator; }
+
   SemaAMDGPU &AMDGPU() {
     assert(AMDGPUPtr);
     return *AMDGPUPtr;
@@ -1199,6 +1212,10 @@ class Sema final : public SemaBase {
 
   mutable IdentifierInfo *Ident_super;
 
+  /// EvalASTMutator implementation that can be passed to constant evaluator
+  /// to enable it to do AST mutations, e.g. template instantiation.
+  SemaASTMutator ASTMutator;
+
   std::unique_ptr<SemaAMDGPU> AMDGPUPtr;
   std::unique_ptr<SemaARM> ARMPtr;
   std::unique_ptr<SemaAVR> AVRPtr;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 69892bda42b256..0aaf23af28d3d6 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -12522,7 +12522,7 @@ GVALinkage ASTContext::GetGVALinkageForVariable(const VarDecl *VD) const {
              basicGVALinkageForVariable(*this, VD)));
 }
 
-bool ASTContext::DeclMustBeEmitted(const Decl *D) {
+bool ASTContext::DeclMustBeEmitted(const Decl *D, EvalASTMutator *ASTMutator) {
   if (const auto *VD = dyn_cast<VarDecl>(D)) {
     if (!VD->isFileVarDecl())
       return false;
@@ -12627,7 +12627,7 @@ bool ASTContext::DeclMustBeEmitted(const Decl *D) {
   // Variables that have initialization with side-effects are required.
   if (VD->getInit() && VD->getInit()->HasSideEffects(*this) &&
       // We can get a value-dependent initializer during error recovery.
-      (VD->getInit()->isValueDependent() || !VD->evaluateValue()))
+      (VD->getInit()->isValueDependent() || !VD->evaluateValue(ASTMutator)))
     return true;
 
   // Likewise, variables with tuple-like bindings are required if their
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 86913763ef9ff5..f49ef66cb43aea 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2546,13 +2546,14 @@ EvaluatedStmt *VarDecl::getEvaluatedStmt() const {
   return Init.dyn_cast<EvaluatedStmt *>();
 }
 
-APValue *VarDecl::evaluateValue() const {
+APValue *VarDecl::evaluateValue(EvalASTMutator *ASTMutator) const {
   SmallVector<PartialDiagnosticAt, 8> Notes;
-  return evaluateValueImpl(Notes, hasConstantInitialization());
+  return evaluateValueImpl(Notes, hasConstantInitialization(), ASTMutator);
 }
 
 APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes,
-                                    bool IsConstantInitialization) const {
+                                    bool IsConstantInitialization,
+                                    EvalASTMutator *ASTMutator) const {
   EvaluatedStmt *Eval = ensureEvaluatedStmt();
 
   const auto *Init = getInit();
@@ -2572,8 +2573,8 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes,
   Eval->IsEvaluating = true;
 
   ASTContext &Ctx = getASTContext();
-  bool Result = Init->EvaluateAsInitializer(Eval->Evaluated, Ctx, this, Notes,
-                                            IsConstantInitialization);
+  bool Result = Init->EvaluateAsInitializer(
+      Eval->Evaluated, Ctx, this, Notes, IsConstantInitialization, ASTMutator);
 
   // In C++, or in C23 if we're initialising a 'constexpr' variable, this isn't
   // a constant initializer if we produced notes. In that case, we can't keep
@@ -2636,7 +2637,8 @@ bool VarDecl::hasConstantInitialization() const {
 }
 
 bool VarDecl::checkForConstantInitialization(
-    SmallVectorImpl<PartialDiagnosticAt> &Notes) const {
+    SmallVectorImpl<PartialDiagnosticAt> &Notes,
+    EvalASTMutator *ASTMutator) const {
   EvaluatedStmt *Eval = ensureEvaluatedStmt();
   // If we ask for the value before we know whether we have a constant
   // initializer, we can compute the wrong value (for example, due to
@@ -2651,7 +2653,7 @@ bool VarDecl::checkForConstantInitialization(
 
   // Evaluate the initializer to check whether it's a constant expression.
   Eval->HasConstantInitialization =
-      evaluateValueImpl(Notes, true) && Notes.empty();
+      evaluateValueImpl(Notes, true, ASTMutator) && Notes.empty();
 
   // If evaluation as a constant initializer failed, allow re-evaluation as a
   // non-constant initializer if we later find we want the value.
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d664c503655ba6..b9a075154b6d6f 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -1004,6 +1004,11 @@ namespace {
       EM_IgnoreSideEffects,
     } EvalMode;
 
+    /// Implementation of an interface for AST mutation.
+    /// Basically something backed by Sema,
+    /// or nullptr if Sema is not available.
+    EvalASTMutator *ASTMutator;
+
     /// Are we checking whether the expression is a potential constant
     /// expression?
     bool checkingPotentialConstantExpression() const override  {
@@ -1017,7 +1022,8 @@ namespace {
       return CheckingForUndefinedBehavior;
     }
 
-    EvalInfo(const ASTContext &C, Expr::EvalStatus &S, EvaluationMode Mode)
+    EvalInfo(const ASTContext &C, Expr::EvalStatus &S, EvaluationMode Mode,
+             EvalASTMutator *ASTMutator = nullptr)
         : Ctx(const_cast<ASTContext &>(C)), EvalStatus(S), CurrentCall(nullptr),
           CallStackDepth(0), NextCallIndex(1),
           StepsLeft(C.getLangOpts().ConstexprStepLimit),
@@ -1027,13 +1033,15 @@ namespace {
                       /*CallExpr=*/nullptr, CallRef()),
           EvaluatingDecl((const ValueDecl *)nullptr),
           EvaluatingDeclValue(nullptr), HasActiveDiagnostic(false),
-          HasFoldFailureDiagnostic(false), EvalMode(Mode) {}
+          HasFoldFailureDiagnostic(false), EvalMode(Mode),
+          ASTMutator(ASTMutator) {}
 
     ~EvalInfo() {
       discardCleanups();
     }
 
     ASTContext &getASTContext() const override { return Ctx; }
+    EvalASTMutator *getASTMutator() const { return ASTMutator; }
 
     void setEvaluatingDecl(APValue::LValueBase Base, APValue &Value,
                            EvaluatingDeclKind EDK = EvaluatingDeclKind::Ctor) {
@@ -8328,6 +8336,12 @@ class ExprEvaluatorBase
 
     const FunctionDecl *Definition = nullptr;
     Stmt *Body = FD->getBody(Definition);
+    if (!Definition && FD->getTemplateInstantiationPattern()) {
+      Info.getASTMutator()->InstantiateFunctionDefinition(
+          E->getExprLoc(), const_cast<FunctionDecl *>(FD),
+          /*Recursive=*/true, /*DefinitionRequired=*/true, /*AtEndOfTU=*/false);
+      Body = FD->getBody(Definition);
+    }
 
     if (!CheckConstexprFunction(Info, E->getExprLoc(), FD, Definition, Body) ||
         !HandleFunctionCall(E->getExprLoc(), Definition, This, E, Args, Call,
@@ -16669,7 +16683,8 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, const ASTContext &Ctx,
 bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
                                  const VarDecl *VD,
                                  SmallVectorImpl<PartialDiagnosticAt> &Notes,
-                                 bool IsConstantInitialization) const {
+                                 bool IsConstantInitialization,
+                                 EvalASTMutator *ASTMutator) const {
   assert(!isValueDependent() &&
          "Expression evaluator can't be called on a dependent expression.");
 
@@ -16687,7 +16702,8 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
                 (IsConstantInitialization &&
                  (Ctx.getLangOpts().CPlusPlus || Ctx.getLangOpts().C23))
                     ? EvalInfo::EM_ConstantExpression
-                    : EvalInfo::EM_ConstantFold);
+                    : EvalInfo::EM_ConstantFold,
+                ASTMutator);
   Info.setEvaluatingDecl(VD, Value);
   Info.InConstantContext = IsConstantInitialization;
 
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 2b51765e80864a..328a8f7912a8ea 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -208,6 +208,8 @@ class SemaPPCallbacks : public PPCallbacks {
 } // end namespace sema
 } // end namespace clang
 
+SemaASTMutator::SemaASTMutator(Sema &SemaRef) : SemaRef(SemaRef) {}
+
 const unsigned Sema::MaxAlignmentExponent;
 const uint64_t Sema::MaximumAlignment;
 
@@ -221,7 +223,7 @@ Sema::Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer,
       LateTemplateParser(nullptr), LateTemplateParserCleanup(nullptr),
       OpaqueParser(nullptr), CurContext(nullptr), ExternalSource(nullptr),
       StackHandler(Diags), CurScope(nullptr), Ident_super(nullptr),
-      AMDGPUPtr(std::make_unique<SemaAMDGPU>(*this)),
+      ASTMutator(*this), AMDGPUPtr(std::make_unique<SemaAMDGPU>(*this)),
       ARMPtr(std::make_unique<SemaARM>(*this)),
       AVRPtr(std::make_unique<SemaAVR>(*this)),
       BPFPtr(std::make_unique<SemaBPF>(*this)),
@@ -2798,3 +2800,10 @@ Attr *Sema::CreateAnnotationAttr(const ParsedAttr &AL) {
 
   return CreateAnnotationAttr(AL, Str, Args);
 }
+
+void SemaASTMutator::InstantiateFunctionDefinition(
+    SourceLocation PointOfInstantiation, FunctionDecl *Function, bool Recursive,
+    bool DefinitionRequired, bool AtEndOfTU) {
+  SemaRef.InstantiateFunctionDefinition(
+      PointOfInstantiation, Function, Recursive, DefinitionRequired, AtEndOfTU);
+}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index f8e5f3c6d309d6..ea6ca66f8c69e9 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -14441,7 +14441,8 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
       }
     } else {
       // Evaluate the initializer to see if it's a constant initializer.
-      HasConstInit = var->checkForConstantInitialization(Notes);
+      HasConstInit =
+          var->checkForConstantInitialization(Notes, getASTMutator());
     }
 
     if (HasConstInit) {
@@ -14549,7 +14550,7 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
 
   // If this variable must be emitted, add it as an initializer for the current
   // module.
-  if (Context.DeclMustBeEmitted(var) && !ModuleScopes.empty())
+  if (Context.DeclMustBeEmitted(var, getASTMutator()) && !ModuleScopes.empty())
     Context.addModuleInitializer(ModuleScopes.back().Module, var);
 
   // Build the bindings if this is a structured binding declaration.
diff --git a/clang/test/SemaCXX/constexpr-function-instantiation.cpp b/clang/test/SemaCXX/constexpr-function-instantiation.cpp
new file mode 100644
index 00000000000000..43c2ced137474d
--- /dev/null
+++ b/clang/test/SemaCXX/constexpr-function-instantiation.cpp
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++2c -fsyntax-only -verify %s
+
+namespace GH73232 {
+namespace ex1 {
+template <typename T>
+constexpr void g(T);
+
+constexpr int f() {
+    g(0);
+    return 0;
+}
+
+template <typename T> 
+constexpr void g(T) {}
+
+constexpr auto z = f();
+} // namespace ex1
+
+namespace ex2 {
+template <typename> constexpr static void fromType();
+
+void registerConverter() { fromType<int>(); }
+template <typename> struct QMetaTypeId  {};
+template <typename T> constexpr void fromType() { 
+  (void)QMetaTypeId<T>{};
+} // #1
+template <> struct QMetaTypeId<int> {}; // #20428 
+} // namespace ex2
+} // namespace GH73232
\ No newline at end of file

>From 57de67a735c40557ba203ad1c3e46c6791dff706 Mon Sep 17 00:00:00 2001
From: Vlad Serebrennikov <serebrennikov.vladislav at gmail.com>
Date: Wed, 6 Nov 2024 18:00:39 +0300
Subject: [PATCH 2/3] Add missing newline

---
 clang/test/SemaCXX/constexpr-function-instantiation.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/test/SemaCXX/constexpr-function-instantiation.cpp b/clang/test/SemaCXX/constexpr-function-instantiation.cpp
index 43c2ced137474d..6c7e43f85cf6f9 100644
--- a/clang/test/SemaCXX/constexpr-function-instantiation.cpp
+++ b/clang/test/SemaCXX/constexpr-function-instantiation.cpp
@@ -28,4 +28,4 @@ template <typename T> constexpr void fromType() {
 } // #1
 template <> struct QMetaTypeId<int> {}; // #20428 
 } // namespace ex2
-} // namespace GH73232
\ No newline at end of file
+} // namespace GH73232

>From 548298c8e2d1030b5d4edb8b548609b55aeba7f0 Mon Sep 17 00:00:00 2001
From: Vlad Serebrennikov <serebrennikov.vladislav at gmail.com>
Date: Wed, 6 Nov 2024 18:14:45 +0300
Subject: [PATCH 3/3] Add missing semicolon

---
 clang/include/clang/Sema/Sema.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 1c0c6949573335..61114b7107c650 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -357,7 +357,8 @@ struct SkipBodyInfo {
 /// to modify AST, e.g. to instantiate templates.
 struct SemaASTMutator : EvalASTMutator {
   Sema &SemaRef;
-  SemaASTMutator(Sema &SemaRef) void InstantiateFunctionDefinition(
+  SemaASTMutator(Sema &SemaRef);
+  void InstantiateFunctionDefinition(
       SourceLocation PointOfInstantiation, FunctionDecl *Function,
       bool Recursive, bool DefinitionRequired, bool AtEndOfTU) override;
 };



More information about the cfe-commits mailing list