-
Notifications
You must be signed in to change notification settings - Fork 10.8k
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
Conversation
5b48d59
to
214cb36
Compare
@llvm/pr-subscribers-clang Author: Timm Baeder (tbaederr) ChangesWe 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:
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
+}
|
214cb36
to
af4c29c
Compare
Ping |
8 similar comments
Ping |
Ping |
Ping |
Ping |
Ping |
Ping |
Ping |
Ping |
unsigned NestedFieldOffset = 0; | ||
const Record::Field *NestedField = nullptr; | ||
for (const NamedDecl *ND : IFD->chain()) { | ||
// FIXME: Can this *not* be a FieldDecl? |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
af4c29c
to
92e5857
Compare
There was a problem hiding this 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
We need to look at the chain of declarations to initialize the right field.