[clang] [clang][bytecode] Compile functions lazily (PR #131596)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Mar 17 03:11:30 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Timm Baeder (tbaederr)
<details>
<summary>Changes</summary>
Create the Function* handles for all functions we see, but delay the actual compilation until we really call the function. This speeds up compile times with the new interpreter a bit.
---
Patch is 22.45 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/131596.diff
8 Files Affected:
- (modified) clang/lib/AST/ByteCode/ByteCodeEmitter.cpp (+35-163)
- (modified) clang/lib/AST/ByteCode/ByteCodeEmitter.h (+1-2)
- (modified) clang/lib/AST/ByteCode/Compiler.cpp (+1-1)
- (modified) clang/lib/AST/ByteCode/Context.cpp (+145-10)
- (modified) clang/lib/AST/ByteCode/Context.h (+3-1)
- (modified) clang/lib/AST/ByteCode/Function.cpp (+2-2)
- (modified) clang/lib/AST/ByteCode/Function.h (+2-1)
- (modified) clang/lib/AST/ByteCode/Interp.cpp (+17-1)
``````````diff
diff --git a/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp b/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp
index 4162b55070da9..68651861a271e 100644
--- a/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp
+++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.cpp
@@ -20,149 +20,63 @@
using namespace clang;
using namespace clang::interp;
-Function *ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) {
+void ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl,
+ Function *Func) {
+ assert(FuncDecl);
+ assert(Func);
// Manually created functions that haven't been assigned proper
// parameters yet.
if (!FuncDecl->param_empty() && !FuncDecl->param_begin())
- return nullptr;
+ return;
+
+ if (!FuncDecl->isDefined())
+ return;
- bool IsLambdaStaticInvoker = false;
+ // Set up lambda captures.
if (const auto *MD = dyn_cast<CXXMethodDecl>(FuncDecl);
- MD && MD->isLambdaStaticInvoker()) {
- // For a lambda static invoker, we might have to pick a specialized
- // version if the lambda is generic. In that case, the picked function
- // will *NOT* be a static invoker anymore. However, it will still
- // be a non-static member function, this (usually) requiring an
- // instance pointer. We suppress that later in this function.
- IsLambdaStaticInvoker = true;
-
- const CXXRecordDecl *ClosureClass = MD->getParent();
- assert(ClosureClass->captures_begin() == ClosureClass->captures_end());
- if (ClosureClass->isGenericLambda()) {
- const CXXMethodDecl *LambdaCallOp = ClosureClass->getLambdaCallOperator();
- assert(MD->isFunctionTemplateSpecialization() &&
- "A generic lambda's static-invoker function must be a "
- "template specialization");
- const TemplateArgumentList *TAL = MD->getTemplateSpecializationArgs();
- FunctionTemplateDecl *CallOpTemplate =
- LambdaCallOp->getDescribedFunctionTemplate();
- void *InsertPos = nullptr;
- const FunctionDecl *CorrespondingCallOpSpecialization =
- CallOpTemplate->findSpecialization(TAL->asArray(), InsertPos);
- assert(CorrespondingCallOpSpecialization);
- FuncDecl = cast<CXXMethodDecl>(CorrespondingCallOpSpecialization);
- }
- }
+ MD && isLambdaCallOperator(MD)) {
+ // Set up lambda capture to closure record field mapping.
+ const Record *R = P.getOrCreateRecord(MD->getParent());
+ assert(R);
+ llvm::DenseMap<const ValueDecl *, FieldDecl *> LC;
+ FieldDecl *LTC;
- // Set up argument indices.
- unsigned ParamOffset = 0;
- SmallVector<PrimType, 8> ParamTypes;
- SmallVector<unsigned, 8> ParamOffsets;
- llvm::DenseMap<unsigned, Function::ParamDescriptor> ParamDescriptors;
-
- // If the return is not a primitive, a pointer to the storage where the
- // value is initialized in is passed as the first argument. See 'RVO'
- // elsewhere in the code.
- QualType Ty = FuncDecl->getReturnType();
- bool HasRVO = false;
- if (!Ty->isVoidType() && !Ctx.classify(Ty)) {
- HasRVO = true;
- ParamTypes.push_back(PT_Ptr);
- ParamOffsets.push_back(ParamOffset);
- ParamOffset += align(primSize(PT_Ptr));
- }
+ MD->getParent()->getCaptureFields(LC, LTC);
- // If the function decl is a member decl, the next parameter is
- // the 'this' pointer. This parameter is pop()ed from the
- // InterpStack when calling the function.
- bool HasThisPointer = false;
- if (const auto *MD = dyn_cast<CXXMethodDecl>(FuncDecl)) {
- if (!IsLambdaStaticInvoker) {
- HasThisPointer = MD->isInstance();
- if (MD->isImplicitObjectMemberFunction()) {
- ParamTypes.push_back(PT_Ptr);
- ParamOffsets.push_back(ParamOffset);
- ParamOffset += align(primSize(PT_Ptr));
- }
+ for (auto Cap : LC) {
+ unsigned Offset = R->getField(Cap.second)->Offset;
+ this->LambdaCaptures[Cap.first] = {
+ Offset, Cap.second->getType()->isReferenceType()};
}
-
- // Set up lambda capture to closure record field mapping.
- if (isLambdaCallOperator(MD)) {
- // The parent record needs to be complete, we need to know about all
- // the lambda captures.
- if (!MD->getParent()->isCompleteDefinition())
- return nullptr;
-
- const Record *R = P.getOrCreateRecord(MD->getParent());
- llvm::DenseMap<const ValueDecl *, FieldDecl *> LC;
- FieldDecl *LTC;
-
- MD->getParent()->getCaptureFields(LC, LTC);
-
- for (auto Cap : LC) {
- // Static lambdas cannot have any captures. If this one does,
- // it has already been diagnosed and we can only ignore it.
- if (MD->isStatic())
- return nullptr;
-
- unsigned Offset = R->getField(Cap.second)->Offset;
- this->LambdaCaptures[Cap.first] = {
- Offset, Cap.second->getType()->isReferenceType()};
- }
- if (LTC) {
- QualType CaptureType = R->getField(LTC)->Decl->getType();
- this->LambdaThisCapture = {R->getField(LTC)->Offset,
- CaptureType->isReferenceType() ||
- CaptureType->isPointerType()};
- }
+ if (LTC) {
+ QualType CaptureType = R->getField(LTC)->Decl->getType();
+ this->LambdaThisCapture = {R->getField(LTC)->Offset,
+ CaptureType->isPointerOrReferenceType()};
}
}
- // Assign descriptors to all parameters.
- // Composite objects are lowered to pointers.
- for (const ParmVarDecl *PD : FuncDecl->parameters()) {
+ // Register parameters with their offset.
+ unsigned ParamIndex = 0;
+ unsigned Drop = Func->hasThisPointer() + Func->hasRVO();
+ for (auto ParamOffset : llvm::drop_begin(Func->ParamOffsets, Drop)) {
+ const ParmVarDecl *PD = FuncDecl->parameters()[ParamIndex];
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);
- }
-
- // Create a handle over the emitted code.
- Function *Func = P.getFunction(FuncDecl);
- if (!Func) {
- Func = P.createFunction(FuncDecl, ParamOffset, std::move(ParamTypes),
- std::move(ParamDescriptors),
- std::move(ParamOffsets), HasThisPointer, HasRVO);
- }
-
- assert(Func);
- // For not-yet-defined functions, we only create a Function instance and
- // compile their body later.
- if (!FuncDecl->isDefined() ||
- (FuncDecl->willHaveBody() && !FuncDecl->hasBody())) {
- Func->setDefined(false);
- return Func;
+ this->Params.insert({PD, {ParamOffset, T != std::nullopt}});
+ ++ParamIndex;
}
Func->setDefined(true);
// Lambda static invokers are a special case that we emit custom code for.
- bool IsEligibleForCompilation = false;
- if (const auto *MD = dyn_cast<CXXMethodDecl>(FuncDecl))
- IsEligibleForCompilation = MD->isLambdaStaticInvoker();
- if (!IsEligibleForCompilation)
- IsEligibleForCompilation =
- FuncDecl->isConstexpr() || FuncDecl->hasAttr<MSConstexprAttr>();
+ bool IsEligibleForCompilation = Func->isLambdaStaticInvoker() ||
+ FuncDecl->isConstexpr() ||
+ FuncDecl->hasAttr<MSConstexprAttr>();
// Compile the function body.
if (!IsEligibleForCompilation || !visitFunc(FuncDecl)) {
Func->setIsFullyCompiled(true);
- return Func;
+ return;
}
// Create scopes from descriptors.
@@ -175,48 +89,6 @@ Function *ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) {
Func->setCode(NextLocalOffset, std::move(Code), std::move(SrcMap),
std::move(Scopes), FuncDecl->hasBody());
Func->setIsFullyCompiled(true);
- return Func;
-}
-
-/// Compile an ObjC block, i.e. ^(){}, that thing.
-///
-/// FIXME: 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),
- /*HasThisPointer=*/false, /*HasRVO=*/false);
-
- 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) {
diff --git a/clang/lib/AST/ByteCode/ByteCodeEmitter.h b/clang/lib/AST/ByteCode/ByteCodeEmitter.h
index 64670c32cbcf6..5c7482923386e 100644
--- a/clang/lib/AST/ByteCode/ByteCodeEmitter.h
+++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.h
@@ -31,8 +31,7 @@ class ByteCodeEmitter {
public:
/// Compiles the function into the module.
- Function *compileFunc(const FunctionDecl *FuncDecl);
- Function *compileObjCBlock(const BlockExpr *BE);
+ void compileFunc(const FunctionDecl *FuncDecl, Function *Func = nullptr);
protected:
ByteCodeEmitter(Context &Ctx, Program &P) : Ctx(Ctx), P(P) {}
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index b9f88230007b5..3524ab5f86de8 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -3576,7 +3576,7 @@ bool Compiler<Emitter>::VisitBlockExpr(const BlockExpr *E) {
return true;
const Function *Func = nullptr;
- if (auto F = Compiler<ByteCodeEmitter>(Ctx, P).compileObjCBlock(E))
+ if (auto F = Ctx.getOrCreateObjCBlock(E))
Func = F;
if (!Func)
diff --git a/clang/lib/AST/ByteCode/Context.cpp b/clang/lib/AST/ByteCode/Context.cpp
index aa434d5c85921..23f4c5a4fa4b7 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -27,10 +27,17 @@ Context::~Context() {}
bool Context::isPotentialConstantExpr(State &Parent, const FunctionDecl *FD) {
assert(Stk.empty());
+
+ // Get a function handle.
const Function *Func = getOrCreateFunction(FD);
if (!Func)
return false;
+ // Compile the function.
+ Compiler<ByteCodeEmitter>(*this, *P).compileFunc(
+ FD, const_cast<Function *>(Func));
+
+ // And run it.
if (!Run(Parent, Func))
return false;
@@ -263,21 +270,149 @@ Context::getOverridingFunction(const CXXRecordDecl *DynamicDecl,
return nullptr;
}
-const Function *Context::getOrCreateFunction(const FunctionDecl *FD) {
- assert(FD);
- FD = FD->getMostRecentDecl();
- const Function *Func = P->getFunction(FD);
- bool IsBeingCompiled = Func && Func->isDefined() && !Func->isFullyCompiled();
- bool WasNotDefined = Func && !Func->isConstexpr() && !Func->isDefined();
+const Function *Context::getOrCreateFunction(const FunctionDecl *FuncDecl) {
+ assert(FuncDecl);
+ FuncDecl = FuncDecl->getMostRecentDecl();
- if (IsBeingCompiled)
+ if (const Function *Func = P->getFunction(FuncDecl))
return Func;
- if (!Func || WasNotDefined) {
- if (auto F = Compiler<ByteCodeEmitter>(*this, *P).compileFunc(FD))
- Func = F;
+ // Manually created functions that haven't been assigned proper
+ // parameters yet.
+ if (!FuncDecl->param_empty() && !FuncDecl->param_begin())
+ return nullptr;
+
+ bool IsLambdaStaticInvoker = false;
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(FuncDecl);
+ MD && MD->isLambdaStaticInvoker()) {
+ // For a lambda static invoker, we might have to pick a specialized
+ // version if the lambda is generic. In that case, the picked function
+ // will *NOT* be a static invoker anymore. However, it will still
+ // be a non-static member function, this (usually) requiring an
+ // instance pointer. We suppress that later in this function.
+ IsLambdaStaticInvoker = true;
+
+ const CXXRecordDecl *ClosureClass = MD->getParent();
+ assert(ClosureClass->captures_begin() == ClosureClass->captures_end());
+ if (ClosureClass->isGenericLambda()) {
+ const CXXMethodDecl *LambdaCallOp = ClosureClass->getLambdaCallOperator();
+ assert(MD->isFunctionTemplateSpecialization() &&
+ "A generic lambda's static-invoker function must be a "
+ "template specialization");
+ const TemplateArgumentList *TAL = MD->getTemplateSpecializationArgs();
+ FunctionTemplateDecl *CallOpTemplate =
+ LambdaCallOp->getDescribedFunctionTemplate();
+ void *InsertPos = nullptr;
+ const FunctionDecl *CorrespondingCallOpSpecialization =
+ CallOpTemplate->findSpecialization(TAL->asArray(), InsertPos);
+ assert(CorrespondingCallOpSpecialization);
+ FuncDecl = CorrespondingCallOpSpecialization;
+ }
+ }
+ // Set up argument indices.
+ unsigned ParamOffset = 0;
+ SmallVector<PrimType, 8> ParamTypes;
+ SmallVector<unsigned, 8> ParamOffsets;
+ llvm::DenseMap<unsigned, Function::ParamDescriptor> ParamDescriptors;
+
+ // If the return is not a primitive, a pointer to the storage where the
+ // value is initialized in is passed as the first argument. See 'RVO'
+ // elsewhere in the code.
+ QualType Ty = FuncDecl->getReturnType();
+ bool HasRVO = false;
+ if (!Ty->isVoidType() && !classify(Ty)) {
+ HasRVO = true;
+ ParamTypes.push_back(PT_Ptr);
+ ParamOffsets.push_back(ParamOffset);
+ ParamOffset += align(primSize(PT_Ptr));
+ }
+
+ // If the function decl is a member decl, the next parameter is
+ // the 'this' pointer. This parameter is pop()ed from the
+ // InterpStack when calling the function.
+ bool HasThisPointer = false;
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(FuncDecl)) {
+ if (!IsLambdaStaticInvoker) {
+ HasThisPointer = MD->isInstance();
+ if (MD->isImplicitObjectMemberFunction()) {
+ ParamTypes.push_back(PT_Ptr);
+ ParamOffsets.push_back(ParamOffset);
+ ParamOffset += align(primSize(PT_Ptr));
+ }
+ }
+
+ if (isLambdaCallOperator(MD)) {
+ // The parent record needs to be complete, we need to know about all
+ // the lambda captures.
+ if (!MD->getParent()->isCompleteDefinition())
+ return nullptr;
+ llvm::DenseMap<const ValueDecl *, FieldDecl *> LC;
+ FieldDecl *LTC;
+
+ MD->getParent()->getCaptureFields(LC, LTC);
+
+ if (MD->isStatic() && !LC.empty()) {
+ // Static lambdas cannot have any captures. If this one does,
+ // it has already been diagnosed and we can only ignore it.
+ return nullptr;
+ }
+ }
+ }
+
+ // Assign descriptors to all parameters.
+ // Composite objects are lowered to pointers.
+ for (const ParmVarDecl *PD : FuncDecl->parameters()) {
+ std::optional<PrimType> T = classify(PD->getType());
+ PrimType PT = T.value_or(PT_Ptr);
+ Descriptor *Desc = P->createDescriptor(PD, PT);
+ ParamDescriptors.insert({ParamOffset, {PT, Desc}});
+ ParamOffsets.push_back(ParamOffset);
+ ParamOffset += align(primSize(PT));
+ ParamTypes.push_back(PT);
}
+ // Create a handle over the emitted code.
+ assert(!P->getFunction(FuncDecl));
+ const Function *Func = P->createFunction(
+ FuncDecl, ParamOffset, std::move(ParamTypes), std::move(ParamDescriptors),
+ std::move(ParamOffsets), HasThisPointer, HasRVO, IsLambdaStaticInvoker);
+ return Func;
+}
+
+const Function *Context::getOrCreateObjCBlock(const BlockExpr *E) {
+ const BlockDecl *BD = E->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 = classify(PD->getType());
+ PrimType PT = T.value_or(PT_Ptr);
+ Descriptor *Desc = P->createDescriptor(PD, PT);
+ ParamDescriptors.insert({ParamOffset, {PT, Desc}});
+ 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(E, ParamOffset, std::move(ParamTypes),
+ std::move(ParamDescriptors), std::move(ParamOffsets),
+ /*HasThisPointer=*/false, /*HasRVO=*/false,
+ /*IsLambdaStaticInvoker=*/false);
+
+ assert(Func);
+ Func->setDefined(true);
+ // We don't compile the BlockDecl code at all right now.
+ Func->setIsFullyCompiled(true);
return Func;
}
diff --git a/clang/lib/AST/ByteCode/Context.h b/clang/lib/AST/ByteCode/Context.h
index ed539def99efd..8e142a0121ed3 100644
--- a/clang/lib/AST/ByteCode/Context.h
+++ b/clang/lib/AST/ByteCode/Context.h
@@ -24,6 +24,7 @@ class LangOptions;
class FunctionDecl;
class VarDecl;
class APValue;
+class BlockExpr;
namespace interp {
class Function;
@@ -91,7 +92,8 @@ class Context final {
const CXXRecordDecl *StaticDecl,
const CXXMethodDecl *InitialFunction) const;
- const Function *getOrCreateFunction(const FunctionDecl *FD);
+ const Function *getOrCreateFunction(const FunctionDecl *FuncDecl);
+ const Function *getOrCreateObjCBlock(const BlockExpr *E);
/// Returns whether we should create a global variable for the
/// given ValueDecl.
diff --git a/clang/lib/AST/ByteCode/Function.cpp b/clang/lib/AST/ByteCode/Function.cpp
index 6b892dfd616c1..fa803070c821d 100644
--- a/clang/lib/AST/ByteCode/Function.cpp
+++ b/clang/lib/AST/ByteCode/Function.cpp
@@ -19,7 +19,7 @@ Function::Function(Program &P, FunctionDeclTy Source, unsigned ArgSize,
llvm::SmallVectorImpl<PrimType> &&ParamTypes,
llvm::DenseMap<unsigned, ParamDescriptor> &&Params,
llvm::SmallVectorImpl<unsigned> &&ParamOffsets,
- bool HasThisPointer, bool HasRVO)
+ bool HasThisPointer, bool HasRVO, bool IsLambdaStaticInvoker)
: P(P), Kind(FunctionKind::Normal), Source(Source), ArgSize(ArgSize),
ParamTypes(std::move(ParamTypes)), Params(std::move(Params)),
ParamOffsets(std::move(ParamOffsets)), HasThisPointer(HasThisPointer),
@@ -35,7 +35,7 @@ Function::Function(Program &P, FunctionDeclTy Source, unsigned ArgSize,
Kind = FunctionKind::Dtor;
} else if (const auto *MD = dyn_cast<CXXMethodDecl>(F)) {
Virtual = MD->isVirtual();
- if (MD->isLambdaStaticInvoker())
+ if (IsLambdaStaticInvoker) // MD->isLambdaStaticInvoker())
Kind = FunctionKind::LambdaStaticInvoker;
else if (clang::isLambdaCallOperator(F))
Kind = FunctionKind::LambdaCallOperator;
diff --git a/clang/lib/AST/ByteCode/Function.h b/clang/lib/AST/ByteCode/Function.h
index e17183eef9eac..cdf98f9e67dde 100644
--- a/clang/lib/AST/ByteCode/Function.h
+++ b/clang/lib/AST/ByteCode/Function.h
@@ -232,7 +232,7 @@ class Function final {
llvm::SmallVectorImpl<PrimType> &&ParamTypes,
llvm::DenseMap<unsigned, ParamDescriptor> &&Params,
llvm::SmallVectorImpl<unsigned> &&ParamOffsets, bool HasThisPointer,
- bool HasRVO);
+ bool HasRVO, bool IsLambdaStaticInvoker);
/// Sets the code of a function.
void setCode(unsigned NewFrameSize, std::vector<std::byte> &&NewCode,
@@ -252,6 +252,...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/131596
More information about the cfe-commits
mailing list