Skip to content

[clang][Interp] IndirectMember initializers #69900

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

Merged
merged 5 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 43 additions & 23 deletions clang/lib/AST/Interp/ByteCodeStmtGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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.
Expand All @@ -198,6 +198,26 @@ 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()) {
const auto *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))
Expand Down
7 changes: 5 additions & 2 deletions clang/lib/AST/Interp/Interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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();
Expand Down
6 changes: 5 additions & 1 deletion clang/lib/AST/Interp/Opcodes.td
Original file line number Diff line number Diff line change
Expand Up @@ -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] -> []
Expand Down
87 changes: 87 additions & 0 deletions clang/test/AST/Interp/records.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
}