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
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions clang/include/clang/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ enum class OverloadCandidateParamOrder : char;
enum OverloadCandidateRewriteKind : unsigned;
class OverloadCandidateSet;
class Preprocessor;
class Sema;
class SemaAMDGPU;
class SemaARM;
class SemaAVR;
Expand Down Expand Up @@ -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
Expand Down
25 changes: 22 additions & 3 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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
Expand Up @@ -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.
Expand Down Expand Up @@ -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() ||
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/ByteCode/ByteCodeEmitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 5 additions & 4 deletions clang/lib/AST/ByteCode/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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());
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/ByteCode/Compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 5 additions & 3 deletions clang/lib/AST/ByteCode/Context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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;
}

Expand Down
3 changes: 2 additions & 1 deletion clang/lib/AST/ByteCode/Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down
22 changes: 22 additions & 0 deletions clang/lib/Sema/Sema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Loading