Skip to content

Commit

Permalink
[Modules] Delay deserialization of preferred_name attribute at r… (ll…
Browse files Browse the repository at this point in the history
…vm#122726)

…ecord level.

This fixes the incorrect diagnostic emitted when compiling the following
snippet

```
// string_view.h
template<class _CharT>
class basic_string_view;

typedef basic_string_view<char> string_view;

template<class _CharT>
class
__attribute__((__preferred_name__(string_view)))
basic_string_view {
public:
    basic_string_view() 
    {
    }
};

inline basic_string_view<char> foo()
{
  return basic_string_view<char>();
}
// A.cppm
module;
#include "string_view.h"
export module A;

// Use.cppm
module;
#include "string_view.h"
export module Use;
import A;
```

The diagnostic is 
```
string_view.h:11:5: error: 'basic_string_view<char>::basic_string_view' from module 'A.<global>' is not present in definition of 'string_view' provided earlier
```

The underlying issue is that deserialization of the `preferred_name`
attribute triggers deserialization of `basic_string_view<char>`, which
triggers the deserialization of the `preferred_name` attribute again
(since it's attached to the `basic_string_view` template).
The deserialization logic is implemented in a way that prevents it from
going on a loop in a literal sense (it detects early on that it has
already seen the `string_view` typedef when trying to start its
deserialization for the second time), but leaves the typedef
deserialization in an unfinished state. Subsequently, the `string_view`
typedef from the deserialized module cannot be merged with the same
typedef from `string_view.h`, resulting in the above diagnostic.

This PR resolves the problem by delaying the deserialization of the
`preferred_name` attribute until the deserialization of the
`basic_string_view` template is completed. As a result of deferring, the
deserialization of the `preferred_name` attribute doesn't need to go on
a loop since the type of the `string_view` typedef is already known when
it's deserialized.
  • Loading branch information
VitaNuo authored Jan 17, 2025
1 parent 1274bca commit c3ba6f3
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 17 deletions.
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 @@ -3254,6 +3260,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;
}

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 @@ -1221,6 +1221,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;
// 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 @@ -1570,6 +1588,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 @@ -10180,6 +10180,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();

// 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 @@ -3093,6 +3093,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 @@ -3123,18 +3125,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 @@ -3164,13 +3177,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 @@ -4459,6 +4487,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();
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 @@ -5067,15 +5068,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 @@ -5086,6 +5086,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

0 comments on commit c3ba6f3

Please sign in to comment.