-
Notifications
You must be signed in to change notification settings - Fork 15k
[C++20][Modules] Fix merging of anonymous members of class templates. #155948
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
base: main
Are you sure you want to change the base?
Conversation
@llvm/pr-subscribers-clang-modules @llvm/pr-subscribers-clang Author: Michael Park (mpark) ChangesI'm having a hard time figuring out what the expected behavior is for this case. If I get rid of the anonymous union and just keep
There are no issues. However, I see that merging occurs to avoid any issues in this case. With the If I structure the
There are also no issues, and this time there doesn't seem to be any merging either. Question: Should this case (1) not be merging at all? or (2) is it correct to be merging, and we need to fix the logic for merging anonymous Full diff: https://github.com/llvm/llvm-project/pull/155948.diff 1 Files Affected:
diff --git a/clang/test/Modules/anon-union-in-template.cpp b/clang/test/Modules/anon-union-in-template.cpp
new file mode 100644
index 0000000000000..97fcdc7db86be
--- /dev/null
+++ b/clang/test/Modules/anon-union-in-template.cpp
@@ -0,0 +1,47 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++20 -fmodule-name=hu-01 -emit-header-unit -xc++-user-header %t/hu-01.h \
+// RUN: -o %t/hu-01.pcm
+
+// RUN: %clang_cc1 -std=c++20 -fmodule-name=hu-02 -emit-header-unit -xc++-user-header %t/hu-02.h \
+// RUN: -Wno-experimental-header-units \
+// RUN: -fmodule-map-file=%t/hu-01.map -fmodule-file=hu-01=%t/hu-01.pcm \
+// RUN: -o %t/hu-02.pcm
+
+// RUN: %clang_cc1 -std=c++20 -emit-obj %t/main.cpp \
+// RUN: -Wno-experimental-header-units \
+// RUN: -fmodule-map-file=%t/hu-01.map -fmodule-file=hu-01=%t/hu-01.pcm \
+// RUN: -fmodule-map-file=%t/hu-02.map -fmodule-file=hu-02=%t/hu-02.pcm
+
+//--- hu-01.map
+module "hu-01" {
+ header "hu-01.h"
+ export *
+}
+
+//--- hu-02.map
+module "hu-02" {
+ header "hu-02.h"
+ export *
+}
+
+//--- hu-01.h
+#pragma once
+
+template <typename T>
+struct S { union { T x; }; };
+
+using SI = S<int>;
+
+//--- hu-02.h
+import "hu-01.h";
+inline void f(S<int> s = {}) { s.x; }
+
+//--- main.cpp
+import "hu-01.h";
+void g(S<int>) {}
+
+import "hu-02.h";
+void h() { f(); }
|
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.
I think we need to merge it. Why do you think we don't need to merge it?
It's just that this is a failing test case that I'd like to ship with a fix. |
375388f
to
7940530
Compare
oh goodness... I only just realized now that you meant that the decls should be merged. At the time I thought you were saying we should merge the PR 😂 |
7940530
to
c69375f
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.
How these decls get merged if:
// a.h
template <typename T>
struct S { union { T x; }; };
// b.h
import "a.h";
inline void f(S<int> s = {}) { s.x; }
// main.cpp
import "a.h";
void g(S<int>) {}
import "b.h";
void h() { f(); }
I feel we can get some ideas from it.
auto InstantiatedLocally = [](Decl *D, SourceManager &SourceMgr) -> bool { | ||
auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(D); | ||
return CTSD && CTSD->getPointOfInstantiation().isValid() && | ||
SourceMgr.isLocalSourceLocation(CTSD->getPointOfInstantiation()); |
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.
This is really not ideal. I only use source location for semantical analysis when debugging... it is pandora's box. I don't want that really.
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.
Ah... great point. Let me see if I can capture this into a bit flag instead.
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.
Okay, I've changed the approach a bit to where this information is essentially tracked in a separate bit flag in ClassTemplateSpecializationDecl
called InstantiatedLocally
.
In this case, because of the missing |
c69375f
to
302a2ea
Compare
302a2ea
to
c9f95c1
Compare
c9f95c1
to
899fb7f
Compare
Problem Description
Consider the following example involving an anonymous
union
in a class template:This example currently triggers an assertion failure that looks like this:
We try to look-up a
FieldDecl
instance in theFieldInfo
of aCGRecordLayout
, which is missing.a.h
has aClassTemplateDecl
(from theS
primary template), and aClassTemplateSpecializationDecl
(from theusing SI = S<int>;
). Note however, that theClassTemplateSpecializationDecl
is not an instantiation at this point.b.h
importsa.h
, and requires a full instantiation ofS<int>
(fromf(S<int> s)
). It performs the instantiation, creates theFieldDecl
instances for the anonymousunion
andint x;
, forms the reference to them, and write all of that tob.pcm
.main.cpp
importsa.h
, and it also requires a full instantiation ofS<int>
(fromg(S<int>)
). Since we don't have an instantiated version (we haven't importedb.h
yet), we perform the instantiation again here.main.cpp
importsb.h
, and that updatesS<int>
instantiated inmain.cpp
with the information fromb.h
. The invocation off()
pulls in thes.x
expression which requires theFieldDecl
s (both the anonymousunion
and theint x;
) inb.pcm
. The anonymousunion
FieldDecl
first tries to merge with the existing one, but this process is unsuccessful, and the merging does not occur.CGRecordLayout
with theClassTemplateSpecializationDecl
with aFieldDecl
for the anonymousunion
frommain.cpp
. We later try to do a look-up of theFieldDecl
for the anonymousunion
fromb.h
. At this point, the expected behavior is for theFieldDecl
s to have been merged such thatgetCanonicalDecl
returns the canonical version, which is present in theCGRecordLayout
. However, since the merging failed, we end up doing the look-up of theFieldDecl
for the anonymousunion
fromb.h
in a hashmap that contains theFieldDecl
for the anonymousunion
frommain.cpp
.Why Merging Fails
The following piece of code is within
ASTDeclReader::getAnonymousDeclForMerging
:https://github.com/llvm/llvm-project/blob/main/clang/lib/Serialization/ASTReaderDecl.cpp#L3430
Typically, an AST import action will insert a
DeclContext
into theAnonymousDeclarationsForMerging
hashmap if no existing entry has been found. However, if theDeclContext
was parsed within this source file, it doesn't go through the same codepath to insert itself into the hashmap. As such, this piece of code is trying to fill that gap by performing the insertion if theDeclContext
is not from an AST file. It's meant to handle an example like this:The problem is that this condition is not accurate enough in the face of template instantiations.
Consider this slightly modified version of the above example:
Here, in
main.cpp
during the import ofa.h
, we do an import of the skeleton (uninstantiated) instance ofClassTemplateSpecializationDecl
. As such, the numbering of anonymous members does not take place. Then, it gets fully instantiated fromvoid g(S<int>);
but still, no numbering occurs. So semantically we're in the same situation as the previous example of having parsed the decl in the source (it just happens to be instantiated in the source rather than parsed), but theisFromASTFile
of theClassTemplateSpecializationDecl
reportstrue
(since the instance was imported froma.h
!). The numbering never happens, which in turn makes it such that merging doesn't happen.Proposed Solution
The solution proposed here is to check whether the point of instantiation is local, even if the decl is from an AST file. Additionally, we make an
UpdateRecord
only update the point of instantiation if it's not already set. This way, the point of instantiation always remains the first point of instantiation.Alternative Considered
An approach where we inject the numbering logic before an
UpdateDecl
call was considered. The idea was that since forUpdateDecl
we already know we're not the first one, we know we should prepare to do some merging. This approach does actually work to addressmerge-anon-in-template-3.cpp
where anUpdateDecl
occurs. However, it does not fix the case where anUpdateDecl
does not occur, e.g. inmerge-anon-in-template-2.cpp
. As such, I believe that the solution proposed in this PR is simpler and more robust.