[clang] [clang][Interp] Support blocks (PR #104551)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Thu Aug 15 23:37:02 PDT 2024
https://github.com/tbaederr created https://github.com/llvm/llvm-project/pull/104551
I started out by adding a new pointer type for blocks, and I was fully prepared to compile their AST to bytecode and later call them.
... then I found out that the current interpreter doesn't support calling blocks at all. So we reuse `Function` to support sources other than `FunctionDecl`s and classify `BlockPointerType` as `PT_FnPtr`.
>From ba97e713b87056b401c31e1f6e8aa6cef0e9d928 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Thu, 15 Aug 2024 20:31:38 +0200
Subject: [PATCH] [clang][Interp] Support blocks
---
clang/lib/AST/Interp/ByteCodeEmitter.cpp | 40 ++++++++++++++++++++
clang/lib/AST/Interp/ByteCodeEmitter.h | 1 +
clang/lib/AST/Interp/Compiler.cpp | 15 ++++++--
clang/lib/AST/Interp/Compiler.h | 1 +
clang/lib/AST/Interp/Context.cpp | 3 ++
clang/lib/AST/Interp/Function.cpp | 13 ++++++-
clang/lib/AST/Interp/Function.h | 48 ++++++++++++++++++------
clang/lib/AST/Interp/FunctionPointer.h | 5 ++-
clang/lib/AST/Interp/Interp.h | 2 +-
clang/test/Sema/block-misc.c | 1 +
clang/test/Sema/block-return.c | 1 +
clang/test/SemaCXX/consteval-cleanup.cpp | 1 +
12 files changed, 111 insertions(+), 20 deletions(-)
diff --git a/clang/lib/AST/Interp/ByteCodeEmitter.cpp b/clang/lib/AST/Interp/ByteCodeEmitter.cpp
index a01fa15dc0b7dc..8aab8eb42ea816 100644
--- a/clang/lib/AST/Interp/ByteCodeEmitter.cpp
+++ b/clang/lib/AST/Interp/ByteCodeEmitter.cpp
@@ -195,6 +195,46 @@ Function *ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) {
return Func;
}
+/// Compile an ObjC block, i.e. ^(){}, that thing.
+///
+/// We do not support calling the block though, so we create a function
+/// here but do not compile any code for it.
+Function *ByteCodeEmitter::compileObjCBlock(const BlockExpr *BE) {
+ const BlockDecl *BD = BE->getBlockDecl();
+ // Set up argument indices.
+ unsigned ParamOffset = 0;
+ SmallVector<PrimType, 8> ParamTypes;
+ SmallVector<unsigned, 8> ParamOffsets;
+ llvm::DenseMap<unsigned, Function::ParamDescriptor> ParamDescriptors;
+
+ // Assign descriptors to all parameters.
+ // Composite objects are lowered to pointers.
+ for (const ParmVarDecl *PD : BD->parameters()) {
+ std::optional<PrimType> T = Ctx.classify(PD->getType());
+ PrimType PT = T.value_or(PT_Ptr);
+ Descriptor *Desc = P.createDescriptor(PD, PT);
+ ParamDescriptors.insert({ParamOffset, {PT, Desc}});
+ Params.insert({PD, {ParamOffset, T != std::nullopt}});
+ ParamOffsets.push_back(ParamOffset);
+ ParamOffset += align(primSize(PT));
+ ParamTypes.push_back(PT);
+ }
+
+ if (BD->hasCaptures())
+ return nullptr;
+
+ // Create a handle over the emitted code.
+ Function *Func =
+ P.createFunction(BE, ParamOffset, std::move(ParamTypes),
+ std::move(ParamDescriptors), std::move(ParamOffsets));
+
+ assert(Func);
+ Func->setDefined(true);
+ // We don't compile the BlockDecl code at all right now.
+ Func->setIsFullyCompiled(true);
+ return Func;
+}
+
Scope::Local ByteCodeEmitter::createLocal(Descriptor *D) {
NextLocalOffset += sizeof(Block);
unsigned Location = NextLocalOffset;
diff --git a/clang/lib/AST/Interp/ByteCodeEmitter.h b/clang/lib/AST/Interp/ByteCodeEmitter.h
index a19a25c2f9e8ec..915960cb515ce6 100644
--- a/clang/lib/AST/Interp/ByteCodeEmitter.h
+++ b/clang/lib/AST/Interp/ByteCodeEmitter.h
@@ -32,6 +32,7 @@ class ByteCodeEmitter {
public:
/// Compiles the function into the module.
Function *compileFunc(const FunctionDecl *FuncDecl);
+ Function *compileObjCBlock(const BlockExpr *BE);
protected:
ByteCodeEmitter(Context &Ctx, Program &P) : Ctx(Ctx), P(P) {}
diff --git a/clang/lib/AST/Interp/Compiler.cpp b/clang/lib/AST/Interp/Compiler.cpp
index d32b595f9f0724..109dbaf7cdf0eb 100644
--- a/clang/lib/AST/Interp/Compiler.cpp
+++ b/clang/lib/AST/Interp/Compiler.cpp
@@ -389,8 +389,6 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *CE) {
return this->emitPop(T, CE);
QualType PtrType = CE->getType();
- assert(PtrType->isPointerType());
-
const Descriptor *Desc;
if (std::optional<PrimType> T = classify(PtrType->getPointeeType()))
Desc = P.createDescriptor(SubExpr, *T);
@@ -2238,8 +2236,6 @@ bool Compiler<Emitter>::VisitExprWithCleanups(const ExprWithCleanups *E) {
LocalScope<Emitter> ES(this);
const Expr *SubExpr = E->getSubExpr();
- assert(E->getNumObjects() == 0 && "TODO: Implement cleanups");
-
return this->delegate(SubExpr) && ES.destroyLocals(E);
}
@@ -2909,6 +2905,17 @@ bool Compiler<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) {
return this->emitFree(E->isArrayForm(), E);
}
+template <class Emitter>
+bool Compiler<Emitter>::VisitBlockExpr(const BlockExpr *E) {
+ const Function *Func = nullptr;
+ if (auto F = Compiler<ByteCodeEmitter>(Ctx, P).compileObjCBlock(E))
+ Func = F;
+
+ if (!Func)
+ return false;
+ return this->emitGetFnPtr(Func, E);
+}
+
template <class Emitter>
bool Compiler<Emitter>::VisitExpressionTraitExpr(const ExpressionTraitExpr *E) {
assert(Ctx.getLangOpts().CPlusPlus);
diff --git a/clang/lib/AST/Interp/Compiler.h b/clang/lib/AST/Interp/Compiler.h
index 112219c49e8bdd..f2a2a2aaecaab3 100644
--- a/clang/lib/AST/Interp/Compiler.h
+++ b/clang/lib/AST/Interp/Compiler.h
@@ -199,6 +199,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
bool VisitStmtExpr(const StmtExpr *E);
bool VisitCXXNewExpr(const CXXNewExpr *E);
bool VisitCXXDeleteExpr(const CXXDeleteExpr *E);
+ bool VisitBlockExpr(const BlockExpr *E);
// Statements.
bool visitCompoundStmt(const CompoundStmt *S);
diff --git a/clang/lib/AST/Interp/Context.cpp b/clang/lib/AST/Interp/Context.cpp
index 92ac28137fdb45..cd9b178fbb6420 100644
--- a/clang/lib/AST/Interp/Context.cpp
+++ b/clang/lib/AST/Interp/Context.cpp
@@ -182,6 +182,9 @@ std::optional<PrimType> Context::classify(QualType T) const {
if (T->isPointerOrReferenceType() || T->isObjCObjectPointerType())
return PT_Ptr;
+ if (T->isBlockPointerType())
+ return PT_FnPtr;
+
if (const auto *AT = T->getAs<AtomicType>())
return classify(AT->getValueType());
diff --git a/clang/lib/AST/Interp/Function.cpp b/clang/lib/AST/Interp/Function.cpp
index 00f5a1fced531a..6ed251f1d9aff9 100644
--- a/clang/lib/AST/Interp/Function.cpp
+++ b/clang/lib/AST/Interp/Function.cpp
@@ -21,12 +21,20 @@ Function::Function(Program &P, const FunctionDecl *F, unsigned ArgSize,
llvm::DenseMap<unsigned, ParamDescriptor> &&Params,
llvm::SmallVectorImpl<unsigned> &&ParamOffsets,
bool HasThisPointer, bool HasRVO, bool UnevaluatedBuiltin)
- : P(P), Loc(F->getBeginLoc()), F(F), ArgSize(ArgSize),
+ : P(P), Loc(F->getBeginLoc()), Source(F), ArgSize(ArgSize),
ParamTypes(std::move(ParamTypes)), Params(std::move(Params)),
ParamOffsets(std::move(ParamOffsets)), HasThisPointer(HasThisPointer),
HasRVO(HasRVO), Variadic(F->isVariadic()),
IsUnevaluatedBuiltin(UnevaluatedBuiltin) {}
+Function::Function(Program &P, const BlockExpr *BE, unsigned ArgSize,
+ llvm::SmallVectorImpl<PrimType> &&ParamTypes,
+ llvm::DenseMap<unsigned, ParamDescriptor> &&Params,
+ llvm::SmallVectorImpl<unsigned> &&ParamOffsets)
+ : P(P), Loc(BE->getBeginLoc()), Source(BE), ArgSize(ArgSize),
+ ParamTypes(std::move(ParamTypes)), Params(std::move(Params)),
+ ParamOffsets(std::move(ParamOffsets)) {}
+
Function::ParamDescriptor Function::getParamDescriptor(unsigned Offset) const {
auto It = Params.find(Offset);
assert(It != Params.end() && "Invalid parameter offset");
@@ -46,7 +54,8 @@ SourceInfo Function::getSource(CodePtr PC) const {
}
bool Function::isVirtual() const {
- if (const auto *M = dyn_cast<CXXMethodDecl>(F))
+ if (const auto *M = dyn_cast_if_present<CXXMethodDecl>(
+ Source.dyn_cast<const FunctionDecl *>()))
return M->isVirtual();
return false;
}
diff --git a/clang/lib/AST/Interp/Function.h b/clang/lib/AST/Interp/Function.h
index 92bcd96927912d..130c1ecf7a2b32 100644
--- a/clang/lib/AST/Interp/Function.h
+++ b/clang/lib/AST/Interp/Function.h
@@ -20,6 +20,7 @@
#include "clang/AST/ASTLambda.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
+#include "llvm/ADT/PointerUnion.h"
#include "llvm/Support/raw_ostream.h"
namespace clang {
@@ -89,15 +90,20 @@ class Function final {
CodePtr getCodeEnd() const { return Code.data() + Code.size(); }
/// Returns the original FunctionDecl.
- const FunctionDecl *getDecl() const { return F; }
+ const FunctionDecl *getDecl() const {
+ return Source.dyn_cast<const FunctionDecl *>();
+ }
+ const BlockExpr *getExpr() const {
+ return Source.dyn_cast<const BlockExpr *>();
+ }
/// Returns the name of the function decl this code
/// was generated for.
const std::string getName() const {
- if (!F)
+ if (!Source)
return "<<expr>>";
- return F->getQualifiedNameAsString();
+ return Source.get<const FunctionDecl *>()->getQualifiedNameAsString();
}
/// Returns the location.
@@ -138,13 +144,20 @@ class Function final {
bool isVirtual() const;
/// Checks if the function is a constructor.
- bool isConstructor() const { return isa<CXXConstructorDecl>(F); }
+ bool isConstructor() const {
+ return isa_and_nonnull<CXXConstructorDecl>(
+ Source.dyn_cast<const FunctionDecl *>());
+ }
/// Checks if the function is a destructor.
- bool isDestructor() const { return isa<CXXDestructorDecl>(F); }
+ bool isDestructor() const {
+ return isa_and_nonnull<CXXDestructorDecl>(
+ Source.dyn_cast<const FunctionDecl *>());
+ }
/// Returns the parent record decl, if any.
const CXXRecordDecl *getParentDecl() const {
- if (const auto *MD = dyn_cast<CXXMethodDecl>(F))
+ if (const auto *MD = dyn_cast_if_present<CXXMethodDecl>(
+ Source.dyn_cast<const FunctionDecl *>()))
return MD->getParent();
return nullptr;
}
@@ -152,7 +165,8 @@ class Function final {
/// Returns whether this function is a lambda static invoker,
/// which we generate custom byte code for.
bool isLambdaStaticInvoker() const {
- if (const auto *MD = dyn_cast<CXXMethodDecl>(F))
+ if (const auto *MD = dyn_cast_if_present<CXXMethodDecl>(
+ Source.dyn_cast<const FunctionDecl *>()))
return MD->isLambdaStaticInvoker();
return false;
}
@@ -160,7 +174,8 @@ class Function final {
/// Returns whether this function is the call operator
/// of a lambda record decl.
bool isLambdaCallOperator() const {
- if (const auto *MD = dyn_cast<CXXMethodDecl>(F))
+ if (const auto *MD = dyn_cast_if_present<CXXMethodDecl>(
+ Source.dyn_cast<const FunctionDecl *>()))
return clang::isLambdaCallOperator(MD);
return false;
}
@@ -178,9 +193,13 @@ class Function final {
bool isVariadic() const { return Variadic; }
- unsigned getBuiltinID() const { return F->getBuiltinID(); }
+ unsigned getBuiltinID() const {
+ return Source.get<const FunctionDecl *>()->getBuiltinID();
+ }
- bool isBuiltin() const { return F->getBuiltinID() != 0; }
+ bool isBuiltin() const {
+ return Source.get<const FunctionDecl *>()->getBuiltinID() != 0;
+ }
bool isUnevaluatedBuiltin() const { return IsUnevaluatedBuiltin; }
@@ -197,7 +216,8 @@ class Function final {
}
bool isThisPointerExplicit() const {
- if (const auto *MD = dyn_cast<CXXMethodDecl>(F))
+ if (const auto *MD = dyn_cast_if_present<CXXMethodDecl>(
+ Source.dyn_cast<const FunctionDecl *>()))
return MD->isExplicitObjectMemberFunction();
return false;
}
@@ -213,6 +233,10 @@ class Function final {
llvm::DenseMap<unsigned, ParamDescriptor> &&Params,
llvm::SmallVectorImpl<unsigned> &&ParamOffsets, bool HasThisPointer,
bool HasRVO, bool UnevaluatedBuiltin);
+ Function(Program &P, const BlockExpr *BE, unsigned ArgSize,
+ llvm::SmallVectorImpl<PrimType> &&ParamTypes,
+ llvm::DenseMap<unsigned, ParamDescriptor> &&Params,
+ llvm::SmallVectorImpl<unsigned> &&ParamOffsets);
/// Sets the code of a function.
void setCode(unsigned NewFrameSize, std::vector<std::byte> &&NewCode,
@@ -238,7 +262,7 @@ class Function final {
/// Location of the executed code.
SourceLocation Loc;
/// Declaration this function was compiled from.
- const FunctionDecl *F;
+ llvm::PointerUnion<const FunctionDecl *, const BlockExpr *> Source;
/// Local area size: storage + metadata.
unsigned FrameSize = 0;
/// Size of the argument stack.
diff --git a/clang/lib/AST/Interp/FunctionPointer.h b/clang/lib/AST/Interp/FunctionPointer.h
index d92cd32933fcdc..23df0e74a651f2 100644
--- a/clang/lib/AST/Interp/FunctionPointer.h
+++ b/clang/lib/AST/Interp/FunctionPointer.h
@@ -49,7 +49,10 @@ class FunctionPointer final {
CharUnits::fromQuantity(getIntegerRepresentation()), {},
/*OnePastTheEnd=*/false, /*IsNull=*/false);
- return APValue(Func->getDecl(), CharUnits::Zero(), {},
+ if (Func->getDecl())
+ return APValue(Func->getDecl(), CharUnits::Zero(), {},
+ /*OnePastTheEnd=*/false, /*IsNull=*/false);
+ return APValue(Func->getExpr(), CharUnits::Zero(), {},
/*OnePastTheEnd=*/false, /*IsNull=*/false);
}
diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h
index 3eab0cfd871385..446fe6b01d8a87 100644
--- a/clang/lib/AST/Interp/Interp.h
+++ b/clang/lib/AST/Interp/Interp.h
@@ -2708,7 +2708,7 @@ inline bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
return false;
}
- if (!FuncPtr.isValid())
+ if (!FuncPtr.isValid() || !F->getDecl())
return Invalid(S, OpPC);
assert(F);
diff --git a/clang/test/Sema/block-misc.c b/clang/test/Sema/block-misc.c
index aea44d55a606a4..c8a34b7f3c9fd5 100644
--- a/clang/test/Sema/block-misc.c
+++ b/clang/test/Sema/block-misc.c
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -Wno-strict-prototypes -verify %s -fblocks
+// RUN: %clang_cc1 -fsyntax-only -Wno-strict-prototypes -verify %s -fblocks -fexperimental-new-constant-interpreter
void donotwarn(void);
int (^IFP) ();
diff --git a/clang/test/Sema/block-return.c b/clang/test/Sema/block-return.c
index d3d70511b18513..126fc6f953dea2 100644
--- a/clang/test/Sema/block-return.c
+++ b/clang/test/Sema/block-return.c
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -Wno-int-to-pointer-cast -Wno-pointer-to-int-cast -pedantic -fsyntax-only %s -verify -fblocks
+// RUN: %clang_cc1 -Wno-int-to-pointer-cast -Wno-pointer-to-int-cast -pedantic -fsyntax-only %s -verify -fblocks -fexperimental-new-constant-interpreter
extern int printf(const char *, ...);
diff --git a/clang/test/SemaCXX/consteval-cleanup.cpp b/clang/test/SemaCXX/consteval-cleanup.cpp
index 499c45db501770..f7d033b2ecafa4 100644
--- a/clang/test/SemaCXX/consteval-cleanup.cpp
+++ b/clang/test/SemaCXX/consteval-cleanup.cpp
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fblocks -Wno-unused-value -std=c++20 -ast-dump -verify %s -ast-dump | FileCheck %s
+// RUN: %clang_cc1 -fblocks -Wno-unused-value -std=c++20 -ast-dump -verify %s -ast-dump -fexperimental-new-constant-interpreter | FileCheck %s
// expected-no-diagnostics
More information about the cfe-commits
mailing list