[clang] [clang][Interp] IndirectMember initializers (PR #69900)

Timm Baeder via cfe-commits cfe-commits at lists.llvm.org
Mon Oct 23 00:30:49 PDT 2023


https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/69900

>From 214cb369b518adb6ef0a046731e0e16133740917 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Sun, 22 Oct 2023 19:47:33 +0200
Subject: [PATCH] [clang][Interp] IndirectMember initializers

---
 clang/lib/AST/Interp/ByteCodeStmtGen.cpp | 67 ++++++++++++++++--------
 clang/lib/AST/Interp/Interp.h            |  7 ++-
 clang/lib/AST/Interp/Opcodes.td          |  6 ++-
 clang/test/AST/Interp/records.cpp        | 60 +++++++++++++++++++++
 4 files changed, 114 insertions(+), 26 deletions(-)

diff --git a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
index 509abe3ae867f93..c22bbd8623e5326 100644
--- a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
+++ b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
@@ -142,6 +142,27 @@ bool ByteCodeStmtGen<Emitter>::visitFunc(const FunctionDecl *F) {
   // Classify the return type.
   ReturnType = this->classify(F->getReturnType());
 
+  auto emitFieldInitializer = [&](const Record::Field *F, unsigned FieldOffset,
+                                  const Expr *InitExpr) -> bool {
+    if (std::optional<PrimType> T = this->classify(InitExpr)) {
+      if (!this->visit(InitExpr))
+        return false;
+
+      if (F->isBitField())
+        return this->emitInitThisBitField(*T, F, FieldOffset, InitExpr);
+      return this->emitInitThisField(*T, FieldOffset, InitExpr);
+    }
+    // Non-primitive case. Get a pointer to the field-to-initialize
+    // on the stack and call visitInitialzer() for it.
+    if (!this->emitGetPtrThisField(FieldOffset, InitExpr))
+      return false;
+
+    if (!this->visitInitializer(InitExpr))
+      return false;
+
+    return this->emitPopPtr(InitExpr);
+  };
+
   // Emit custom code if this is a lambda static invoker.
   if (const auto *MD = dyn_cast<CXXMethodDecl>(F);
       MD && MD->isLambdaStaticInvoker())
@@ -162,29 +183,8 @@ bool ByteCodeStmtGen<Emitter>::visitFunc(const FunctionDecl *F) {
       if (const FieldDecl *Member = Init->getMember()) {
         const Record::Field *F = R->getField(Member);
 
-        if (std::optional<PrimType> T = this->classify(InitExpr)) {
-          if (!this->visit(InitExpr))
-            return false;
-
-          if (F->isBitField()) {
-            if (!this->emitInitThisBitField(*T, F, InitExpr))
-              return false;
-          } else {
-            if (!this->emitInitThisField(*T, F->Offset, InitExpr))
-              return false;
-          }
-        } else {
-          // Non-primitive case. Get a pointer to the field-to-initialize
-          // on the stack and call visitInitialzer() for it.
-          if (!this->emitGetPtrThisField(F->Offset, InitExpr))
-            return false;
-
-          if (!this->visitInitializer(InitExpr))
-            return false;
-
-          if (!this->emitPopPtr(InitExpr))
-            return false;
-        }
+        if (!emitFieldInitializer(F, F->Offset, InitExpr))
+          return false;
       } else if (const Type *Base = Init->getBaseClass()) {
         // Base class initializer.
         // Get This Base and call initializer on it.
@@ -198,6 +198,27 @@ bool ByteCodeStmtGen<Emitter>::visitFunc(const FunctionDecl *F) {
           return false;
         if (!this->emitInitPtrPop(InitExpr))
           return false;
+      } else if (const IndirectFieldDecl *IFD = Init->getIndirectMember()) {
+        assert(IFD->getChainingSize() >= 2);
+
+        unsigned NestedFieldOffset = 0;
+        const Record::Field *NestedField = nullptr;
+        for (const NamedDecl *ND : IFD->chain()) {
+          // FIXME: Can this *not* be a FieldDecl?
+          const FieldDecl *FD = cast<FieldDecl>(ND);
+          const Record *FieldRecord =
+              this->P.getOrCreateRecord(FD->getParent());
+          assert(FieldRecord);
+
+          NestedField = FieldRecord->getField(FD);
+          assert(NestedField);
+
+          NestedFieldOffset += NestedField->Offset;
+        }
+        assert(NestedField);
+
+        if (!emitFieldInitializer(NestedField, NestedFieldOffset, InitExpr))
+          return false;
       } else {
         assert(Init->isDelegatingInitializer());
         if (!this->emitThis(InitExpr))
diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h
index 7ef1e344224a3c3..b2777c8a007ea7f 100644
--- a/clang/lib/AST/Interp/Interp.h
+++ b/clang/lib/AST/Interp/Interp.h
@@ -1069,15 +1069,18 @@ bool InitThisField(InterpState &S, CodePtr OpPC, uint32_t I) {
   return true;
 }
 
+// FIXME: The Field pointer here is too much IMO and we could instead just
+// pass an Offset + BitWidth pair.
 template <PrimType Name, class T = typename PrimConv<Name>::T>
-bool InitThisBitField(InterpState &S, CodePtr OpPC, const Record::Field *F) {
+bool InitThisBitField(InterpState &S, CodePtr OpPC, const Record::Field *F,
+                      uint32_t FieldOffset) {
   assert(F->isBitField());
   if (S.checkingPotentialConstantExpression())
     return false;
   const Pointer &This = S.Current->getThis();
   if (!CheckThis(S, OpPC, This))
     return false;
-  const Pointer &Field = This.atField(F->Offset);
+  const Pointer &Field = This.atField(FieldOffset);
   const auto &Value = S.Stk.pop<T>();
   Field.deref<T>() = Value.truncate(F->Decl->getBitWidthValue(S.getCtx()));
   Field.initialize();
diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td
index e1e7e5e2efbb059..709de4755af82e1 100644
--- a/clang/lib/AST/Interp/Opcodes.td
+++ b/clang/lib/AST/Interp/Opcodes.td
@@ -416,7 +416,11 @@ def InitThisField : AccessOpcode;
 // [Value] -> []
 def InitThisFieldActive : AccessOpcode;
 // [Value] -> []
-def InitThisBitField : BitFieldOpcode;
+def InitThisBitField : Opcode {
+  let Types = [AluTypeClass];
+  let Args = [ArgRecordField, ArgUint32];
+  let HasGroup = 1;
+}
 // [Pointer, Value] -> []
 def InitField : AccessOpcode;
 // [Pointer, Value] -> []
diff --git a/clang/test/AST/Interp/records.cpp b/clang/test/AST/Interp/records.cpp
index a2e878f6132d0ac..f30722a2ef146ff 100644
--- a/clang/test/AST/Interp/records.cpp
+++ b/clang/test/AST/Interp/records.cpp
@@ -1105,3 +1105,63 @@ namespace DelegatingConstructors {
   static_assert(d4.a == 10, "");
   static_assert(d4.b == 12, "");
 }
+
+namespace IndirectFieldInit {
+#if __cplusplus >= 202002L
+  /// Primitive.
+  struct Nested1 {
+    struct {
+      int first;
+    };
+    int x;
+    constexpr Nested1(int x) : first(12), x() { x = 4; }
+    constexpr Nested1() : Nested1(42) {}
+  };
+  constexpr Nested1 N1{};
+  static_assert(N1.first == 12, "");
+
+  /// Composite.
+  struct Nested2 {
+    struct First { int x = 42; };
+    struct {
+      First first;
+    };
+    int x;
+    constexpr Nested2(int x) : first(12), x() { x = 4; }
+    constexpr Nested2() : Nested2(42) {}
+  };
+  constexpr Nested2 N2{};
+  static_assert(N2.first.x == 12, "");
+
+  /// Bitfield.
+  struct Nested3 {
+    struct {
+      unsigned first : 2;
+    };
+    int x;
+    constexpr Nested3(int x) : first(3), x() { x = 4; }
+    constexpr Nested3() : Nested3(42) {}
+  };
+
+  constexpr Nested3 N3{};
+  static_assert(N3.first == 3, "");
+
+  /// Test that we get the offset right if the
+  /// record has a base.
+  struct Nested4Base {
+    int a;
+    int b;
+    char c;
+  };
+  struct Nested4 : Nested4Base{
+    struct {
+      int first;
+    };
+    int x;
+    constexpr Nested4(int x) : first(123), x() { a = 1; b = 2; c = 3; x = 4; }
+    constexpr Nested4() : Nested4(42) {}
+  };
+  constexpr Nested4 N4{};
+  static_assert(N4.first == 123, "");
+#endif
+}



More information about the cfe-commits mailing list