Skip to content

Enable AST mutation in the constant evaluator #115168

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
42 changes: 42 additions & 0 deletions clang/include/clang/AST/ASTContext.h
Original file line number Diff line number Diff line change
@@ -182,6 +182,22 @@ struct TypeInfoChars {
}
};

/// Interface that allows constant evaluator to call Sema
/// and mutate the AST.
struct SemaProxy {
virtual ~SemaProxy() = default;

bool getIgnoreSideEffectsOnAST();
void setIgnoreSideEffectsOnAST(bool ignore = true);

virtual void
InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
FunctionDecl *Function) = 0;

private:
bool IgnoreSideEffectsOnAST = false;
};

/// Holds long-lived AST nodes (such as types and decls) that can be
/// referred to throughout the semantic analysis of a file.
class ASTContext : public RefCountedBase<ASTContext> {
@@ -671,7 +687,19 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// Keeps track of the deallocated DeclListNodes for future reuse.
DeclListNode *ListNodeFreeList = nullptr;

/// Implementation of the interface that Sema provides during its
/// construction.
std::unique_ptr<SemaProxy> SemaProxyPtr;

public:
/// Returns an object that is capable of modifying AST,
/// or nullptr if it's not available. The latter happens when
/// Sema is not available.
SemaProxy &getSemaProxy() const {
assert(SemaProxyPtr);
return *SemaProxyPtr;
}

IdentifierTable &Idents;
SelectorTable &Selectors;
Builtin::Context &BuiltinInfo;
@@ -3509,6 +3537,12 @@ OPT_LIST(V)

void ReleaseDeclContextMaps();

/// This is a function that is implemented in the Sema layer,
/// that needs friendship to initialize SemaProxy without this capability
/// being exposed in the public interface of ASTContext.
friend void injectSemaProxyIntoASTContext(ASTContext &,
std::unique_ptr<SemaProxy>);

public:
enum PragmaSectionFlag : unsigned {
PSF_None = 0,
@@ -3576,6 +3610,14 @@ inline Selector GetUnarySelector(StringRef name, ASTContext &Ctx) {
return Ctx.Selectors.getSelector(1, &II);
}

/// Placeholder implementation that issues a diagnostic on any usage.
struct UnimplementedSemaProxy final : SemaProxy {
virtual ~UnimplementedSemaProxy() = default;

void InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
FunctionDecl *Function) override;
};

} // namespace clang

// operator new and delete aren't allowed inside namespaces.
10 changes: 10 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
@@ -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 SemaProxy interface that enables constant evaluator
/// to call Sema and modify AST, e.g. to instantiate templates.
struct SemaProxyImpl final : SemaProxy {
Sema &SemaRef;
explicit SemaProxyImpl(Sema &SemaRef);
void InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
FunctionDecl *Function) override;
};

/// Describes the result of template argument deduction.
///
/// The TemplateDeductionResult enumeration describes the result of
25 changes: 22 additions & 3 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
@@ -51,6 +51,7 @@
#include "clang/Basic/AddressSpaces.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/CommentOptions.h"
#include "clang/Basic/DiagnosticAST.h"
#include "clang/Basic/ExceptionSpecificationType.h"
#include "clang/Basic/IdentifierTable.h"
#include "clang/Basic/LLVM.h"
@@ -902,9 +903,11 @@ ASTContext::ASTContext(LangOptions &LOpts, SourceManager &SM,
LangOpts.XRayNeverInstrumentFiles,
LangOpts.XRayAttrListFiles, SM)),
ProfList(new ProfileList(LangOpts.ProfileListFiles, SM)),
PrintingPolicy(LOpts), Idents(idents), Selectors(sels),
BuiltinInfo(builtins), TUKind(TUKind), DeclarationNames(*this),
Comments(SM), CommentCommandTraits(BumpAlloc, LOpts.CommentOpts),
PrintingPolicy(LOpts),
SemaProxyPtr(std::make_unique<UnimplementedSemaProxy>()),
Idents(idents), Selectors(sels), BuiltinInfo(builtins), TUKind(TUKind),
DeclarationNames(*this), Comments(SM),
CommentCommandTraits(BumpAlloc, LOpts.CommentOpts),
CompCategories(this_()), LastSDM(nullptr, 0) {
addTranslationUnitDecl();
}
@@ -14492,3 +14495,19 @@ bool ASTContext::useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl,
ThunksToBeAbbreviated[VirtualMethodDecl] = std::move(SimplifiedThunkNames);
return Result;
}

bool SemaProxy::getIgnoreSideEffectsOnAST() { return IgnoreSideEffectsOnAST; }

void SemaProxy::setIgnoreSideEffectsOnAST(bool Ignore) {
IgnoreSideEffectsOnAST = Ignore;
}

void UnimplementedSemaProxy::InstantiateFunctionDefinition(
SourceLocation PointOfInstantiation, FunctionDecl *Function) {
if (getIgnoreSideEffectsOnAST())
return;
llvm_unreachable(
"AST mutation was requested without clang::Sema available. "
"Consider providing it, or disabling side effects on AST via "
"ASTContext.getSemaProxy().setIgnoreSideEffectsOnAST(true).");
}
12 changes: 11 additions & 1 deletion clang/lib/AST/ByteCode/ByteCodeEmitter.cpp
Original file line number Diff line number Diff line change
@@ -22,7 +22,8 @@
using namespace clang;
using namespace clang::interp;

Function *ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) {
Function *ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl,
SourceLocation Loc) {

// Manually created functions that haven't been assigned proper
// parameters yet.
@@ -145,6 +146,15 @@ Function *ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) {
}

assert(Func);

if (Ctx.getLangOpts().CPlusPlus23 && !FuncDecl->isDefined() &&
FuncDecl->isImplicitlyInstantiable()) {
assert(Loc.isValid() && "Implicitly instantiating from the new constant "
"interpreter without a valid source location!");
Ctx.getASTContext().getSemaProxy().InstantiateFunctionDefinition(
Loc, const_cast<FunctionDecl *>(FuncDecl));
}

// For not-yet-defined functions, we only create a Function instance and
// compile their body later.
if (!FuncDecl->isDefined() ||
2 changes: 1 addition & 1 deletion clang/lib/AST/ByteCode/ByteCodeEmitter.h
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ class ByteCodeEmitter {

public:
/// Compiles the function into the module.
Function *compileFunc(const FunctionDecl *FuncDecl);
Function *compileFunc(const FunctionDecl *FuncDecl, SourceLocation Loc);
Function *compileObjCBlock(const BlockExpr *BE);

protected:
9 changes: 5 additions & 4 deletions clang/lib/AST/ByteCode/Compiler.cpp
Original file line number Diff line number Diff line change
@@ -2983,7 +2983,7 @@ bool Compiler<Emitter>::VisitCXXConstructExpr(const CXXConstructExpr *E) {
return true;
}

const Function *Func = getFunction(Ctor);
const Function *Func = getFunction(Ctor, E->getExprLoc());

if (!Func)
return false;
@@ -4121,8 +4121,9 @@ Record *Compiler<Emitter>::getRecord(const RecordDecl *RD) {
}

template <class Emitter>
const Function *Compiler<Emitter>::getFunction(const FunctionDecl *FD) {
return Ctx.getOrCreateFunction(FD);
const Function *Compiler<Emitter>::getFunction(const FunctionDecl *FD,
SourceLocation Loc) {
return Ctx.getOrCreateFunction(FD, Loc);
}

template <class Emitter>
@@ -4648,7 +4649,7 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
}

if (FuncDecl) {
const Function *Func = getFunction(FuncDecl);
const Function *Func = getFunction(FuncDecl, E->getExprLoc());
if (!Func)
return false;
assert(HasRVO == Func->hasRVO());
2 changes: 1 addition & 1 deletion clang/lib/AST/ByteCode/Compiler.h
Original file line number Diff line number Diff line change
@@ -243,7 +243,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,

/// Returns a function for the given FunctionDecl.
/// If the function does not exist yet, it is compiled.
const Function *getFunction(const FunctionDecl *FD);
const Function *getFunction(const FunctionDecl *FD, SourceLocation Loc = {});

std::optional<PrimType> classify(const Expr *E) const {
return Ctx.classify(E);
8 changes: 5 additions & 3 deletions clang/lib/AST/ByteCode/Context.cpp
Original file line number Diff line number Diff line change
@@ -29,7 +29,8 @@ bool Context::isPotentialConstantExpr(State &Parent, const FunctionDecl *FD) {
assert(Stk.empty());
Function *Func = P->getFunction(FD);
if (!Func || !Func->hasBody())
Func = Compiler<ByteCodeEmitter>(*this, *P).compileFunc(FD);
Func =
Compiler<ByteCodeEmitter>(*this, *P).compileFunc(FD, SourceLocation{});

if (!Func)
return false;
@@ -270,7 +271,8 @@ Context::getOverridingFunction(const CXXRecordDecl *DynamicDecl,
return nullptr;
}

const Function *Context::getOrCreateFunction(const FunctionDecl *FD) {
const Function *Context::getOrCreateFunction(const FunctionDecl *FD,
SourceLocation Loc) {
assert(FD);
const Function *Func = P->getFunction(FD);
bool IsBeingCompiled = Func && Func->isDefined() && !Func->isFullyCompiled();
@@ -280,7 +282,7 @@ const Function *Context::getOrCreateFunction(const FunctionDecl *FD) {
return Func;

if (!Func || WasNotDefined) {
if (auto F = Compiler<ByteCodeEmitter>(*this, *P).compileFunc(FD))
if (auto F = Compiler<ByteCodeEmitter>(*this, *P).compileFunc(FD, Loc))
Func = F;
}

3 changes: 2 additions & 1 deletion clang/lib/AST/ByteCode/Context.h
Original file line number Diff line number Diff line change
@@ -91,7 +91,8 @@ class Context final {
const CXXRecordDecl *StaticDecl,
const CXXMethodDecl *InitialFunction) const;

const Function *getOrCreateFunction(const FunctionDecl *FD);
const Function *getOrCreateFunction(const FunctionDecl *FD,
SourceLocation Loc = {});

/// Returns whether we should create a global variable for the
/// given ValueDecl.
7 changes: 7 additions & 0 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
@@ -1034,6 +1034,7 @@ namespace {
}

ASTContext &getASTContext() const override { return Ctx; }
SemaProxy &getSemaProxy() const { return Ctx.getSemaProxy(); }

void setEvaluatingDecl(APValue::LValueBase Base, APValue &Value,
EvaluatingDeclKind EDK = EvaluatingDeclKind::Ctor) {
@@ -8328,6 +8329,12 @@ class ExprEvaluatorBase

const FunctionDecl *Definition = nullptr;
Stmt *Body = FD->getBody(Definition);
if (Info.getLangOpts().CPlusPlus23 && !Definition &&
FD->getTemplateInstantiationPattern()) {
Info.getSemaProxy().InstantiateFunctionDefinition(
E->getExprLoc(), const_cast<FunctionDecl *>(FD));
Body = FD->getBody(Definition);
}

if (!CheckConstexprFunction(Info, E->getExprLoc(), FD, Definition, Body) ||
!HandleFunctionCall(E->getExprLoc(), Definition, This, E, Args, Call,
22 changes: 22 additions & 0 deletions clang/lib/Sema/Sema.cpp
Original file line number Diff line number Diff line change
@@ -208,6 +208,13 @@ class SemaPPCallbacks : public PPCallbacks {
} // end namespace sema
} // end namespace clang

void clang::injectSemaProxyIntoASTContext(ASTContext &Context,
std::unique_ptr<SemaProxy> ProxyPtr) {
Context.SemaProxyPtr = std::move(ProxyPtr);
}

SemaProxyImpl::SemaProxyImpl(Sema &SemaRef) : SemaRef(SemaRef) {}

const unsigned Sema::MaxAlignmentExponent;
const uint64_t Sema::MaximumAlignment;

@@ -298,6 +305,12 @@ Sema::Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer,
SemaPPCallbackHandler->set(*this);

CurFPFeatures.setFPEvalMethod(PP.getCurrentFPEvalMethod());

/// Initialize SemaProxyPtr within ASTContext.
/// This is very intentionally not a part of public interface
/// of ASTContext.
injectSemaProxyIntoASTContext(Context,
std::make_unique<SemaProxyImpl>(*this));
}

// Anchor Sema's type info to this TU.
@@ -2798,3 +2811,12 @@ Attr *Sema::CreateAnnotationAttr(const ParsedAttr &AL) {

return CreateAnnotationAttr(AL, Str, Args);
}

void SemaProxyImpl::InstantiateFunctionDefinition(
SourceLocation PointOfInstantiation, FunctionDecl *Function) {
if (getIgnoreSideEffectsOnAST())
return;
SemaRef.InstantiateFunctionDefinition(
PointOfInstantiation, Function, /*Recursive=*/true,
/*DefinitionRequired=*/true, /*AtEndOfTU=*/false);
}
41 changes: 41 additions & 0 deletions clang/test/SemaCXX/constexpr-function-instantiation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify=cxx20 %s
// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify=cxx23-26 %s
// RUN: %clang_cc1 -std=c++2c -fsyntax-only -verify=cxx23-26 %s

// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify=cxx20 -fexperimental-new-constant-interpreter %s
// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify=cxx23-26 -fexperimental-new-constant-interpreter %s
// RUN: %clang_cc1 -std=c++2c -fsyntax-only -verify=cxx23-26 -fexperimental-new-constant-interpreter %s

// cxx23-26-no-diagnostics

namespace GH73232 {
namespace ex1 {
template <typename T>
constexpr void g(T); // #ex1-g-decl

constexpr int f() {
g(0); // #ex1-g-call
return 0;
}

template <typename T>
constexpr void g(T) {}

constexpr auto z = f(); // #ex1-z-defn
// cxx20-error@-1 {{constexpr variable 'z' must be initialized by a constant expression}}
// cxx20-note@#ex1-g-call {{undefined function 'g<int>' cannot be used in a constant expression}}
// cxx20-note@#ex1-z-defn {{in call to 'f()'}}
// cxx20-note@#ex1-g-decl {{declared here}}
} // 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