Skip to content
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

[clang][Interp] IndirectMember initializers #69900

Merged
merged 5 commits into from
Jan 18, 2024

Conversation

tbaederr
Copy link
Contributor

We need to look at the chain of declarations to initialize the right field.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Oct 23, 2023
@llvmbot
Copy link
Collaborator

llvmbot commented Oct 23, 2023

@llvm/pr-subscribers-clang

Author: Timm Baeder (tbaederr)

Changes

We need to look at the chain of declarations to initialize the right field.


Full diff: https://github.com/llvm/llvm-project/pull/69900.diff

4 Files Affected:

  • (modified) clang/lib/AST/Interp/ByteCodeStmtGen.cpp (+44-23)
  • (modified) clang/lib/AST/Interp/Interp.h (+5-2)
  • (modified) clang/lib/AST/Interp/Opcodes.td (+5-1)
  • (modified) clang/test/AST/Interp/records.cpp (+60)
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
+}

@tbaederr
Copy link
Contributor Author

tbaederr commented Nov 5, 2023

Ping

8 similar comments
@tbaederr
Copy link
Contributor Author

Ping

@tbaederr
Copy link
Contributor Author

Ping

@tbaederr
Copy link
Contributor Author

Ping

@tbaederr
Copy link
Contributor Author

tbaederr commented Dec 8, 2023

Ping

@tbaederr
Copy link
Contributor Author

Ping

@tbaederr
Copy link
Contributor Author

Ping

@tbaederr
Copy link
Contributor Author

tbaederr commented Jan 8, 2024

Ping

@tbaederr
Copy link
Contributor Author

Ping

unsigned NestedFieldOffset = 0;
const Record::Field *NestedField = nullptr;
for (const NamedDecl *ND : IFD->chain()) {
// FIXME: Can this *not* be a FieldDecl?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it could be an IndirectFieldDecl, for example. You should add some tests using anonymous structures and unions where the members are indirectly injected into the parent structure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current interpreter uses cast<FieldDecl> as well, so this should be fine. Unfortunately, unions aren't handled at all in the new interpreter right now, so that could be a FIXME comment at best.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can run into the same with anonymous structures: https://godbolt.org/z/9565Ea5qo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks for the test case.

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM assuming no testing surprises come up

clang/lib/AST/Interp/ByteCodeStmtGen.cpp Outdated Show resolved Hide resolved
clang/test/AST/Interp/records.cpp Show resolved Hide resolved
@tbaederr tbaederr merged commit 819bd9e into llvm:main Jan 18, 2024
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants