Author: Timm Baeder
Date: 2026-09-20T07:35:08+02:00
New Revision: 780937f68bfd726b3e45dd2de14d0d152e075147

URL: 
https://github.com/llvm/llvm-project/commit/780937f68bfd726b3e45dd2de14d0d152e075147
DIFF: 
https://github.com/llvm/llvm-project/commit/780937f68bfd726b3e45dd2de14d0d152e075147.diff

LOG: [clang][bytecode] Add a special opcode for trivial default initializers 
(#224839)

Trivial default initializers from modules are not implicitly defined in
the AST, so we would diagnose the attached test case as an undefined
constructor. Do what the current interpreter does and special-case this.

Fixes https://github.com/llvm/llvm-project/issues/221400

Added: 
    clang/test/AST/ByteCode/Inputs/default-ctor.cppm
    clang/test/AST/ByteCode/module-default-ctor.cpp

Modified: 
    clang/lib/AST/ByteCode/Compiler.cpp
    clang/lib/AST/ByteCode/Interp.cpp
    clang/lib/AST/ByteCode/Interp.h
    clang/lib/AST/ByteCode/Opcodes.td

Removed: 
    


################################################################################
diff  --git a/clang/lib/AST/ByteCode/Compiler.cpp 
b/clang/lib/AST/ByteCode/Compiler.cpp
index a2a3516ac3d78..2d5dbdfda003c 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -3903,6 +3903,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);


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to