Skip to content

[Modules] Delay deserialization of preferred_name attribute at r… #122726

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 7 commits into from
Jan 17, 2025
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
14 changes: 10 additions & 4 deletions clang/include/clang/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class Attr : public AttributeCommonInfo {
unsigned IsLateParsed : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned InheritEvenIfAlreadyPresent : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned DeferDeserialization : 1;

void *operator new(size_t bytes) noexcept {
llvm_unreachable("Attrs cannot be allocated with regular 'new'.");
Expand All @@ -80,10 +82,11 @@ class Attr : public AttributeCommonInfo {

protected:
Attr(ASTContext &Context, const AttributeCommonInfo &CommonInfo,
attr::Kind AK, bool IsLateParsed)
attr::Kind AK, bool IsLateParsed, bool DeferDeserialization = false)
: AttributeCommonInfo(CommonInfo), AttrKind(AK), Inherited(false),
IsPackExpansion(false), Implicit(false), IsLateParsed(IsLateParsed),
InheritEvenIfAlreadyPresent(false) {}
InheritEvenIfAlreadyPresent(false),
DeferDeserialization(DeferDeserialization) {}

public:
attr::Kind getKind() const { return static_cast<attr::Kind>(AttrKind); }
Expand All @@ -105,6 +108,8 @@ class Attr : public AttributeCommonInfo {
void setPackExpansion(bool PE) { IsPackExpansion = PE; }
bool isPackExpansion() const { return IsPackExpansion; }

bool shouldDeferDeserialization() const { return DeferDeserialization; }

// Clone this attribute.
Attr *clone(ASTContext &C) const;

Expand Down Expand Up @@ -146,8 +151,9 @@ class InheritableAttr : public Attr {
protected:
InheritableAttr(ASTContext &Context, const AttributeCommonInfo &CommonInfo,
attr::Kind AK, bool IsLateParsed,
bool InheritEvenIfAlreadyPresent)
: Attr(Context, CommonInfo, AK, IsLateParsed) {
bool InheritEvenIfAlreadyPresent,
bool DeferDeserialization = false)
: Attr(Context, CommonInfo, AK, IsLateParsed, DeferDeserialization) {
this->InheritEvenIfAlreadyPresent = InheritEvenIfAlreadyPresent;
}

Expand Down
11 changes: 11 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,12 @@ class Attr {
// attribute may be documented under multiple categories, more than one
// Documentation entry may be listed.
list<Documentation> Documentation;
// Set to true if deserialization of this attribute must be deferred until
// the parent Decl is fully deserialized (during header module file
// deserialization). E.g., this is the case for the preferred_name attribute,
// since its type deserialization depends on its target Decl type.
// (See https://github.com/llvm/llvm-project/issues/56490 for details).
bit DeferDeserialization = 0;
}

/// Used to define a set of mutually exclusive attributes.
Expand Down Expand Up @@ -3240,6 +3246,11 @@ def PreferredName : InheritableAttr {
let InheritEvenIfAlreadyPresent = 1;
let MeaningfulToClassTemplateDefinition = 1;
let TemplateDependent = 1;
// Type of this attribute depends on the target Decl type.
// Therefore, its deserialization must be deferred until
// deserialization of the target Decl is complete
// (for header modules).
let DeferDeserialization = 1;
Copy link
Member

Choose a reason for hiding this comment

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

Maybe it is better to add comment here to explain why we want to defer PreferredName.

}

def PreserveMost : DeclOrTypeAttr {
Expand Down
19 changes: 19 additions & 0 deletions clang/include/clang/Serialization/ASTReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,24 @@ class ASTReader
/// been completed.
std::deque<PendingDeclContextInfo> PendingDeclContextInfos;

/// Deserialization of some attributes must be deferred since they refer
/// to themselves in their type (e.g., preferred_name attribute refers to the
/// typedef that refers back to the template specialization of the template
/// that the attribute is attached to).
/// More attributes that store TypeSourceInfo might be potentially affected,
/// see https://github.com/llvm/llvm-project/issues/56490 for details.
struct DeferredAttribute {
// Index of the deferred attribute in the Record of the TargetedDecl.
uint64_t RecordIdx;
Copy link
Member

Choose a reason for hiding this comment

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

nit: Add a comment to the field. e.g., something like the Index of the delayed attribute in the Record of the targeted decl.

// Decl to attach a deferred attribute to.
Decl *TargetedDecl;
};

/// The collection of Decls that have been loaded but some of their attributes
/// have been deferred, paired with the index inside the record pointing
/// at the skipped attribute.
SmallVector<DeferredAttribute> PendingDeferredAttributes;

template <typename DeclTy>
using DuplicateObjCDecls = std::pair<DeclTy *, DeclTy *>;

Expand Down Expand Up @@ -1551,6 +1569,7 @@ class ASTReader
void loadPendingDeclChain(Decl *D, uint64_t LocalOffset);
void loadObjCCategories(GlobalDeclID ID, ObjCInterfaceDecl *D,
unsigned PreviousGeneration = 0);
void loadDeferredAttribute(const DeferredAttribute &DA);

RecordLocation getLocalBitOffset(uint64_t GlobalOffset);
uint64_t getGlobalBitOffset(ModuleFile &M, uint64_t LocalOffset);
Expand Down
13 changes: 12 additions & 1 deletion clang/include/clang/Serialization/ASTRecordReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ class ASTRecordReader
/// Returns the current value in this record, without advancing.
uint64_t peekInt() { return Record[Idx]; }

/// Returns the next N values in this record, without advancing.
uint64_t peekInts(unsigned N) { return Record[Idx + N]; }

/// Skips the current value.
void skipInt() { Idx += 1; }

/// Skips the specified number of values.
void skipInts(unsigned N) { Idx += N; }

Expand Down Expand Up @@ -335,7 +341,12 @@ class ASTRecordReader
Attr *readAttr();

/// Reads attributes from the current stream position, advancing Idx.
void readAttributes(AttrVec &Attrs);
/// For some attributes (where type depends on itself recursively), defer
/// reading the attribute until the type has been read.
void readAttributes(AttrVec &Attrs, Decl *D = nullptr);

/// Reads one attribute from the current stream position, advancing Idx.
Attr *readOrDeferAttrFor(Decl *D);

/// Read an BTFTypeTagAttr object.
BTFTypeTagAttr *readBTFTypeTagAttr() {
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/Serialization/ASTReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10079,6 +10079,11 @@ void ASTReader::finishPendingActions() {
}
PendingDeducedVarTypes.clear();

// Load the delayed preferred name attributes.
for (unsigned I = 0; I != PendingDeferredAttributes.size(); ++I)
loadDeferredAttribute(PendingDeferredAttributes[I]);
PendingDeferredAttributes.clear();
Comment on lines +10082 to +10085
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Load the delayed preferred name attributes.
for (unsigned I = 0; I != PendingDeferredAttributes.size(); ++I)
loadDeferredAttribute(PendingDeferredAttributes[I]);
PendingDeferredAttributes.clear();
// Load the delayed preferred name attributes.
while (!PendingDeferredAttributes.empty()) {
auto DeferredAttributes = std::move(PendingDeferredAttributes);
for (DeferredAttribute &DA : DeferredAttribute)
loadDeferredAttribute(DA);
}

Technically, it is possible to update PendingDeferredAttributes during loadDeferredAttribute().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks. The original snippet seems more concise though, so it might be preferable to stick to it for now.


// For each decl chain that we wanted to complete while deserializing, mark
// it as "still needs to be completed".
for (unsigned I = 0; I != PendingIncompleteDeclChains.size(); ++I) {
Expand Down
79 changes: 75 additions & 4 deletions clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ void ASTDeclReader::VisitDecl(Decl *D) {

if (HasAttrs) {
AttrVec Attrs;
Record.readAttributes(Attrs);
Record.readAttributes(Attrs, D);
// Avoid calling setAttrs() directly because it uses Decl::getASTContext()
// internally which is unsafe during derialization.
D->setAttrsImpl(Attrs, Reader.getContext());
Expand Down Expand Up @@ -3088,6 +3088,8 @@ class AttrReader {
return Reader.readInt();
}

uint64_t peekInts(unsigned N) { return Reader.peekInts(N); }

bool readBool() { return Reader.readBool(); }

SourceRange readSourceRange() {
Expand Down Expand Up @@ -3118,18 +3120,29 @@ class AttrReader {
return Reader.readVersionTuple();
}

void skipInt() { Reader.skipInts(1); }

void skipInts(unsigned N) { Reader.skipInts(N); }

unsigned getCurrentIdx() { return Reader.getIdx(); }

OMPTraitInfo *readOMPTraitInfo() { return Reader.readOMPTraitInfo(); }

template <typename T> T *readDeclAs() { return Reader.readDeclAs<T>(); }
};
}

/// Reads one attribute from the current stream position, advancing Idx.
Attr *ASTRecordReader::readAttr() {
AttrReader Record(*this);
auto V = Record.readInt();
if (!V)
return nullptr;

// Read and ignore the skip count, since attribute deserialization is not
// deferred on this pass.
Record.skipInt();

Attr *New = nullptr;
// Kind is stored as a 1-based integer because 0 is used to indicate a null
// Attr pointer.
Expand Down Expand Up @@ -3159,13 +3172,28 @@ Attr *ASTRecordReader::readAttr() {
return New;
}

/// Reads attributes from the current stream position.
void ASTRecordReader::readAttributes(AttrVec &Attrs) {
/// Reads attributes from the current stream position, advancing Idx.
/// For some attributes (where type depends on itself recursively), defer
/// reading the attribute until the type has been read.
void ASTRecordReader::readAttributes(AttrVec &Attrs, Decl *D) {
for (unsigned I = 0, E = readInt(); I != E; ++I)
if (auto *A = readAttr())
if (auto *A = readOrDeferAttrFor(D))
Attrs.push_back(A);
}

/// Reads one attribute from the current stream position, advancing Idx.
/// For some attributes (where type depends on itself recursively), defer
/// reading the attribute until the type has been read.
Attr *ASTRecordReader::readOrDeferAttrFor(Decl *D) {
AttrReader Record(*this);
unsigned SkipCount = Record.peekInts(1);
if (!SkipCount)
return readAttr();
Reader->PendingDeferredAttributes.push_back({Record.getCurrentIdx(), D});
Record.skipInts(SkipCount);
return nullptr;
}

//===----------------------------------------------------------------------===//
// ASTReader Implementation
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -4424,6 +4452,49 @@ void ASTReader::loadPendingDeclChain(Decl *FirstLocal, uint64_t LocalOffset) {
ASTDeclReader::attachLatestDecl(CanonDecl, MostRecent);
}

void ASTReader::loadDeferredAttribute(const DeferredAttribute &DA) {
Decl *D = DA.TargetedDecl;
ModuleFile *M = getOwningModuleFile(D);

unsigned LocalDeclIndex = D->getGlobalID().getLocalDeclIndex();
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought we might need to store the indices separately, but having the declaration around we actually have enough to look up the bitstream position.

Nice trick, kudos for coming up with this!

const DeclOffset &DOffs = M->DeclOffsets[LocalDeclIndex];
RecordLocation Loc(M, DOffs.getBitOffset(M->DeclsBlockStartOffset));

llvm::BitstreamCursor &Cursor = Loc.F->DeclsCursor;
SavedStreamPosition SavedPosition(Cursor);
if (llvm::Error Err = Cursor.JumpToBit(Loc.Offset)) {
Error(std::move(Err));
}

Expected<unsigned> MaybeCode = Cursor.ReadCode();
if (!MaybeCode) {
llvm::report_fatal_error(
Twine("ASTReader::loadPreferredNameAttribute failed reading code: ") +
toString(MaybeCode.takeError()));
}
unsigned Code = MaybeCode.get();

ASTRecordReader Record(*this, *Loc.F);
Expected<unsigned> MaybeRecCode = Record.readRecord(Cursor, Code);
if (!MaybeRecCode) {
llvm::report_fatal_error(
Twine(
"ASTReader::loadPreferredNameAttribute failed reading rec code: ") +
toString(MaybeCode.takeError()));
}
unsigned RecCode = MaybeRecCode.get();
if (RecCode < DECL_TYPEDEF || RecCode > DECL_LAST) {
llvm::report_fatal_error(
Twine("ASTReader::loadPreferredNameAttribute failed reading rec code: "
"expected valid DeclCode") +
toString(MaybeCode.takeError()));
}

Record.skipInts(DA.RecordIdx);
Attr *A = Record.readAttr();
getContext().getDeclAttrs(D).push_back(A);
}

namespace {

/// Given an ObjC interface, goes through the modules and links to the
Expand Down
16 changes: 11 additions & 5 deletions clang/lib/Serialization/ASTWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
#include "clang/AST/TypeLocVisitor.h"
#include "clang/Basic/AttrKinds.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileEntry.h"
Expand Down Expand Up @@ -4906,15 +4907,14 @@ void ASTWriter::WriteModuleFileExtension(Sema &SemaRef,

void ASTRecordWriter::AddAttr(const Attr *A) {
auto &Record = *this;
// FIXME: Clang can't handle the serialization/deserialization of
// preferred_name properly now. See
// https://github.com/llvm/llvm-project/issues/56490 for example.
if (!A || (isa<PreferredNameAttr>(A) &&
Writer->isWritingStdCXXNamedModules()))
if (!A)
return Record.push_back(0);

Record.push_back(A->getKind() + 1); // FIXME: stable encoding, target attrs

auto SkipIdx = Record.size();
// Add placeholder for the size of deferred attribute.
Record.push_back(0);
Record.AddIdentifierRef(A->getAttrName());
Record.AddIdentifierRef(A->getScopeName());
Record.AddSourceRange(A->getRange());
Expand All @@ -4925,6 +4925,12 @@ void ASTRecordWriter::AddAttr(const Attr *A) {
Record.push_back(A->isRegularKeywordAttribute());

#include "clang/Serialization/AttrPCHWrite.inc"

if (A->shouldDeferDeserialization()) {
// Record the actual size of deferred attribute (+ 1 to count the attribute
// kind).
Record[SkipIdx] = Record.size() - SkipIdx + 1;
}
}

/// Emit the list of attributes to the specified record.
Expand Down
12 changes: 9 additions & 3 deletions clang/test/Modules/preferred_name.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,16 @@ import A;
export using ::foo_templ;

//--- Use1.cpp
import A; // expected-[email protected]:8 {{attribute declaration must precede definition}}
#include "foo.h" // [email protected]:9 {{previous definition is here}}

// expected-no-diagnostics
import A;
#include "foo.h"
//--- Use2.cpp
// expected-no-diagnostics
#include "foo.h"
import A;

//--- Use3.cpp
#include "foo.h"
import A;
foo test;
int size = test.size(); // expected-error {{no member named 'size' in 'foo'}}
4 changes: 4 additions & 0 deletions clang/utils/TableGen/ClangAttrEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3043,6 +3043,10 @@ static void emitAttributes(const RecordKeeper &Records, raw_ostream &OS,
<< (R.getValueAsBit("InheritEvenIfAlreadyPresent") ? "true"
: "false");
}
if (R.getValueAsBit("DeferDeserialization")) {
OS << ", "
<< "/*DeferDeserialization=*/true";
}
OS << ")\n";

for (auto const &ai : Args) {
Expand Down
Loading