Skip to content

[clang][bytecode] Use Param decl as variable source if we can #152909

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions clang/lib/AST/ByteCode/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2062,21 +2062,36 @@ bool Compiler<Emitter>::visitArrayElemInit(unsigned ElemIndex, const Expr *Init,
template <class Emitter>
bool Compiler<Emitter>::visitCallArgs(ArrayRef<const Expr *> Args,
const FunctionDecl *FuncDecl,
bool Activate) {
bool Activate, bool IsOperatorCall) {
assert(VarScope->getKind() == ScopeKind::Call);
llvm::BitVector NonNullArgs;
if (FuncDecl && FuncDecl->hasAttr<NonNullAttr>())
NonNullArgs = collectNonNullArgs(FuncDecl, Args);

bool ExplicitMemberFn = false;
if (const auto *MD = dyn_cast_if_present<CXXMethodDecl>(FuncDecl))
ExplicitMemberFn = MD->isExplicitObjectMemberFunction();

unsigned ArgIndex = 0;
for (const Expr *Arg : Args) {
if (canClassify(Arg)) {
if (!this->visit(Arg))
return false;
} else {

std::optional<unsigned> LocalIndex = allocateLocal(
Arg, Arg->getType(), /*ExtendingDecl=*/nullptr, ScopeKind::Call);
DeclTy Source = Arg;
if (FuncDecl) {
// Try to use the parameter declaration instead of the argument
// expression as a source.
unsigned DeclIndex = ArgIndex - IsOperatorCall + ExplicitMemberFn;
if (DeclIndex < FuncDecl->getNumParams())
Source = FuncDecl->getParamDecl(ArgIndex - IsOperatorCall +
ExplicitMemberFn);
}

std::optional<unsigned> LocalIndex =
allocateLocal(std::move(Source), Arg->getType(),
/*ExtendingDecl=*/nullptr, ScopeKind::Call);
if (!LocalIndex)
return false;

Expand Down Expand Up @@ -4489,14 +4504,6 @@ template <class Emitter>
unsigned Compiler<Emitter>::allocateLocalPrimitive(
DeclTy &&Src, PrimType Ty, bool IsConst, const ValueDecl *ExtendingDecl,
ScopeKind SC, bool IsConstexprUnknown) {
// Make sure we don't accidentally register the same decl twice.
if (const auto *VD =
dyn_cast_if_present<ValueDecl>(Src.dyn_cast<const Decl *>())) {
assert(!P.getGlobal(VD));
assert(!Locals.contains(VD));
(void)VD;
}

// FIXME: There are cases where Src.is<Expr*>() is wrong, e.g.
// (int){12} in C. Consider using Expr::isTemporaryObject() instead
// or isa<MaterializeTemporaryExpr>().
Expand All @@ -4518,19 +4525,11 @@ std::optional<unsigned>
Compiler<Emitter>::allocateLocal(DeclTy &&Src, QualType Ty,
const ValueDecl *ExtendingDecl, ScopeKind SC,
bool IsConstexprUnknown) {
// Make sure we don't accidentally register the same decl twice.
if ([[maybe_unused]] const auto *VD =
dyn_cast_if_present<ValueDecl>(Src.dyn_cast<const Decl *>())) {
assert(!P.getGlobal(VD));
assert(!Locals.contains(VD));
}

const ValueDecl *Key = nullptr;
const Expr *Init = nullptr;
bool IsTemporary = false;
if (auto *VD = dyn_cast_if_present<ValueDecl>(Src.dyn_cast<const Decl *>())) {
Key = VD;
Ty = VD->getType();

if (const auto *VarD = dyn_cast<VarDecl>(VD))
Init = VarD->getInit();
Expand Down Expand Up @@ -5167,7 +5166,8 @@ bool Compiler<Emitter>::VisitCallExpr(const CallExpr *E) {
return false;
}

if (!this->visitCallArgs(Args, FuncDecl, IsAssignmentOperatorCall))
if (!this->visitCallArgs(Args, FuncDecl, IsAssignmentOperatorCall,
isa<CXXOperatorCallExpr>(E)))
return false;

// Undo the argument reversal we did earlier.
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/ByteCode/Compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
bool visitArrayElemInit(unsigned ElemIndex, const Expr *Init,
OptPrimType InitT);
bool visitCallArgs(ArrayRef<const Expr *> Args, const FunctionDecl *FuncDecl,
bool Activate);
bool Activate, bool IsOperatorCall);

/// Creates a local primitive value.
unsigned allocateLocalPrimitive(DeclTy &&Decl, PrimType Ty, bool IsConst,
Expand Down
5 changes: 2 additions & 3 deletions clang/test/AST/ByteCode/cxx23.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -309,15 +309,14 @@ namespace NonLiteralDtorInParam {
~NonLiteral() {} // all23-note {{declared here}}
};
constexpr int F2(NonLiteral N) { // all20-error {{constexpr function's 1st parameter type 'NonLiteral' is not a literal type}} \
// ref23-note {{non-constexpr function '~NonLiteral' cannot be used in a constant expression}}
// all23-note {{non-constexpr function '~NonLiteral' cannot be used in a constant expression}}
return 8;
}


void test() {
NonLiteral L;
constexpr auto D = F2(L); // all23-error {{must be initialized by a constant expression}} \
// expected23-note {{non-constexpr function '~NonLiteral' cannot be used in a constant expression}}
constexpr auto D = F2(L); // all23-error {{must be initialized by a constant expression}}
}
}

Expand Down
9 changes: 4 additions & 5 deletions clang/test/AST/ByteCode/lifetimes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,13 @@ namespace CallScope {
int n = 0;
constexpr int f() const { return 0; }
};
constexpr Q *out_of_lifetime(Q q) { return &q; } // both-warning {{address of stack}}
constexpr Q *out_of_lifetime(Q q) { return &q; } // both-warning {{address of stack}} \
// expected-note 2{{declared here}}
constexpr int k3 = out_of_lifetime({})->n; // both-error {{must be initialized by a constant expression}} \
// expected-note {{read of temporary whose lifetime has ended}} \
// expected-note {{temporary created here}} \
// expected-note {{read of variable whose lifetime has ended}} \
// ref-note {{read of object outside its lifetime}}

constexpr int k4 = out_of_lifetime({})->f(); // both-error {{must be initialized by a constant expression}} \
// expected-note {{member call on temporary whose lifetime has ended}} \
// expected-note {{temporary created here}} \
// expected-note {{member call on variable whose lifetime has ended}} \
// ref-note {{member call on object outside its lifetime}}
}
1 change: 1 addition & 0 deletions clang/test/SemaCXX/cxx23-invalid-constexpr.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -verify=expected -std=c++23 %s
// RUN: %clang_cc1 -fsyntax-only -verify=expected -std=c++23 %s -fexperimental-new-constant-interpreter

// This test covers modifications made by P2448R2.

Expand Down