[clang] [clang][bytecode] Add a special opcode for trivial default initializers (PR #224839)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Sat Sep 19 08:53:51 PDT 2026
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/224839
>From cd1b6f9af512f86e7a80e4a6230b3b56b503dde3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Sat, 19 Sep 2026 15:47:00 +0200
Subject: [PATCH] default ctor
---
clang/lib/AST/ByteCode/Compiler.cpp | 10 +++
clang/lib/AST/ByteCode/Interp.cpp | 63 +++++++++++++++++++
clang/lib/AST/ByteCode/Interp.h | 1 +
clang/lib/AST/ByteCode/Opcodes.td | 5 ++
.../AST/ByteCode/Inputs/default-ctor.cppm | 12 ++++
.../test/AST/ByteCode/module-default-ctor.cpp | 11 ++++
6 files changed, 102 insertions(+)
create mode 100644 clang/test/AST/ByteCode/Inputs/default-ctor.cppm
create mode 100644 clang/test/AST/ByteCode/module-default-ctor.cpp
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index d7bdb2f217a9a..83b118c18b7aa 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -3899,6 +3899,16 @@ bool Compiler<Emitter>::VisitCXXConstructExpr(const CXXConstructExpr *E) {
return true;
}
+ // Trivial default constructors might never be implicitly defined by the
+ // AST, so we need to special-case them here.
+ if (Ctor->isTrivial() && Ctor->isDefaultConstructor()) {
+ if (!this->emitDefaultInit(Ctor, E))
+ return false;
+ if (DiscardResult)
+ return this->emitPopPtr(E);
+ return true;
+ }
+
// Avoid materializing a temporary for an elidable copy/move constructor.
if (!ZeroInit && E->isElidable()) {
const Expr *SrcObj = E->getArg(0);
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index ab6b474503a3b..dde7e54d86977 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2683,6 +2683,69 @@ bool MarkDestroyed(InterpState &S, CodePtr OpPC) {
return true;
}
+// Initializes all bases and virtual bases.
+// Only starts the lifetime of fields, but doesn't initialize them.
+static void initBasesRecurse(PtrView Ptr) {
+ assert(Ptr.getRecord());
+
+ const Record *R = Ptr.getRecord();
+ for (const Record::Base &B : R->bases()) {
+ PtrView BasePtr = Ptr.atField(B.Offset);
+ BasePtr.initialize();
+ BasePtr.startLifetime();
+ initBasesRecurse(BasePtr);
+ }
+
+ for (const Record::Field &F : R->fields()) {
+ PtrView FieldPtr = Ptr.atField(F.Offset);
+ FieldPtr.startLifetime();
+ if (FieldPtr.getRecord())
+ initBasesRecurse(FieldPtr);
+ }
+
+ for (const Record::Base &B : R->virtual_bases()) {
+ PtrView BasePtr = Ptr.atField(B.Offset);
+ BasePtr.initialize();
+ BasePtr.startLifetime();
+ initBasesRecurse(BasePtr);
+ }
+}
+
+bool DefaultInit(InterpState &S, CodePtr OpPC, const CXXConstructorDecl *Ctor) {
+ auto Ptr = S.Stk.peek<Pointer>();
+
+ if (!Ptr.isBlockPointer())
+ return false;
+ const Record *R = Ptr.getRecord();
+ if (!R)
+ return false;
+
+ if (Ctor->isInvalidDecl() || Ctor->getParent()->isInvalidDecl())
+ return false;
+
+ if (!Ctor->isConstexpr()) {
+ if (S.getLangOpts().CPlusPlus11) {
+ // FIXME: If DiagDecl is an implicitly-declared special member function,
+ // we should be much more explicit about why it's not constexpr.
+ S.CCEDiag(S.Current->getSource(OpPC),
+ diag::note_constexpr_invalid_function, 1)
+ << /*IsConstexpr*/ 0 << /*IsConstructor*/ 1 << Ctor;
+ S.Note(Ctor->getLocation(), diag::note_declared_at);
+ } else {
+ S.CCEDiag(S.Current->getSource(OpPC),
+ diag::note_invalid_subexpr_in_const_expr);
+ }
+ }
+
+ Ptr.startLifetime();
+ Ptr.initialize();
+
+ startLifetimeRecurse(Ptr.view());
+ initBasesRecurse(Ptr.view());
+
+ return true;
+}
+
bool CheckNewTypeMismatch(InterpState &S, CodePtr OpPC, const Expr *E,
std::optional<uint64_t> ArraySize) {
Pointer &Orig = S.Stk.peek<Pointer>();
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index edbf2adcda637..f7d396b720767 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -1642,6 +1642,7 @@ bool PseudoDtor(InterpState &S, CodePtr OpPC);
bool StartThisLifetime(InterpState &S);
bool StartThisLifetime1(InterpState &S);
bool MarkDestroyed(InterpState &S, CodePtr OpPC);
+bool DefaultInit(InterpState &S, CodePtr OpPC, const CXXConstructorDecl *Ctor);
/// 1) Pops the value from the stack.
/// 2) Writes the value to the local variable with the
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index 11ba6ddf41b74..afed0014c1a10 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -73,6 +73,7 @@ def ArgPrimType : ArgType { let Name = "PrimType"; }
def ArgEnumDecl : ArgType { let Name = "const EnumDecl *"; }
def ArgTypePtr : ArgType { let Name = "const Type *"; }
def ArgDeclOrExpr : ArgType { let Name = "DeclOrExpr"; }
+def ArgCtorDecl : ArgType { let Name = "const CXXConstructorDecl *"; }
//===----------------------------------------------------------------------===//
// Classes of types instructions operate on.
@@ -479,6 +480,10 @@ def StartThisLifetime1 : Opcode {
let NeedsOpPC = 0;
}
+def DefaultInit : Opcode {
+ let Args = [ArgCtorDecl];
+}
+
def CheckDecl : Opcode {
let Args = [ArgVarDecl];
let NeedsOpPC = 0;
diff --git a/clang/test/AST/ByteCode/Inputs/default-ctor.cppm b/clang/test/AST/ByteCode/Inputs/default-ctor.cppm
new file mode 100644
index 0000000000000..ee945435eaea1
--- /dev/null
+++ b/clang/test/AST/ByteCode/Inputs/default-ctor.cppm
@@ -0,0 +1,12 @@
+export module m;
+
+export struct allocator_like {
+ constexpr allocator_like() noexcept = default;
+};
+
+export struct box {
+ allocator_like allocation;
+ int value = 42;
+ constexpr box() = default;
+ [[nodiscard]] constexpr int get() const { return value; }
+};
diff --git a/clang/test/AST/ByteCode/module-default-ctor.cpp b/clang/test/AST/ByteCode/module-default-ctor.cpp
new file mode 100644
index 0000000000000..4883b2a6e28ba
--- /dev/null
+++ b/clang/test/AST/ByteCode/module-default-ctor.cpp
@@ -0,0 +1,11 @@
+// RUN: mkdir -p %t
+// RUN: %clang -std=c++20 %p/Inputs/default-ctor.cppm --precompile -o %t/default-ctor.pcm
+// RUN: %clang -c -std=c++20 -fmodule-file=m=%t/default-ctor.pcm -fexperimental-new-constant-interpreter %s
+import m;
+
+
+consteval int evaluate() {
+ box b;
+ return b.get();
+}
+static_assert(evaluate() == 42);
More information about the cfe-commits
mailing list