1//===--- InterpFrame.cpp - Call Frame implementation for the VM -*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "InterpFrame.h"
10#include "Boolean.h"
11#include "Char.h"
12#include "Function.h"
13#include "InterpStack.h"
14#include "InterpState.h"
15#include "MemberPointer.h"
16#include "Pointer.h"
17#include "PrimType.h"
18#include "clang/AST/ASTContext.h"
19#include "clang/AST/DeclCXX.h"
20#include "clang/AST/ExprCXX.h"
21
22using namespace clang;
23using namespace clang::interp;
24
25InterpFrame::InterpFrame(InterpState &S)
26 : Caller(nullptr), S(S), Func(nullptr), RetPC(CodePtr()), Args(nullptr),
27 ArgSize(0) {}
28
29InterpFrame::InterpFrame(InterpState &S, const Function *Func,
30 InterpFrame *Caller, CodePtr RetPC, unsigned ArgSize)
31 : Caller(Caller), S(S), Func(Func), RetPC(RetPC),
32 Args(static_cast<char *>(S.Stk.top())), ArgSize(ArgSize) {
33 assert(Func);
34#ifndef NDEBUG
35 FrameOffset = S.Stk.size();
36#endif
37
38 FuncFlags |= Func->hasRVO() * HasRVOFlag;
39 FuncFlags |= Func->hasThisPointer() * HasThisFlag;
40
41 // Initialize argument blocks.
42 for (unsigned I = 0, N = Func->getNumWrittenParams(); I != N; ++I)
43 new (argBlock(Index: I)) Block(S.EvalID, Func->getParamDescriptor(Index: I).Desc);
44
45 if (Func->getFrameSize() == 0)
46 return;
47
48 for (auto &Scope : Func->scopes()) {
49 for (auto &Local : Scope.locals()) {
50 new (localBlock(Offset: Local.Offset))
51 Block(S.EvalID, Local.Desc, Block::InlineDescMD);
52 // Note that we are NOT calling invokeCtor() here, since that is done
53 // via the InitScope op.
54 new (localInlineDesc(Offset: Local.Offset)) InlineDescriptor(Local.Desc);
55 }
56 }
57}
58
59InterpFrame::InterpFrame(InterpState &S, const Function *Func, CodePtr RetPC,
60 unsigned VarArgSize)
61 : InterpFrame(S, Func, S.Current, RetPC, Func->getArgSize() + VarArgSize) {
62 // As per our calling convention, the this pointer is
63 // part of the ArgSize.
64 // If the function has RVO, the RVO pointer is first.
65 // If the fuction has a This pointer, that one is next.
66 // Then follow the actual arguments (but those are handled
67 // in getParamPointer()).
68}
69
70InterpFrame::~InterpFrame() {
71 if (!Func)
72 return;
73
74 // De-initialize all argument blocks.
75 for (unsigned I = 0, N = Func->getNumWrittenParams(); I != N; ++I)
76 S.deallocate(B: argBlock(Index: I));
77
78 // When destroying the InterpFrame, call the Dtor for all blocks
79 // that haven't been destroyed via a destroy() op yet.
80 // This happens when the execution is interruped midway-through.
81 for (auto &Scope : Func->scopes()) {
82 for (auto &Local : Scope.locals()) {
83 S.deallocate(B: localBlock(Offset: Local.Offset));
84 }
85 }
86}
87
88void InterpFrame::initScope(unsigned Idx) {
89 if (!Func)
90 return;
91
92 for (auto &Local : Func->getScope(Idx).locals()) {
93 assert(!localBlock(Local.Offset)->isInitialized());
94 localBlock(Offset: Local.Offset)->invokeCtor();
95 }
96}
97
98void InterpFrame::enableLocal(unsigned Idx) {
99 assert(Func);
100
101 // FIXME: This is a little dirty, but to avoid adding a flag to
102 // InlineDescriptor that's only ever useful on the toplevel of local
103 // variables, we reuse the IsActive flag for the enabled state. We should
104 // probably use a different struct than InlineDescriptor for the block-level
105 // inline descriptor of local varaibles.
106 localInlineDesc(Offset: Idx)->IsActive = true;
107}
108
109void InterpFrame::destroy(unsigned Idx) {
110 for (auto &Local : Func->getScope(Idx).locals_reverse()) {
111 S.deallocate(B: localBlock(Offset: Local.Offset));
112 }
113}
114
115template <typename T>
116static void print(llvm::raw_ostream &OS, const T &V, const Context &Ctx,
117 QualType Ty) {
118 if constexpr (std::is_same_v<Pointer, T>) {
119 if (Ty->isPointerOrReferenceType())
120 V.toAPValue(Ctx.getASTContext()).printPretty(OS, Ctx.getASTContext(), Ty);
121 else {
122 if (std::optional<APValue> RValue = V.toRValue(Ctx, Ty))
123 RValue->printPretty(OS, Ctx: Ctx.getASTContext(), Ty);
124 else
125 OS << "...";
126 }
127 } else {
128 V.toAPValue(Ctx.getASTContext()).printPretty(OS, Ctx.getASTContext(), Ty);
129 }
130}
131
132static bool shouldSkipInBacktrace(const Function *F) {
133 if (F->isLambdaStaticInvoker())
134 return true;
135
136 const FunctionDecl *FD = F->getDecl();
137 if (FD->getDeclName().getCXXOverloadedOperator() == OO_New ||
138 FD->getDeclName().getCXXOverloadedOperator() == OO_Array_New)
139 return true;
140
141 if (const auto *MD = dyn_cast<CXXMethodDecl>(Val: FD);
142 MD && MD->getParent()->isAnonymousStructOrUnion())
143 return true;
144
145 return false;
146}
147
148void InterpFrame::describe(llvm::raw_ostream &OS) const {
149 assert(Func);
150 // For lambda static invokers, we would just print __invoke().
151 if (shouldSkipInBacktrace(F: Func))
152 return;
153
154 const ASTContext &ASTCtx = S.getASTContext();
155 const Expr *CallExpr = Caller->getExpr(PC: getRetOpPC());
156 const FunctionDecl *F = Func->getDecl();
157 auto PrintingPolicy = ASTCtx.getPrintingPolicy();
158 PrintingPolicy.SuppressLambdaBody = true;
159
160 bool IsMemberCall = false;
161 bool ExplicitInstanceParam = false;
162 if (const auto *MD = dyn_cast<CXXMethodDecl>(Val: F)) {
163 IsMemberCall = !isa<CXXConstructorDecl>(Val: MD) && !MD->isStatic();
164 ExplicitInstanceParam = MD->isExplicitObjectMemberFunction();
165 }
166
167 if (Func->hasThisPointer() && IsMemberCall) {
168 if (const auto *MCE = dyn_cast_if_present<CXXMemberCallExpr>(Val: CallExpr)) {
169 const Expr *Object = MCE->getImplicitObjectArgument();
170 Object->printPretty(OS, /*Helper=*/nullptr,
171 Policy: PrintingPolicy,
172 /*Indentation=*/0);
173 if (Object->getType()->isPointerType())
174 OS << "->";
175 else
176 OS << '.';
177 } else if (const auto *OCE =
178 dyn_cast_if_present<CXXOperatorCallExpr>(Val: CallExpr)) {
179 OCE->getArg(Arg: 0)->printPretty(OS, /*Helper=*/nullptr,
180 Policy: PrintingPolicy,
181 /*Indentation=*/0);
182 OS << '.';
183 } else if (const auto *M = dyn_cast<CXXMethodDecl>(Val: F)) {
184 print(OS, V: getThis(), Ctx: S.getContext(),
185 Ty: ASTCtx.getLValueReferenceType(
186 T: ASTCtx.getCanonicalTagType(TD: M->getParent())));
187 OS << '.';
188 }
189 }
190
191 F->getNameForDiagnostic(OS, Policy: PrintingPolicy, /*Qualified=*/false);
192 OS << '(';
193 unsigned Off = 0;
194 unsigned ParamIndex = ExplicitInstanceParam;
195 Off += Func->hasRVO() ? primSize(Type: PT_Ptr) : 0;
196 Off += Func->hasThisPointer() ? primSize(Type: PT_Ptr) : 0;
197 llvm::ListSeparator Comma;
198 for (const ParmVarDecl *Param :
199 F->parameters().slice(N: ExplicitInstanceParam)) {
200 OS << Comma;
201 PrimType PrimT = Func->getParamDescriptor(Index: ParamIndex).T;
202 TYPE_SWITCH(PrimT,
203 print(OS, stackRef<T>(Off), S.getContext(), Param->getType()));
204 Off += align(Size: primSize(Type: PrimT));
205 ++ParamIndex;
206 }
207 OS << ')';
208}
209
210SourceRange InterpFrame::getCallRange() const {
211 if (!Caller->Func) {
212 if (SourceRange NullRange = S.getSource(PC: {}).getRange(); NullRange.isValid())
213 return NullRange;
214
215 return S.EvalLocation;
216 }
217
218 // Move up to the frame that has a valid location for the caller.
219 for (const InterpFrame *C = this; C; C = C->Caller) {
220 if (!C->RetPC)
221 continue;
222 SourceRange CallRange =
223 C->Caller->Func->getSource(PC: C->getRetOpPC()).getRange();
224 if (CallRange.isValid())
225 return CallRange;
226 }
227 return S.EvalLocation;
228}
229
230const FunctionDecl *InterpFrame::getCallee() const {
231 if (!Func)
232 return nullptr;
233 return Func->getDecl();
234}
235
236Pointer InterpFrame::getLocalPointer(unsigned Offset) const {
237 assert(Offset < Func->getFrameSize() && "Invalid local offset.");
238 return Pointer(localBlock(Offset));
239}
240
241Block *InterpFrame::getLocalBlock(unsigned Offset) const {
242 return localBlock(Offset);
243}
244
245Pointer InterpFrame::getParamPointer(unsigned Index) {
246 assert(!isBottomFrame());
247
248 Block *B = argBlock(Index);
249
250 // Copy the initial value.
251 if (!B->isInitialized()) {
252 unsigned ByteOffset = Func->getParamDescriptor(Index).Offset;
253 assert(B->getDescriptor()->isPrimitive());
254 B->invokeCtor();
255 TYPE_SWITCH(B->getDescriptor()->getPrimType(),
256 new (B->data()) T(stackRef<T>(ByteOffset)));
257 assert(B->isInitialized());
258 }
259
260 return Pointer(B);
261}
262
263static bool funcHasUsableBody(const Function *F) {
264 assert(F);
265
266 if (F->isConstructor() || F->isDestructor())
267 return true;
268
269 return !F->getDecl()->isImplicit();
270}
271
272SourceInfo InterpFrame::getSource(CodePtr PC) const {
273 if (!Func)
274 return S.getSource(PC);
275
276 // Implicitly created functions don't have any code we could point at,
277 // so return the call site.
278 if (Func && !funcHasUsableBody(F: Func) && Caller)
279 return Caller->getSource(PC: getRetOpPC());
280
281 // If we have a frame for an invalid function, check the caller.
282 // This happens for the fake function frame we create in TrivialCopy.
283 if (Func && !Func->isValid())
284 return Caller->getSource(PC: getRetOpPC());
285
286 // Similarly, if the resulting source location is invalid anyway,
287 // point to the caller instead.
288 SourceInfo Result = Func->getSource(PC);
289 if (Result.getLoc().isInvalid() && Caller)
290 return Caller->getSource(PC: getRetOpPC());
291
292 return Result;
293}
294
295bool InterpFrame::isStdFunction() const {
296 if (!Func)
297 return false;
298 for (const DeclContext *DC = Func->getDecl(); DC; DC = DC->getParent())
299 if (DC->isStdNamespace())
300 return true;
301
302 return false;
303}
304