-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[Doc] Update documentation for no-transitive-change #96453
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 @llvm/pr-subscribers-clang-modules Author: Chuanqi Xu (ChuanqiXu9) Changes(Some backgrounds, not required to read: https://discourse.llvm.org/t/rfc-c-20-modules-introduce-thin-bmi-and-decls-hash/74755) This is the document part for the no-transitive-change (#86912, #92083, #92085, #92511) to provide the ability for build system to skip some unnecessary recompilations. See the patch for examples. CC: @mathstuf @boris-kolpackov Full diff: https://github.com/llvm/llvm-project/pull/96453.diff 2 Files Affected:
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 9c8f8c4a4fbaf..89e433870c9ff 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -229,6 +229,10 @@ C++20 Feature Support
will now work.
(#GH62925).
+- Clang refactored the BMI format to make it possible to support no transitive changes
+ mode for modules. See `StandardCPlusPlusModules <StandardCPlusPlusModules.html>`_ for
+ details.
+
C++23 Feature Support
^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/docs/StandardCPlusPlusModules.rst b/clang/docs/StandardCPlusPlusModules.rst
index 1c3c4d319c0e1..68854636e617d 100644
--- a/clang/docs/StandardCPlusPlusModules.rst
+++ b/clang/docs/StandardCPlusPlusModules.rst
@@ -652,6 +652,134 @@ in the future. The expected roadmap for Reduced BMIs as of Clang 19.x is:
comes, the term BMI will refer to the Reduced BMI and the Full BMI will only
be meaningful to build systems which elect to support two-phase compilation.
+Experimental No Transitive Change
+---------------------------------
+
+Starting with clang19.x, we introduced an experimental feature: the non-transitive
+change for modules, aimed at reducing unnecessary recompilations. For example,
+
+.. code-block:: c++
+
+ // m-partA.cppm
+ export module m:partA;
+
+ // m-partB.cppm
+ export module m:partB;
+ export int getB() { return 44; }
+
+ // m.cppm
+ export module m;
+ export import :partA;
+ export import :partB;
+
+ // useBOnly.cppm
+ export module useBOnly;
+ import m;
+ export int B() {
+ return getB();
+ }
+
+ // Use.cc
+ import useBOnly;
+ int get() {
+ return B();
+ }
+
+Now let's compile the project (For brevity, some commands are omitted.):
+
+.. code-block:: console
+
+ $ clang++ -std=c++20 m-partA.cppm --precompile -o m-partA.pcm
+ $ clang++ -std=c++20 m-partB.cppm --precompile -o m-partB.pcm
+ $ clang++ -std=c++20 m.cppm --precompile -o m.pcm -fprebuilt-module-path=.
+ $ clang++ -std=c++20 useBOnly.cppm --precompile -o useBOnly.pcm -fprebuilt-module-path=.
+ $ md5sum useBOnly.pcm
+ 07656bf4a6908626795729295f9608da useBOnly.pcm
+
+then let's change the interface of ``m-partA.cppm`` to:
+
+.. code-block:: c++
+
+ // m-partA.v1.cppm
+ export module m:partA;
+ export int getA() { return 43; }
+
+Let's compile the BMI for `useBOnly` again:
+
+.. code-block:: console
+
+ $ clang++ -std=c++20 m-partA.cppm --precompile -o m-partA.pcm
+ $ clang++ -std=c++20 m-partB.cppm --precompile -o m-partB.pcm
+ $ clang++ -std=c++20 m.cppm --precompile -o m.pcm -fprebuilt-module-path=.
+ $ clang++ -std=c++20 useBOnly.cppm --precompile -o useBOnly.pcm -fprebuilt-module-path=.
+ $ md5sum useBOnly.pcm
+ 07656bf4a6908626795729295f9608da useBOnly.pcm
+
+We observed that the contents of useBOnly.pcm remain unchanged.
+Consequently, if the build system bases recompilation decisions on directly imported modules only,
+it becomes possible to skip the recompilation of Use.cc.
+It should be fine because the altered interfaces do not affect Use.cc in any way.
+This concept is called as no transitive changes.
+
+When clang generates a BMI, it records the hash values of all potentially contributory BMIs
+into the currently written BMI. This ensures that build systems are not required to consider
+transitively imported modules when deciding on recompilations.
+
+The definition for potential contributory BMIs is implementation defined. We don't intend to
+display detailed rules for users. The contract is:
+
+1. It is a severe bug if a BMI remains unchanged erroneously following an observable change
+ that affects its users.
+2. It is an potential improvement opportunity if a BMI changes after an unobservable change
+ happens.
+
+We suggest build systems to support this feature as a configurable option for a long time.
+So that users can go back to the transitive change mode safely at any time.
+
+Interactions with Reduced BMI
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+With reduced BMI, the no transitive change feature can be more powerful if the change
+can be reduced. For example,
+
+.. code-block:: c++
+
+ // A.cppm
+ export module A;
+ export int a() { return 44; }
+
+ // B.cppm
+ export module B;
+ import A;
+ export int b() { return a(); }
+
+.. code-block:: console
+
+ $ clang++ -std=c++20 A.cppm -c -fmodule-output=A.pcm -fexperimental-modules-reduced-bmi -o A.o
+ $ clang++ -std=c++20 B.cppm -c -fmodule-output=B.pcm -fexperimental-modules-reduced-bmi -o B.o -fmodule-file=A=A.pcm
+ $md5sum B.pcm
+ 6c2bd452ca32ab418bf35cd141b060b9 B.pcm
+
+And let's change the implementation for ``A.cppm`` into:
+
+.. code-block:: c++
+
+ export module A;
+ int a_impl() { return 99; }
+ export int a() { return a_impl(); }
+
+and recompile the example:
+
+.. code-block:: console
+
+ $ clang++ -std=c++20 A.cppm -c -fmodule-output=A.pcm -fexperimental-modules-reduced-bmi -o A.o
+ $ clang++ -std=c++20 B.cppm -c -fmodule-output=B.pcm -fexperimental-modules-reduced-bmi -o B.o -fmodule-file=A=A.pcm
+ $md5sum B.pcm
+ 6c2bd452ca32ab418bf35cd141b060b9 B.pcm
+
+We should find the contents of ``B.pcm`` keeps the same. In such case, the build system is
+allowed to skip recompilations of TUs which solely and directly dependent on module B.
+
Performance Tips
----------------
|
I really like the idea of documenting this, hopefully this create some fruitful discussions with the build system developers and other compiler users. After trying to put myself into the shoes of the compiler users reading this, I can't help but think that the "no transitive changes" is slightly confusing and hard to grasp. The example does help quite a bit, but I also feel there should be a way to document this in shorter form. (E.g. we can double-down on the "this is a build optimization" angle and being explicit that we want people to try this and let them know how to report bugs about it?) I wanted to take a little bit of time to think how to do it before providing concrete suggestions, but still wanted to mention this. |
How about adding a sentence like "this section is primarily for build system vendors. For end compiler users, if you don't want to read it all, this is helpful to reduce recompilations. We encourage build system vendors and end users try this out and bring feedbacks"? |
Yeah, SG, I'd also recommend adding something like that to the release notes (I suspect many more people will read it initially when Clang 19 is released than the section on C++20 Modules) |
clang/docs/ReleaseNotes.rst
Outdated
@@ -229,6 +229,10 @@ C++20 Feature Support | |||
will now work. | |||
(#GH62925). | |||
|
|||
- Clang refactored the BMI format to make it possible to support no transitive changes |
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.
Maybe explicitly mention the user-facing effect of what "no transitive change" means and mention that we want feedback on this?
Something like:
- Clang implemented improvements to BMI of C++20 Modules that should reduce the number of rebuilds during incremental recompilation. We are seeking feedback from Build System authors and other interested users, especially when you feel Clang changes the BMI and missses an opportunity to avoid recompilations or causes correctness issues. See
StandardCPlusPlusModules <StandardCPlusPlusModules.html>
_ for more details.
Also, maybe put this at the top to increase chances of people noticing this? (if we do want people filing bugs about it, it'd help to increase the chances that they do).
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.
Good suggestions. Applied. (Just change should
to can
. Since I feel should
read like users can get this benefit directly. But it is not the case. They need helps from build systems).
And I put this into the What's New in Clang |release|?
section, which is highest except the Potentially Breaking Change
section.
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.
Good suggestions. Applied. (Just change should to can. Since I feel should read like users can get this benefit directly. But it is not the case. They need helps from build systems).
Yeah, makse sense, "can" fits better here, we also want some follow up from the build system vendors.
And I put this into the What's New in Clang |release|? section, which is highest except the Potentially Breaking Change section.
This seems very reasonable, I am not sure this is a "potentially breaking change" even. It does change the BMI, but that's expected with every new version of the compiler.
@@ -652,6 +652,134 @@ in the future. The expected roadmap for Reduced BMIs as of Clang 19.x is: | |||
comes, the term BMI will refer to the Reduced BMI and the Full BMI will only | |||
be meaningful to build systems which elect to support two-phase compilation. | |||
|
|||
Experimental No Transitive Change |
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.
Is it actually experimental if it's the default and there is no way to disable it?
I suggest saying "initial", would it better to capture what we intend to do (I suspect we want to keep improving this and want feedback where it falls short)?
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'd like to find a better term than "no transitive change" -- this doesn't work well as a noun phrase in English, so we say awkward things like "...the no transitive change feature can be more powerful if ..." I think what bothers me about is the "no" and "change" part -- "change" is a verb, so it comes out sounding verb-like and we end up tacking on "feature" to try to make it a noun again.
That said, I don't have a significantly better phrase currently. :-)
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.
- "no-cascade" feature?
- "isolated BMI" (not sure if this is a great idea given how there's already full/reduced BMI)
- "BMI isolation"
- + variations with synonyms for isolated/secluded/confined/...
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.
Is it actually experimental if it's the default and there is no way to disable it?
This experiment can only be performed with the build systems. There shouldn't be any risk if the build systems implemented transitive changes model already. In another word, what I did actually provides an opportunity to the build systems to implement this feature. So ideally, the users should be able to disable it in the build system level.
I suggest saying "initial", would it better to capture what we intend to do (I suspect we want to keep improving this and want feedback where it falls short)?
Yes that we want to keep improving this and want feedback where it falls short. I used experimental
since I want to mention this feature may not be stable. It might be problematic that some recompilations got skipped but they shouldn't. So that users should go back to traditional mode in such cases. Then I feel experimental
sounds good for such situations.
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'd like to find a better term than "no transitive change" -- this doesn't work well as a noun phrase in English, so we say awkward things like "...the no transitive change feature can be more powerful if ..." I think what bothers me about is the "no" and "change" part -- "change" is a verb, so it comes out sounding verb-like and we end up tacking on "feature" to try to make it a noun again.
While I respect the feelings from native speakers, Google told me that change
can be a noun too. For the no
part, may be we can use the term less
? So that the feature name become to less transitive change
, which may be more precise since there should always some changes are transitive.
"no-cascade" feature?
I didn't know this word cascade
before. I think it may be appropriate after I searched it. I can replace with it if we're happy with that.
"isolated BMI"
isolated
may not be accurate. Since the BMI still depends on other BMIs.
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.
A slightly cleaner formulation might be "non-cascading X" or perhaps "non-percolating Y".
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.
On "experimental": thanks for clarifying that you meant this as "experimental in the build system".
I am afraid that the current wording actually does the opposite and tells that this the compiler support is "experimental" (aka "unstable", aka "don't touch this until it stabilizes").
On "no transitive change": I'm also not a native speaker, but this did sound weird to me as well. So +1 to changing this.
"Better isolation of BMI" would be my preferred option. "Better" communicates that the isolation is imperfect (in shades of gray rather than black and white), so I wouldn't expect that to cause any confusion. +
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.
For the name: Update with non-cascading change.
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.
On "experimental": thanks for clarifying that you meant this as "experimental in the build system". I am afraid that the current wording actually does the opposite and tells that this the compiler support is "experimental" (aka "unstable", aka "don't touch this until it stabilizes").
Yeah, and I like to call it as experimental with the build system
. Since although the users can only get it from build systems, the core abilities are provided by the compiler. Also when bug reports comes, I believe most of them need to solve in the compiler side. But this doesn't matter though : )
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.
Some wording changes; more may come as I better understand the concept.
@@ -652,6 +652,134 @@ in the future. The expected roadmap for Reduced BMIs as of Clang 19.x is: | |||
comes, the term BMI will refer to the Reduced BMI and the Full BMI will only | |||
be meaningful to build systems which elect to support two-phase compilation. | |||
|
|||
Experimental No Transitive Change |
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'd like to find a better term than "no transitive change" -- this doesn't work well as a noun phrase in English, so we say awkward things like "...the no transitive change feature can be more powerful if ..." I think what bothers me about is the "no" and "change" part -- "change" is a verb, so it comes out sounding verb-like and we end up tacking on "feature" to try to make it a noun again.
That said, I don't have a significantly better phrase currently. :-)
With reduced BMI, the no transitive change feature can be more powerful if the change | ||
can be reduced. For example, |
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'm not certain what "if the change can be reduced" is trying to say. Is it trying to say that removing code will have less transitive impacts on other BMIs than adding code?
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.
For example,
// B.cppm
export module B;
import A;
export int b() { return a(); }
If reduced BMI is not enabled, we will record a()
part in the BMI of B
, then we will think module A contributes to module B. Then the BMI of B
should change if module A changes like the example shows.
However, if reduced BMI enabled, we won't record the function body for int b()
, then module A
doesn't contributes to module B. So that we can avoid the changes for B if module A changes like the example shows.
Maybe it will be better to remove the if the change can be reduced
part. I'll add a sentence to describe the above idea to make it clear.
We recommend that build systems support this feature as a configurable option so that users | ||
can go back to the transitive change mode safely at any time. |
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 does this happen? Is something missing in the example commands? They look "normal" to me.
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.
Do you mean in what cases the users need to go back to normal mode? The answer may be when they meet compiler bugs.
Or if you're asking about the command line options, there is no such option in the compiler side. We refactored the format in the BMI so it is in some level always "enabled". But the users have to wait the build system's support to feel so called "no-transitive-change". So the "option" here means a build system option.
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.
Ok, so what do I do differently? AFAIK, CMake is relying on the compiler's -MF
reporting mechanisms here.
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.
In the higher level, after offering an experimental option to enable the mode, I think what build systems to do is, when considering the recompilations, the build system don't need to consider the indirectly imported modules. And for -MF
, as far as I know, it will collect the included headers and headers shouldn't take part in here.
For example,
// a.cpp
#include "a.h"
import a;
...
If a.h
changes, I think we should recompile a.cpp
.
For implementations, I don't know the implementations of cmake. But I remember you said somewhere that it might be possible to make it by a compile-and-swap action.
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, ok.
FWIW, I was thinking that even a.pcm
would get reported in -MF
for your example and elided if it didn't contribute. As for the restat = 1
with a swap, yes, that is build-system side. I think some docs saying something like:
Build systems may utilize this optimization by doing an update-if-changed operation to the BMI
that is consumed from the BMI that is output by the compiler.
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.
Suggestion Added
@@ -652,6 +652,134 @@ in the future. The expected roadmap for Reduced BMIs as of Clang 19.x is: | |||
comes, the term BMI will refer to the Reduced BMI and the Full BMI will only | |||
be meaningful to build systems which elect to support two-phase compilation. | |||
|
|||
Experimental No Transitive Change |
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.
On "experimental": thanks for clarifying that you meant this as "experimental in the build system".
I am afraid that the current wording actually does the opposite and tells that this the compiler support is "experimental" (aka "unstable", aka "don't touch this until it stabilizes").
On "no transitive change": I'm also not a native speaker, but this did sound weird to me as well. So +1 to changing this.
"Better isolation of BMI" would be my preferred option. "Better" communicates that the isolation is imperfect (in shades of gray rather than black and white), so I wouldn't expect that to cause any confusion. +
clang/docs/ReleaseNotes.rst
Outdated
@@ -229,6 +229,10 @@ C++20 Feature Support | |||
will now work. | |||
(#GH62925). | |||
|
|||
- Clang refactored the BMI format to make it possible to support no transitive changes |
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.
Good suggestions. Applied. (Just change should to can. Since I feel should read like users can get this benefit directly. But it is not the case. They need helps from build systems).
Yeah, makse sense, "can" fits better here, we also want some follow up from the build system vendors.
And I put this into the What's New in Clang |release|? section, which is highest except the Potentially Breaking Change section.
This seems very reasonable, I am not sure this is a "potentially breaking change" even. It does change the BMI, but that's expected with every new version of the compiler.
Experimental No Transitive Change | ||
--------------------------------- | ||
|
||
This section is primarily for build system vendors. For end compiler users, |
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 may have been discussed somewhere that I missed, please point me to that if that's the case.
I was putting a little more thought into it and am wondering what would be a way for the build systems to take advantage of that?
When compiling something that dependends on modules, the build system will need to check if any of the (transitive) dependencies of the modules changes, right? Because any BMI that was changed in the transitive set can potentially affect the result of the current compilations.
So even if direct dependencies did not change (because of this optimization), despite a change in the deeper transitive dependency, the build system still can't reuse the result and has to rerun the compile.
We only know that the output BMI wasn't affected by any of the transitive changes after we finish compiling it and can compare the outputs, right?
But the build system needs to know about it before the compilation happens to avoid recompilations.
I suspect I'm missing something, but don't know what...
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 may have been discussed somewhere that I missed, please point me to that if that's the case.
I was putting a little more thought into it and am wondering what would be a way for the build systems to take advantage of that?
When compiling something that dependends on modules, the build system will need to check if any of the (transitive) dependencies of the modules changes, right? Because any BMI that was changed in the transitive set can potentially affect the result of the current compilations.
So even if direct dependencies did not change (because of this optimization), despite a change in the deeper transitive dependency, the build system still can't reuse the result and has to rerun the compile.
Yes, and this is the reason why it is experimental.
We only know that the output BMI wasn't affected by any of the transitive changes after we finish compiling it and can compare the outputs, right? But the build system needs to know about it before the compilation happens to avoid recompilations.
I suspect I'm missing something, but don't know what...
It is not about to avoid the re-compilation for that unchanged BMI, but for other TUs that only dependent on the unchanged BMI. For example,
// a.cppm
export module a;
// intentional empty
// b.cppm
export module b;
import a;
// intentional empty
// c.cpp
import b;
...
For this example, every time a.cppm
changes, we need to recompile b.cppm
. But if the BMI of module B
doesn't change at all, it should be good to not recompile c.cpp
.
So this is the ability provided by the compiler that all the needed changes are propagated to the BMI:
llvm-project/clang/lib/Serialization/ASTWriter.cpp
Lines 1229 to 1230 in c791d86
for (Module *M : TouchedTopLevelModules) | |
Hasher.update(M->Signature); |
The theory of this is that, the users of a build system, can only access the entities in the indirectly imported modules via the directly imported modules. So that the directly imported modules have a full control of accessiable entities in the indirectly imported modules for their (the directly imported modules) users.
Maybe it is a good idea for the build system to provide a verify option that the skippable compilations can have the same result if they are not skipped.
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.
That's what I thought is happening, thanks for confirming my intuition.
I think it'd be great to spell this out in the documentation, because I believe this mode of operation is something that the build system vendors might need to infer from the whole text right now.
Adding something like the following should do the trick:
We encourage build systems to add an experimental mode that
reuses the cached BMI when **direct** dependencies did not change,
even if **transitive** dependencies did change.
PS My 2 cents on Bazel: I suspect this actually goes against its design. Bazel has very strong capabilities to avoid recompilations when inputs don't change, but it relies on stable hashes for all inputs (including transitive dependencies).
At the same time I suspect that other build systems (CMake
+ ccache
/ sccache
) are more flexible in that regard.
PPS I am not a build system expert, so take that with a grain of salt. I'm also happy to loop in Bazel folks I work with to confirm or rebut my claims about Bazel's design, if that's useful.
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, added suggested text.
For bazel, I think it might be fine since it should be configurable that whether not to recompile a target as far as I know.
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.
Some minor corrections for grammar.
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.
LGTM but please wait for other active reviewers to sign off before landing.
I am going to land this in later of the week if no objections come. As 19.x is going to be branched. |
Summary: (Some backgrounds, not required to read: https://discourse.llvm.org/t/rfc-c-20-modules-introduce-thin-bmi-and-decls-hash/74755) This is the document part for the no-transitive-change (#86912, #92083, #92085, #92511) to provide the ability for build system to skip some unnecessary recompilations. See the patch for examples. Test Plan: Reviewers: Subscribers: Tasks: Tags: Differential Revision: https://phabricator.intern.facebook.com/D60251509
(Some backgrounds, not required to read: https://discourse.llvm.org/t/rfc-c-20-modules-introduce-thin-bmi-and-decls-hash/74755)
This is the document part for the no-transitive-change (#86912, #92083, #92085, #92511) to provide the ability for build system to skip some unnecessary recompilations.
See the patch for examples.
CC: @mathstuf @boris-kolpackov