-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[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
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(); }
|
ChuanqiXu9
left a comment
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
ChuanqiXu9
left a comment
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.
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
c9f95c1 to
899fb7f
Compare
Got it. And I am curious, how does it handle the case that the previous class template specialization comes from AST case? |
Oh, I guess I forgot to include the sequence of events in the "good" case. Here's my understanding what happens:
The end effect of all this is that the first time around, a piece of information is placed (via
|
|
I am confused that when we try to merge the field from "b.h", the primary DC should be the definition instead of the declaration: https://github.com/llvm/llvm-project/blob/899fb7f663a973d6cef57533c85884631f08f26b/clang/lib/Serialization/ASTReaderDecl.cpp#L3385C1-L3389C42 I think the definition in your case should be fully instantiated in main.cpp and its value of |
Yeah, so as far I know:
|
Then in this case, although the |
Oh, hmm. So I did consider the fact that the instantiated |
Hm... okay. So I did check for the The latest update checks for |
899fb7f to
3b0eb11
Compare
ChuanqiXu9
left a comment
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.
Now it looks much better.
f04da44 to
4ca6e01
Compare
4ca6e01 to
27dcc80
Compare
@ChuanqiXu9 One more tweak here... I also tried just not checking the I'm proposing to go with the |
ChuanqiXu9
left a comment
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.
Yeah, I like the idea. I do think we lack a lot of noload_ APIs and I feel, there are a lot of spaces to improve if we'd like to update the usage in Sema.
Problem Description
Consider the following example involving an anonymous
unionin a class template:This example currently triggers an assertion failure that looks like this:
We try to look-up a
FieldDeclinstance in theFieldInfoof aCGRecordLayout, which is missing.a.hhas aClassTemplateDecl(from theSprimary template), and aClassTemplateSpecializationDecl(from theusing SI = S<int>;). Note however, that theClassTemplateSpecializationDeclis not an instantiation at this point.b.himportsa.h, and requires a full instantiation ofS<int>(fromf(S<int> s)). It performs the instantiation, creates theFieldDeclinstances for the anonymousunionandint x;, forms the reference to them, and write all of that tob.pcm.main.cppimportsa.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.hyet), we perform the instantiation again here.main.cppimportsb.h, and that updatesS<int>instantiated inmain.cppwith the information fromb.h. The invocation off()pulls in thes.xexpression which requires theFieldDecls (both the anonymousunionand theint x;) inb.pcm. The anonymousunionFieldDeclfirst tries to merge with the existing one, but this process is unsuccessful, and the merging does not occur.CGRecordLayoutwith theClassTemplateSpecializationDeclwith aFieldDeclfor the anonymousunionfrommain.cpp. We later try to do a look-up of theFieldDeclfor the anonymousunionfromb.h. At this point, the expected behavior is for theFieldDecls to have been merged such thatgetCanonicalDeclreturns the canonical version, which is present in theCGRecordLayout. However, since the merging failed, we end up doing the look-up of theFieldDeclfor the anonymousunionfromb.hin a hashmap that contains theFieldDeclfor the anonymousunionfrommain.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
DeclContextinto theAnonymousDeclarationsForMerginghashmap if no existing entry has been found. However, if theDeclContextwas 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 theDeclContextis 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.cppduring 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 theisFromASTFileof theClassTemplateSpecializationDeclreportstrue(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
UpdateRecordonly 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
UpdateDeclcall was considered. The idea was that since forUpdateDeclwe 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.cppwhere anUpdateDecloccurs. However, it does not fix the case where anUpdateDecldoes 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.