From 819bd9e39b8c255600b7ec13ac195f726aa9a082 Mon Sep 17 00:00:00 2001 From: Timm Baeder Date: Thu, 18 Jan 2024 16:25:05 +0100 Subject: [PATCH] [clang][Interp] IndirectMember initializers (#69900) We need to look at the chain of declarations to initialize the right field. --- clang/lib/AST/Interp/ByteCodeStmtGen.cpp | 66 +++++++++++++++--------- clang/lib/AST/Interp/Interp.h | 7 ++- clang/lib/AST/Interp/Opcodes.td | 6 ++- clang/test/AST/Interp/records.cpp | 87 ++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 26 deletions(-) diff --git a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp index b1ab5fcf9cb6..38067be73e25 100644 --- a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp @@ -142,6 +142,27 @@ bool ByteCodeStmtGen::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 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(F); MD && MD->isLambdaStaticInvoker()) @@ -162,29 +183,8 @@ bool ByteCodeStmtGen::visitFunc(const FunctionDecl *F) { if (const FieldDecl *Member = Init->getMember()) { const Record::Field *F = R->getField(Member); - if (std::optional 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,26 @@ bool ByteCodeStmtGen::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()) { + const auto *FD = cast(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 dbbc4c09ce42..5321f9617fef 100644 --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -1078,15 +1078,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 ::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(); Field.deref() = 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 e01b6b9eea7d..6e0f1c939460 100644 --- a/clang/lib/AST/Interp/Opcodes.td +++ b/clang/lib/AST/Interp/Opcodes.td @@ -417,7 +417,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 a1ced049dced..800dc6f910be 100644 --- a/clang/test/AST/Interp/records.cpp +++ b/clang/test/AST/Interp/records.cpp @@ -1133,3 +1133,90 @@ namespace AccessOnNullptr { // ref-error {{not an integral constant expression}} \ // ref-note {{in call to 'a2()'}} } + +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, ""); + + struct S { + struct { + int x, y; + }; + + constexpr S(int x_, int y_) : x(x_), y(y_) {} + }; + + constexpr S s(1, 2); + static_assert(s.x == 1 && s.y == 2); + + struct S2 { + int a; + struct { + int b; + struct { + int x, y; + }; + }; + + constexpr S2(int x_, int y_) : a(3), b(4), x(x_), y(y_) {} + }; + + constexpr S2 s2(1, 2); + static_assert(s2.x == 1 && s2.y == 2 && s2.a == 3 && s2.b == 4); + +#endif +} -- 2.11.4.GIT