Skip to content

Commit 83356f3

Browse files
authored
[clang][bytecode] Compile functions lazily (#131596)
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.
1 parent 9455df9 commit 83356f3

File tree

8 files changed

+207
-181
lines changed

8 files changed

+207
-181
lines changed

clang/lib/AST/ByteCode/ByteCodeEmitter.cpp

Lines changed: 36 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -20,149 +20,64 @@
2020
using namespace clang;
2121
using namespace clang::interp;
2222

23-
Function *ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) {
23+
void ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl,
24+
Function *Func) {
25+
assert(FuncDecl);
26+
assert(Func);
2427

2528
// Manually created functions that haven't been assigned proper
2629
// parameters yet.
2730
if (!FuncDecl->param_empty() && !FuncDecl->param_begin())
28-
return nullptr;
31+
return;
32+
33+
if (!FuncDecl->isDefined())
34+
return;
2935

30-
bool IsLambdaStaticInvoker = false;
36+
// Set up lambda captures.
3137
if (const auto *MD = dyn_cast<CXXMethodDecl>(FuncDecl);
32-
MD && MD->isLambdaStaticInvoker()) {
33-
// For a lambda static invoker, we might have to pick a specialized
34-
// version if the lambda is generic. In that case, the picked function
35-
// will *NOT* be a static invoker anymore. However, it will still
36-
// be a non-static member function, this (usually) requiring an
37-
// instance pointer. We suppress that later in this function.
38-
IsLambdaStaticInvoker = true;
39-
40-
const CXXRecordDecl *ClosureClass = MD->getParent();
41-
assert(ClosureClass->captures_begin() == ClosureClass->captures_end());
42-
if (ClosureClass->isGenericLambda()) {
43-
const CXXMethodDecl *LambdaCallOp = ClosureClass->getLambdaCallOperator();
44-
assert(MD->isFunctionTemplateSpecialization() &&
45-
"A generic lambda's static-invoker function must be a "
46-
"template specialization");
47-
const TemplateArgumentList *TAL = MD->getTemplateSpecializationArgs();
48-
FunctionTemplateDecl *CallOpTemplate =
49-
LambdaCallOp->getDescribedFunctionTemplate();
50-
void *InsertPos = nullptr;
51-
const FunctionDecl *CorrespondingCallOpSpecialization =
52-
CallOpTemplate->findSpecialization(TAL->asArray(), InsertPos);
53-
assert(CorrespondingCallOpSpecialization);
54-
FuncDecl = cast<CXXMethodDecl>(CorrespondingCallOpSpecialization);
55-
}
56-
}
38+
MD && isLambdaCallOperator(MD)) {
39+
// Set up lambda capture to closure record field mapping.
40+
const Record *R = P.getOrCreateRecord(MD->getParent());
41+
assert(R);
42+
llvm::DenseMap<const ValueDecl *, FieldDecl *> LC;
43+
FieldDecl *LTC;
5744

58-
// Set up argument indices.
59-
unsigned ParamOffset = 0;
60-
SmallVector<PrimType, 8> ParamTypes;
61-
SmallVector<unsigned, 8> ParamOffsets;
62-
llvm::DenseMap<unsigned, Function::ParamDescriptor> ParamDescriptors;
63-
64-
// If the return is not a primitive, a pointer to the storage where the
65-
// value is initialized in is passed as the first argument. See 'RVO'
66-
// elsewhere in the code.
67-
QualType Ty = FuncDecl->getReturnType();
68-
bool HasRVO = false;
69-
if (!Ty->isVoidType() && !Ctx.classify(Ty)) {
70-
HasRVO = true;
71-
ParamTypes.push_back(PT_Ptr);
72-
ParamOffsets.push_back(ParamOffset);
73-
ParamOffset += align(primSize(PT_Ptr));
74-
}
45+
MD->getParent()->getCaptureFields(LC, LTC);
7546

76-
// If the function decl is a member decl, the next parameter is
77-
// the 'this' pointer. This parameter is pop()ed from the
78-
// InterpStack when calling the function.
79-
bool HasThisPointer = false;
80-
if (const auto *MD = dyn_cast<CXXMethodDecl>(FuncDecl)) {
81-
if (!IsLambdaStaticInvoker) {
82-
HasThisPointer = MD->isInstance();
83-
if (MD->isImplicitObjectMemberFunction()) {
84-
ParamTypes.push_back(PT_Ptr);
85-
ParamOffsets.push_back(ParamOffset);
86-
ParamOffset += align(primSize(PT_Ptr));
87-
}
47+
for (auto Cap : LC) {
48+
unsigned Offset = R->getField(Cap.second)->Offset;
49+
this->LambdaCaptures[Cap.first] = {
50+
Offset, Cap.second->getType()->isReferenceType()};
8851
}
89-
90-
// Set up lambda capture to closure record field mapping.
91-
if (isLambdaCallOperator(MD)) {
92-
// The parent record needs to be complete, we need to know about all
93-
// the lambda captures.
94-
if (!MD->getParent()->isCompleteDefinition())
95-
return nullptr;
96-
97-
const Record *R = P.getOrCreateRecord(MD->getParent());
98-
llvm::DenseMap<const ValueDecl *, FieldDecl *> LC;
99-
FieldDecl *LTC;
100-
101-
MD->getParent()->getCaptureFields(LC, LTC);
102-
103-
for (auto Cap : LC) {
104-
// Static lambdas cannot have any captures. If this one does,
105-
// it has already been diagnosed and we can only ignore it.
106-
if (MD->isStatic())
107-
return nullptr;
108-
109-
unsigned Offset = R->getField(Cap.second)->Offset;
110-
this->LambdaCaptures[Cap.first] = {
111-
Offset, Cap.second->getType()->isReferenceType()};
112-
}
113-
if (LTC) {
114-
QualType CaptureType = R->getField(LTC)->Decl->getType();
115-
this->LambdaThisCapture = {R->getField(LTC)->Offset,
116-
CaptureType->isReferenceType() ||
117-
CaptureType->isPointerType()};
118-
}
52+
if (LTC) {
53+
QualType CaptureType = R->getField(LTC)->Decl->getType();
54+
this->LambdaThisCapture = {R->getField(LTC)->Offset,
55+
CaptureType->isPointerOrReferenceType()};
11956
}
12057
}
12158

122-
// Assign descriptors to all parameters.
123-
// Composite objects are lowered to pointers.
124-
for (const ParmVarDecl *PD : FuncDecl->parameters()) {
59+
// Register parameters with their offset.
60+
unsigned ParamIndex = 0;
61+
unsigned Drop = Func->hasRVO() +
62+
(Func->hasThisPointer() && !Func->isThisPointerExplicit());
63+
for (auto ParamOffset : llvm::drop_begin(Func->ParamOffsets, Drop)) {
64+
const ParmVarDecl *PD = FuncDecl->parameters()[ParamIndex];
12565
std::optional<PrimType> T = Ctx.classify(PD->getType());
126-
PrimType PT = T.value_or(PT_Ptr);
127-
Descriptor *Desc = P.createDescriptor(PD, PT);
128-
ParamDescriptors.insert({ParamOffset, {PT, Desc}});
129-
Params.insert({PD, {ParamOffset, T != std::nullopt}});
130-
ParamOffsets.push_back(ParamOffset);
131-
ParamOffset += align(primSize(PT));
132-
ParamTypes.push_back(PT);
133-
}
134-
135-
// Create a handle over the emitted code.
136-
Function *Func = P.getFunction(FuncDecl);
137-
if (!Func) {
138-
Func = P.createFunction(FuncDecl, ParamOffset, std::move(ParamTypes),
139-
std::move(ParamDescriptors),
140-
std::move(ParamOffsets), HasThisPointer, HasRVO);
141-
}
142-
143-
assert(Func);
144-
// For not-yet-defined functions, we only create a Function instance and
145-
// compile their body later.
146-
if (!FuncDecl->isDefined() ||
147-
(FuncDecl->willHaveBody() && !FuncDecl->hasBody())) {
148-
Func->setDefined(false);
149-
return Func;
66+
this->Params.insert({PD, {ParamOffset, T != std::nullopt}});
67+
++ParamIndex;
15068
}
15169

15270
Func->setDefined(true);
15371

15472
// Lambda static invokers are a special case that we emit custom code for.
155-
bool IsEligibleForCompilation = false;
156-
if (const auto *MD = dyn_cast<CXXMethodDecl>(FuncDecl))
157-
IsEligibleForCompilation = MD->isLambdaStaticInvoker();
158-
if (!IsEligibleForCompilation)
159-
IsEligibleForCompilation =
160-
FuncDecl->isConstexpr() || FuncDecl->hasAttr<MSConstexprAttr>();
73+
bool IsEligibleForCompilation = Func->isLambdaStaticInvoker() ||
74+
FuncDecl->isConstexpr() ||
75+
FuncDecl->hasAttr<MSConstexprAttr>();
16176

16277
// Compile the function body.
16378
if (!IsEligibleForCompilation || !visitFunc(FuncDecl)) {
16479
Func->setIsFullyCompiled(true);
165-
return Func;
80+
return;
16681
}
16782

16883
// Create scopes from descriptors.
@@ -175,48 +90,6 @@ Function *ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) {
17590
Func->setCode(NextLocalOffset, std::move(Code), std::move(SrcMap),
17691
std::move(Scopes), FuncDecl->hasBody());
17792
Func->setIsFullyCompiled(true);
178-
return Func;
179-
}
180-
181-
/// Compile an ObjC block, i.e. ^(){}, that thing.
182-
///
183-
/// FIXME: We do not support calling the block though, so we create a function
184-
/// here but do not compile any code for it.
185-
Function *ByteCodeEmitter::compileObjCBlock(const BlockExpr *BE) {
186-
const BlockDecl *BD = BE->getBlockDecl();
187-
// Set up argument indices.
188-
unsigned ParamOffset = 0;
189-
SmallVector<PrimType, 8> ParamTypes;
190-
SmallVector<unsigned, 8> ParamOffsets;
191-
llvm::DenseMap<unsigned, Function::ParamDescriptor> ParamDescriptors;
192-
193-
// Assign descriptors to all parameters.
194-
// Composite objects are lowered to pointers.
195-
for (const ParmVarDecl *PD : BD->parameters()) {
196-
std::optional<PrimType> T = Ctx.classify(PD->getType());
197-
PrimType PT = T.value_or(PT_Ptr);
198-
Descriptor *Desc = P.createDescriptor(PD, PT);
199-
ParamDescriptors.insert({ParamOffset, {PT, Desc}});
200-
Params.insert({PD, {ParamOffset, T != std::nullopt}});
201-
ParamOffsets.push_back(ParamOffset);
202-
ParamOffset += align(primSize(PT));
203-
ParamTypes.push_back(PT);
204-
}
205-
206-
if (BD->hasCaptures())
207-
return nullptr;
208-
209-
// Create a handle over the emitted code.
210-
Function *Func =
211-
P.createFunction(BE, ParamOffset, std::move(ParamTypes),
212-
std::move(ParamDescriptors), std::move(ParamOffsets),
213-
/*HasThisPointer=*/false, /*HasRVO=*/false);
214-
215-
assert(Func);
216-
Func->setDefined(true);
217-
// We don't compile the BlockDecl code at all right now.
218-
Func->setIsFullyCompiled(true);
219-
return Func;
22093
}
22194

22295
Scope::Local ByteCodeEmitter::createLocal(Descriptor *D) {

clang/lib/AST/ByteCode/ByteCodeEmitter.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ class ByteCodeEmitter {
3131

3232
public:
3333
/// Compiles the function into the module.
34-
Function *compileFunc(const FunctionDecl *FuncDecl);
35-
Function *compileObjCBlock(const BlockExpr *BE);
34+
void compileFunc(const FunctionDecl *FuncDecl, Function *Func = nullptr);
3635

3736
protected:
3837
ByteCodeEmitter(Context &Ctx, Program &P) : Ctx(Ctx), P(P) {}

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3576,7 +3576,7 @@ bool Compiler<Emitter>::VisitBlockExpr(const BlockExpr *E) {
35763576
return true;
35773577

35783578
const Function *Func = nullptr;
3579-
if (auto F = Compiler<ByteCodeEmitter>(Ctx, P).compileObjCBlock(E))
3579+
if (auto F = Ctx.getOrCreateObjCBlock(E))
35803580
Func = F;
35813581

35823582
if (!Func)

0 commit comments

Comments
 (0)