Skip to content

Commit bca39f4

Browse files
authored
[clang][bytecode] Add a scope to function calls (llvm#140441)
We need a place to destroy the temporaries created for call arguments.
1 parent 1b711b2 commit bca39f4

File tree

3 files changed

+121
-73
lines changed

3 files changed

+121
-73
lines changed

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 70 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@ template <class Emitter> class DeclScope final : public LocalScope<Emitter> {
4545
Ctx->InitStack.push_back(InitLink::Decl(VD));
4646
}
4747

48-
void addExtended(const Scope::Local &Local) override {
49-
return this->addLocal(Local);
50-
}
51-
5248
~DeclScope() {
5349
this->Ctx->InitializingDecl = OldInitializingDecl;
5450
this->Ctx->InitStack.pop_back();
@@ -2021,6 +2017,45 @@ bool Compiler<Emitter>::visitArrayElemInit(unsigned ElemIndex, const Expr *Init,
20212017
return this->emitFinishInitPop(Init);
20222018
}
20232019

2020+
template <class Emitter>
2021+
bool Compiler<Emitter>::visitCallArgs(ArrayRef<const Expr *> Args,
2022+
const FunctionDecl *FuncDecl) {
2023+
assert(VarScope->getKind() == ScopeKind::Call);
2024+
llvm::BitVector NonNullArgs = collectNonNullArgs(FuncDecl, Args);
2025+
2026+
unsigned ArgIndex = 0;
2027+
for (const Expr *Arg : Args) {
2028+
if (std::optional<PrimType> T = classify(Arg)) {
2029+
if (!this->visit(Arg))
2030+
return false;
2031+
} else {
2032+
2033+
std::optional<unsigned> LocalIndex = allocateLocal(
2034+
Arg, Arg->getType(), /*ExtendingDecl=*/nullptr, ScopeKind::Call);
2035+
if (!LocalIndex)
2036+
return false;
2037+
2038+
if (!this->emitGetPtrLocal(*LocalIndex, Arg))
2039+
return false;
2040+
InitLinkScope<Emitter> ILS(this, InitLink::Temp(*LocalIndex));
2041+
if (!this->visitInitializer(Arg))
2042+
return false;
2043+
}
2044+
2045+
if (FuncDecl && NonNullArgs[ArgIndex]) {
2046+
PrimType ArgT = classify(Arg).value_or(PT_Ptr);
2047+
if (ArgT == PT_Ptr) {
2048+
if (!this->emitCheckNonNullArg(ArgT, Arg))
2049+
return false;
2050+
}
2051+
}
2052+
2053+
++ArgIndex;
2054+
}
2055+
2056+
return true;
2057+
}
2058+
20242059
template <class Emitter>
20252060
bool Compiler<Emitter>::VisitInitListExpr(const InitListExpr *E) {
20262061
return this->visitInitList(E->inits(), E->getArrayFiller(), E);
@@ -4343,7 +4378,7 @@ bool Compiler<Emitter>::emitConst(const APSInt &Value, const Expr *E) {
43434378
template <class Emitter>
43444379
unsigned Compiler<Emitter>::allocateLocalPrimitive(
43454380
DeclTy &&Src, PrimType Ty, bool IsConst, const ValueDecl *ExtendingDecl,
4346-
bool IsConstexprUnknown) {
4381+
ScopeKind SC, bool IsConstexprUnknown) {
43474382
// Make sure we don't accidentally register the same decl twice.
43484383
if (const auto *VD =
43494384
dyn_cast_if_present<ValueDecl>(Src.dyn_cast<const Decl *>())) {
@@ -4364,14 +4399,14 @@ unsigned Compiler<Emitter>::allocateLocalPrimitive(
43644399
if (ExtendingDecl)
43654400
VarScope->addExtended(Local, ExtendingDecl);
43664401
else
4367-
VarScope->add(Local, false);
4402+
VarScope->addForScopeKind(Local, SC);
43684403
return Local.Offset;
43694404
}
43704405

43714406
template <class Emitter>
43724407
std::optional<unsigned>
43734408
Compiler<Emitter>::allocateLocal(DeclTy &&Src, QualType Ty,
4374-
const ValueDecl *ExtendingDecl,
4409+
const ValueDecl *ExtendingDecl, ScopeKind SC,
43754410
bool IsConstexprUnknown) {
43764411
// Make sure we don't accidentally register the same decl twice.
43774412
if ([[maybe_unused]] const auto *VD =
@@ -4409,7 +4444,7 @@ Compiler<Emitter>::allocateLocal(DeclTy &&Src, QualType Ty,
44094444
if (ExtendingDecl)
44104445
VarScope->addExtended(Local, ExtendingDecl);
44114446
else
4412-
VarScope->add(Local, false);
4447+
VarScope->addForScopeKind(Local, SC);
44134448
return Local.Offset;
44144449
}
44154450

@@ -4676,7 +4711,7 @@ VarCreationState Compiler<Emitter>::visitVarDecl(const VarDecl *VD,
46764711
if (VarT) {
46774712
unsigned Offset = this->allocateLocalPrimitive(
46784713
VD, *VarT, VD->getType().isConstQualified(), nullptr,
4679-
IsConstexprUnknown);
4714+
ScopeKind::Block, IsConstexprUnknown);
46804715
if (Init) {
46814716
// If this is a toplevel declaration, create a scope for the
46824717
// initializer.
@@ -4692,8 +4727,9 @@ VarCreationState Compiler<Emitter>::visitVarDecl(const VarDecl *VD,
46924727
}
46934728
}
46944729
} else {
4695-
if (std::optional<unsigned> Offset = this->allocateLocal(
4696-
VD, VD->getType(), nullptr, IsConstexprUnknown)) {
4730+
if (std::optional<unsigned> Offset =
4731+
this->allocateLocal(VD, VD->getType(), nullptr, ScopeKind::Block,
4732+
IsConstexprUnknown)) {
46974733
if (!Init)
46984734
return true;
46994735

@@ -4881,26 +4917,28 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
48814917
if (FuncDecl) {
48824918
if (unsigned BuiltinID = FuncDecl->getBuiltinID())
48834919
return VisitBuiltinCallExpr(E, BuiltinID);
4884-
}
48854920

4886-
// Calls to replaceable operator new/operator delete.
4887-
if (FuncDecl &&
4888-
FuncDecl->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) {
4889-
if (FuncDecl->getDeclName().isAnyOperatorNew()) {
4890-
return VisitBuiltinCallExpr(E, Builtin::BI__builtin_operator_new);
4891-
} else {
4892-
assert(FuncDecl->getDeclName().getCXXOverloadedOperator() == OO_Delete);
4893-
return VisitBuiltinCallExpr(E, Builtin::BI__builtin_operator_delete);
4921+
// Calls to replaceable operator new/operator delete.
4922+
if (FuncDecl->isUsableAsGlobalAllocationFunctionInConstantEvaluation()) {
4923+
if (FuncDecl->getDeclName().isAnyOperatorNew()) {
4924+
return VisitBuiltinCallExpr(E, Builtin::BI__builtin_operator_new);
4925+
} else {
4926+
assert(FuncDecl->getDeclName().getCXXOverloadedOperator() == OO_Delete);
4927+
return VisitBuiltinCallExpr(E, Builtin::BI__builtin_operator_delete);
4928+
}
4929+
}
4930+
4931+
// Explicit calls to trivial destructors
4932+
if (const auto *DD = dyn_cast<CXXDestructorDecl>(FuncDecl);
4933+
DD && DD->isTrivial()) {
4934+
const auto *MemberCall = cast<CXXMemberCallExpr>(E);
4935+
if (!this->visit(MemberCall->getImplicitObjectArgument()))
4936+
return false;
4937+
return this->emitCheckDestruction(E) && this->emitPopPtr(E);
48944938
}
48954939
}
4896-
// Explicit calls to trivial destructors
4897-
if (const auto *DD = dyn_cast_if_present<CXXDestructorDecl>(FuncDecl);
4898-
DD && DD->isTrivial()) {
4899-
const auto *MemberCall = cast<CXXMemberCallExpr>(E);
4900-
if (!this->visit(MemberCall->getImplicitObjectArgument()))
4901-
return false;
4902-
return this->emitCheckDestruction(E) && this->emitPopPtr(E);
4903-
}
4940+
4941+
BlockScope<Emitter> CallScope(this, ScopeKind::Call);
49044942

49054943
QualType ReturnType = E->getCallReturnType(Ctx.getASTContext());
49064944
std::optional<PrimType> T = classify(ReturnType);
@@ -4996,23 +5034,8 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
49965034
return false;
49975035
}
49985036

4999-
llvm::BitVector NonNullArgs = collectNonNullArgs(FuncDecl, Args);
5000-
// Put arguments on the stack.
5001-
unsigned ArgIndex = 0;
5002-
for (const auto *Arg : Args) {
5003-
if (!this->visit(Arg))
5004-
return false;
5005-
5006-
// If we know the callee already, check the known parametrs for nullability.
5007-
if (FuncDecl && NonNullArgs[ArgIndex]) {
5008-
PrimType ArgT = classify(Arg).value_or(PT_Ptr);
5009-
if (ArgT == PT_Ptr) {
5010-
if (!this->emitCheckNonNullArg(ArgT, Arg))
5011-
return false;
5012-
}
5013-
}
5014-
++ArgIndex;
5015-
}
5037+
if (!this->visitCallArgs(Args, FuncDecl))
5038+
return false;
50165039

50175040
// Undo the argument reversal we did earlier.
50185041
if (IsAssignmentOperatorCall) {
@@ -5088,9 +5111,9 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
50885111

50895112
// Cleanup for discarded return values.
50905113
if (DiscardResult && !ReturnType->isVoidType() && T)
5091-
return this->emitPop(*T, E);
5114+
return this->emitPop(*T, E) && CallScope.destroyLocals();
50925115

5093-
return true;
5116+
return CallScope.destroyLocals();
50945117
}
50955118

50965119
template <class Emitter>

clang/lib/AST/ByteCode/Compiler.h

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ struct VarCreationState {
102102
bool notCreated() const { return !S; }
103103
};
104104

105+
enum class ScopeKind { Call, Block };
106+
105107
/// Compilation context for expressions.
106108
template <class Emitter>
107109
class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
@@ -305,17 +307,19 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
305307
const Expr *E);
306308
bool visitArrayElemInit(unsigned ElemIndex, const Expr *Init,
307309
std::optional<PrimType> InitT);
310+
bool visitCallArgs(ArrayRef<const Expr *> Args, const FunctionDecl *FuncDecl);
308311

309312
/// Creates a local primitive value.
310313
unsigned allocateLocalPrimitive(DeclTy &&Decl, PrimType Ty, bool IsConst,
311314
const ValueDecl *ExtendingDecl = nullptr,
315+
ScopeKind SC = ScopeKind::Block,
312316
bool IsConstexprUnknown = false);
313317

314318
/// Allocates a space storing a local given its type.
315319
std::optional<unsigned>
316320
allocateLocal(DeclTy &&Decl, QualType Ty = QualType(),
317321
const ValueDecl *ExtendingDecl = nullptr,
318-
bool IsConstexprUnknown = false);
322+
ScopeKind = ScopeKind::Block, bool IsConstexprUnknown = false);
319323
std::optional<unsigned> allocateTemporary(const Expr *E);
320324

321325
private:
@@ -451,28 +455,16 @@ extern template class Compiler<EvalEmitter>;
451455
/// Scope chain managing the variable lifetimes.
452456
template <class Emitter> class VariableScope {
453457
public:
454-
VariableScope(Compiler<Emitter> *Ctx, const ValueDecl *VD)
455-
: Ctx(Ctx), Parent(Ctx->VarScope), ValDecl(VD) {
458+
VariableScope(Compiler<Emitter> *Ctx, const ValueDecl *VD,
459+
ScopeKind Kind = ScopeKind::Block)
460+
: Ctx(Ctx), Parent(Ctx->VarScope), ValDecl(VD), Kind(Kind) {
456461
Ctx->VarScope = this;
457462
}
458463

459464
virtual ~VariableScope() { Ctx->VarScope = this->Parent; }
460465

461-
void add(const Scope::Local &Local, bool IsExtended) {
462-
if (IsExtended)
463-
this->addExtended(Local);
464-
else
465-
this->addLocal(Local);
466-
}
467-
468466
virtual void addLocal(const Scope::Local &Local) {
469-
if (this->Parent)
470-
this->Parent->addLocal(Local);
471-
}
472-
473-
virtual void addExtended(const Scope::Local &Local) {
474-
if (this->Parent)
475-
this->Parent->addExtended(Local);
467+
llvm_unreachable("Shouldn't be called");
476468
}
477469

478470
void addExtended(const Scope::Local &Local, const ValueDecl *ExtendingDecl) {
@@ -496,23 +488,43 @@ template <class Emitter> class VariableScope {
496488
this->addLocal(Local);
497489
}
498490

491+
/// Like addExtended, but adds to the nearest scope of the given kind.
492+
void addForScopeKind(const Scope::Local &Local, ScopeKind Kind) {
493+
VariableScope *P = this;
494+
while (P) {
495+
if (P->Kind == Kind) {
496+
P->addLocal(Local);
497+
return;
498+
}
499+
P = P->Parent;
500+
if (!P)
501+
break;
502+
}
503+
504+
// Add to this scope.
505+
this->addLocal(Local);
506+
}
507+
499508
virtual void emitDestruction() {}
500509
virtual bool emitDestructors(const Expr *E = nullptr) { return true; }
501510
virtual bool destroyLocals(const Expr *E = nullptr) { return true; }
502511
VariableScope *getParent() const { return Parent; }
512+
ScopeKind getKind() const { return Kind; }
503513

504514
protected:
505515
/// Compiler instance.
506516
Compiler<Emitter> *Ctx;
507517
/// Link to the parent scope.
508518
VariableScope *Parent;
509519
const ValueDecl *ValDecl = nullptr;
520+
ScopeKind Kind;
510521
};
511522

512523
/// Generic scope for local variables.
513524
template <class Emitter> class LocalScope : public VariableScope<Emitter> {
514525
public:
515-
LocalScope(Compiler<Emitter> *Ctx) : VariableScope<Emitter>(Ctx, nullptr) {}
526+
LocalScope(Compiler<Emitter> *Ctx, ScopeKind Kind = ScopeKind::Block)
527+
: VariableScope<Emitter>(Ctx, nullptr, Kind) {}
516528
LocalScope(Compiler<Emitter> *Ctx, const ValueDecl *VD)
517529
: VariableScope<Emitter>(Ctx, VD) {}
518530

@@ -599,16 +611,11 @@ template <class Emitter> class LocalScope : public VariableScope<Emitter> {
599611
};
600612

601613
/// Scope for storage declared in a compound statement.
614+
// FIXME: Remove?
602615
template <class Emitter> class BlockScope final : public LocalScope<Emitter> {
603616
public:
604-
BlockScope(Compiler<Emitter> *Ctx) : LocalScope<Emitter>(Ctx) {}
605-
606-
void addExtended(const Scope::Local &Local) override {
607-
// If we to this point, just add the variable as a normal local
608-
// variable. It will be destroyed at the end of the block just
609-
// like all others.
610-
this->addLocal(Local);
611-
}
617+
BlockScope(Compiler<Emitter> *Ctx, ScopeKind Kind = ScopeKind::Block)
618+
: LocalScope<Emitter>(Ctx, Kind) {}
612619
};
613620

614621
template <class Emitter> class ArrayIndexScope final {

clang/test/AST/ByteCode/lifetimes.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,21 @@ namespace PseudoDtor {
8888
// both-note {{visible outside that expression}}
8989
};
9090
}
91+
92+
/// Diagnostic differences
93+
namespace CallScope {
94+
struct Q {
95+
int n = 0;
96+
constexpr int f() const { return 0; }
97+
};
98+
constexpr Q *out_of_lifetime(Q q) { return &q; } // both-warning {{address of stack}}
99+
constexpr int k3 = out_of_lifetime({})->n; // both-error {{must be initialized by a constant expression}} \
100+
// expected-note {{read of temporary whose lifetime has ended}} \
101+
// expected-note {{temporary created here}} \
102+
// ref-note {{read of object outside its lifetime}}
103+
104+
constexpr int k4 = out_of_lifetime({})->f(); // both-error {{must be initialized by a constant expression}} \
105+
// expected-note {{member call on temporary whose lifetime has ended}} \
106+
// expected-note {{temporary created here}} \
107+
// ref-note {{member call on object outside its lifetime}}
108+
}

0 commit comments

Comments
 (0)