-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Use a two step Fortran and C++ dependency scanner #12539
base: master
Are you sure you want to change the base?
Conversation
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.
There's a few "add missing type annotations" commits in a row which I think make sense to be folded into one commit.
Have not reviewed the full patch series yet...
58c8923
to
df414f3
Compare
a468b08
to
501ba9e
Compare
The downside of two phase scanning is that you need to invoke two processes per each source file just to do the scanning. This is ... unfortunate. I'm working on a blog post/project that I hope to cause at least some sort of a tooling change. I'd like to get that out before thinking about merging this. Hopefully it only takes a few days. |
501ba9e
to
8698451
Compare
I've ready both of your blog posts, both this one and the one one about the dynamic command lines. Clang does have the option you want, it's called Second, Meson's scanner is actually broken, since it relies on hacks that are emitted at configure time (although it looks like the hack hasn't been implemented for C++ yet, and can be easily demonstrated by hacking the module test to put src9.cxx in a static library, then attempting to build gcc/modtest.p/src8.cxx, with my patches here it correctly attempts to generate src9.cxx first for it's ifc file (although with gcc 12 it runs into the "inputs can't also have inputs" problem.)) This is done by making each target linked with a orderonly dependency. I've removed the fortran hack as well in this series. But thinking about it, I'm not sure that this is even sufficient, since you could add another module to an existing source file and we wouldn't properly wait on that, so it actually needs to a full dependency. Third, To accurately get this information and have the order be correct you must read the module outputs of all of the dependencies of a target and account for those, so the bmi (I'm going to call it that from now on) is up to date. You also need to recalculate your dynamic outputs in the even that. This is also necessary if a new module name is added. You can do this in one step, but the dyndep doesn't actually have enough information for this, so you'd have have a sideband (ie, generate json and dyndep in one step). Having dep information on a per tu basis has advantages compared to a per target basis, at the cost of simplicity. It means that we can start more parallel compilation, and do less work for incremental builds. So, we're basically making tradeoffs:
|
8698451
to
e3a107e
Compare
I've added the necessary orderdep -> full dep changes, but I haven't gotten the tests yet. They'll require a python unittest because we're going to have to compile -> change the source -> recompile a specific target to prove that the bug is fixed, and I'm out of time for today |
e3a107e
to
3960b72
Compare
And I just realized that there's another case that isn't handled here that has to be handled, which is recursive dependencies, and after a quick test I found that that too is broken. |
Yes, but does it have a compiler flag for "write module files, whatever their name, in directory X"? All the examples that I see do not have that and use explicit output file it is needed to avoid having to generate command line arguments during compilation. That is the biggest problem FWICT, the others we can fix and/or work around.
I would have been amazed if it were not. :D I only implemented enough of it to get things going. Sadly they have been stagnant for a few years.
Assuming target A that depends on B, we only need to add a dependency of type "all compilations of A must wait until all compilations of B are done". You'd need a pseudo target for this but it's doable. The delay would be at most the longest compilation in A on average a fraction of that. Is that too much? Maybe. Maybe not. I'm hesitant to throw around performance estimates without actual measurements. |
I just did echo "export module speech; export const char * say() { return "Hello, World!"l; }" > "speech.cppm"
mkdir priv
clang++ -std=c++20 -fmodule-output -c speech.cppm -o priv
ls priv and it wrote out a This is much closer to what we actually want than GCC's server plan, and MSVC which requires you to declare whether a source file exports an interface or a partition. AFAICT clang might have the only implementation that we can easily get non-trivial examples working with without either scanning sources at configure time, or dynamically generating command line options at compile time. |
But in that case |
Right, that would need to be per file rather than per target. Which, I admit is not ideal, but at least it's closer than what GCC and MSVC have, and the consumer side is what we'd like |
3960b72
to
f2f9c08
Compare
8a2365f
to
fed1aa3
Compare
fed1aa3
to
49ce96e
Compare
40f94d7
to
fd74510
Compare
fd74510
to
c2b000e
Compare
c2b000e
to
5f49d64
Compare
c1a7e8b
to
6877a72
Compare
6877a72
to
dddad34
Compare
But we really expect it to be a string once the initializer is done. Therefore, let's set it to `''` just like we do with the scratchdir in the case that it's set to None in the initializer.
Because we don't set the appropriate dependencies
Because we use this dependency to ensure that any binary module files are generated, we must have a full dependency. Otherwise, if a new module is added to a dependent target, and used in our target, we race the generation of the binary module definition.
And fix some internal annotations
Otherwise only shared libraries get installed, which isn't correct.
Otherwise we again miss out on half of the targets we need
Otherwise they won't be able to find their module outputs.
…ned sources It is not sufficient to have an order dependency on generated sources. If any of those sources change we need to rescan, otherwise we may not notice changes to modules.
This requires that every Fortran target that uses modules have a full dependency on the scan target of it's dependencies. This means that for a three step target `A -> B -> C`, we cannot start compiling any of B until all of A is linked, and cannot start compiling any of C until all of A and B is linked. This fixes various kinds of races, but it serializes the build and makes it slow. This is the best we can do though, since we don't have any sort of portable format for telling C what is in A and B, so C can't know what sources to wait on for it's modules to be fulfilled.
This splits the scanner into two discrete steps, one that scans the source files, and one that that reads in the dependency information and produces a dyndep. The scanner uses the JSON format from https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html, which is the same format the MSVC and Clang use for C++ modules scanning. This will allow us to more easily move to using MSVC and clang-scan-deps when possible. As an added bonus, this correctly tracks dependencies across TU and Target boundaries, unlike the previous implementation, which assumed that if it couldn't find a provider that everything was good, but could run into issues. Because of that limitation Fortran code had to fully depend on all of it's dependencies, transitive or not. Now, when using the dep scanner, we can remove that restriction, allowing more parallelism.
We already test for custom_targets and configure_file, but we should test a generator too
Since the comment saying we need a generic way to do this is a little outdated.
dddad34
to
0ce68be
Compare
This series does a bit of cleanup and optimization as well, but the main goal is to have a scanner that outputs P1689r5 compatible JSON scanning information, and then a second step that reads in multiple JSON files and produces a dyndep.
This has a couple of advantages:
This is really about getting to the point of having reliable C++ module support for both MSVC and Clang using their provided tools (GCC has gone down a very strange path which is different than MSVC, Clang, and Gfortran), but as a clean first step