mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-27 02:56:05 +00:00

This relands 6c31104. The patch was reverted due to incorrectly introduced alignment. And the patch was re-commited after fixing the alignment issue. Following off are the original message: This is part of "no transitive change" patch series, "no transitive source location change". I talked this with @Bigcheese in the tokyo's WG21 meeting. The idea comes from @jyknight posted on LLVM discourse. That for: ``` // A.cppm export module A; ... // B.cppm export module B; import A; ... //--- C.cppm export module C; import C; ``` Almost every time A.cppm changes, we need to recompile `B`. Due to we think the source location is significant to the semantics. But it may be good if we can avoid recompiling `C` if the change from `A` wouldn't change the BMI of B. This patch only cares source locations. So let's focus on source location's example. We can see the full example from the attached test. ``` //--- A.cppm export module A; export template <class T> struct C { T func() { return T(43); } }; export int funcA() { return 43; } //--- A.v1.cppm export module A; export template <class T> struct C { T func() { return T(43); } }; export int funcA() { return 43; } //--- B.cppm export module B; import A; export int funcB() { return funcA(); } //--- C.cppm export module C; import A; export void testD() { C<int> c; c.func(); } ``` Here the only difference between `A.cppm` and `A.v1.cppm` is that `A.v1.cppm` has an additional blank line. Then the test shows that two BMI of `B.cppm`, one specified `-fmodule-file=A=A.pcm` and the other specified `-fmodule-file=A=A.v1.pcm`, should have the bit-wise same contents. However, it is a different story for C, since C instantiates templates from A, and the instantiation records the source information from module A, which is different from `A` and `A.v1`, so it is expected that the BMI `C.pcm` and `C.v1.pcm` can and should differ. To fully understand the patch, we need to understand how we encodes source locations and how we serialize and deserialize them. For source locations, we encoded them as: ``` | | | _____ base offset of an imported module | | | |_____ base offset of another imported module | | | | | ___ 0 ``` As the diagram shows, we encode the local (unloaded) source location from 0 to higher bits. And we allocate the space for source locations from the loaded modules from high bits to 0. Then the source locations from the loaded modules will be mapped to our source location space according to the allocated offset. For example, for, ``` // a.cppm export module a; ... // b.cppm export module b; import a; ... ``` Assuming the offset of a source location (let's name the location as `S`) in a.cppm is 45 and we will record the value `45` into the BMI `a.pcm`. Then in b.cppm, when we import a, the source manager will allocate a space for module 'a' (according to the recorded number of source locations) as the base offset of module 'a' in the current source location spaces. Let's assume the allocated base offset as 90 in this example. Then when we want to get the location in the current source location space for `S`, we can get it simply by adding `45` to `90` to `135`. Finally we can get the source location for `S` in module B as `135`. And when we want to write module `b`, we would also write the source location of `S` as `135` directly in the BMI. And to clarify the location `S` comes from module `a`, we also need to record the base offset of module `a`, 90 in the BMI of `b`. Then the problem comes. Since the base offset of module 'a' is computed by the number source locations in module 'a'. In module 'b', the recorded base offset of module 'a' will change every time the number of source locations in module 'a' increase or decrease. In other words, the contents of BMI of B will change every time the number of locations in module 'a' changes. This is pretty sensitive. Almost every change will change the number of locations. So this is the problem this patch want to solve. Let's continue with the existing design to understand what's going on. Another interesting case is: ``` // c.cppm export module c; import whatever; import a; import b; ... ``` In `c.cppm`, when we import `a`, we still need to allocate a base location offset for it, let's say the value becomes to `200` somehow. Then when we reach the location `S` recorded in module `b`, we need to translate it into the current source location space. The solution is quite simple, we can get it by `135 + (200 - 90) = 245`. In another word, the offset of a source location in current module can be computed as `Recorded Offset + Base Offset of the its module file - Recorded Base Offset`. Then we're almost done about how we handle the offset of source locations in serializers. From the abstract level, what we want to do is to remove the hardcoded base offset of imported modules and remain the ability to calculate the source location in a new module unit. To achieve this, we need to be able to find the module file owning a source location from the encoding of the source location. So in this patch, for each source location, we will store the local offset of the location and the module file index. For the above example, in `b.pcm`, the source location of `S` will be recorded as `135` directly. And in the new design, the source location of `S` will be recorded as `<1, 45>`. Here `1` stands for the module file index of `a` in module `b`. And `45` means the offset of `S` to the base offset of module `a`. So the trade-off here is that, to make the BMI more independent, we need to record more abstract information. And I feel it is worthy. The recompilation problem of modules is really annoying and there are still people complaining this. But if we can make this (including stopping other changes transitively), I think this may be a killer feature for modules. And from @Bigcheese , this should be helpful for clang explicit modules too. And the benchmarking side, I tested this patch against https://github.com/alibaba/async_simple/tree/CXX20Modules. No significant change on compilation time. The size of .pcm files becomes to 204M from 200M. I think the trade-off is pretty fair. I didn't use another slot to record the module file index. I tried to use the higher 32 bits of the existing source location encodings to store that information. This design may be safe. Since we use `unsigned` to store source locations but we use uint64_t in serialization. And generally `unsigned` is 32 bit width in most platforms. So it might not be a safe problem. Since all the bits we used to store the module file index is not used before. So the new encodings may be: ``` |-----------------------|-----------------------| | A | B | C | * A: 32 bit. The index of the module file in the module manager + 1. * The +1 here is necessary since we wish 0 stands for the current module file. * B: 31 bit. The offset of the source location to the module file * containing it. * C: The macro bit. We rotate it to the lowest bit so that we can save * some space in case the index of the module file is 0. ``` (The B and C is the existing raw encoding for source locations) Another reason to reuse the same slot of the source location is to reduce the impact of the patch. Since there are a lot of places assuming we can store and get a source location from a slot. And if I tried to add another slot, a lot of codes breaks. I don't feel it is worhty. Another impact of this decision is that, the existing small optimizations for encoding source location may be invalided. The key of the optimization is that we can turn large values into small values then we can use VBR6 format to reduce the size. But if we decided to put the module file index into the higher bits, then maybe it simply doesn't work. An example may be the `SourceLocationSequence` optimization. This will only affect the size of on-disk .pcm files. I don't expect this impact the speed and memory use of compilations. And seeing my small experiments above, I feel this trade off is worthy. The mental model for handling source location offsets is not so complex and I believe we can solve it by adding module file index to each stored source location. For the practical side, since the source location is pretty sensitive, and the patch can pass all the in-tree tests and a small scale projects, I feel it should be correct. I'll continue to work on no transitive decl change and no transitive identifier change (if matters) to achieve the goal to stop the propagation of unnecessary changes. But all of this depends on this patch. Since, clearly, the source locations are the most sensitive thing. --- The release nots and documentation will be added seperately.
110 lines
3.4 KiB
C++
110 lines
3.4 KiB
C++
//===- unittests/Serialization/SourceLocationEncodingTests.cpp ------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/Serialization/SourceLocationEncoding.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
#include <climits>
|
|
#include <optional>
|
|
|
|
using namespace llvm;
|
|
using namespace clang;
|
|
|
|
namespace {
|
|
using LocSeq = SourceLocationSequence;
|
|
|
|
// Convert a single source location into encoded form and back.
|
|
// If ExpectedEncoded is provided, verify the encoded value too.
|
|
// Loc is the raw (in-memory) form of SourceLocation.
|
|
void roundTrip(SourceLocation::UIntTy Loc,
|
|
std::optional<uint64_t> ExpectedEncoded = std::nullopt) {
|
|
uint64_t ActualEncoded = SourceLocationEncoding::encode(
|
|
SourceLocation::getFromRawEncoding(Loc), /*BaseOffset=*/0,
|
|
/*BaseModuleFileIndex=*/0);
|
|
if (ExpectedEncoded) {
|
|
ASSERT_EQ(ActualEncoded, *ExpectedEncoded) << "Encoding " << Loc;
|
|
}
|
|
SourceLocation::UIntTy DecodedEncoded =
|
|
SourceLocationEncoding::decode(ActualEncoded).first.getRawEncoding();
|
|
ASSERT_EQ(DecodedEncoded, Loc) << "Decoding " << ActualEncoded;
|
|
}
|
|
|
|
// As above, but use sequence encoding for a series of locations.
|
|
void roundTrip(std::vector<SourceLocation::UIntTy> Locs,
|
|
std::vector<uint64_t> ExpectedEncoded = {}) {
|
|
std::vector<uint64_t> ActualEncoded;
|
|
{
|
|
LocSeq::State Seq;
|
|
for (auto L : Locs)
|
|
ActualEncoded.push_back(SourceLocationEncoding::encode(
|
|
SourceLocation::getFromRawEncoding(L), /*BaseOffset=*/0,
|
|
/*BaseModuleFileIndex=*/0, Seq));
|
|
if (!ExpectedEncoded.empty()) {
|
|
ASSERT_EQ(ActualEncoded, ExpectedEncoded)
|
|
<< "Encoding " << testing::PrintToString(Locs);
|
|
}
|
|
}
|
|
std::vector<SourceLocation::UIntTy> DecodedEncoded;
|
|
{
|
|
LocSeq::State Seq;
|
|
for (auto L : ActualEncoded) {
|
|
SourceLocation Loc = SourceLocationEncoding::decode(L, Seq).first;
|
|
DecodedEncoded.push_back(Loc.getRawEncoding());
|
|
}
|
|
ASSERT_EQ(DecodedEncoded, Locs)
|
|
<< "Decoding " << testing::PrintToString(ActualEncoded);
|
|
}
|
|
}
|
|
|
|
constexpr SourceLocation::UIntTy MacroBit =
|
|
1 << (sizeof(SourceLocation::UIntTy) * CHAR_BIT - 1);
|
|
constexpr SourceLocation::UIntTy Big = MacroBit >> 1;
|
|
constexpr SourceLocation::UIntTy Biggest = -1;
|
|
|
|
TEST(SourceLocationEncoding, Individual) {
|
|
roundTrip(1, 2);
|
|
roundTrip(100, 200);
|
|
roundTrip(MacroBit, 1);
|
|
roundTrip(MacroBit | 5, 11);
|
|
roundTrip(Big);
|
|
roundTrip(Big + 1);
|
|
roundTrip(MacroBit | Big);
|
|
roundTrip(MacroBit | (Big + 1));
|
|
}
|
|
|
|
TEST(SourceLocationEncoding, Sequence) {
|
|
roundTrip({1, 2, 3, 3, 2, 1},
|
|
{2, // 1
|
|
5, // +2 (+1 of non-raw)
|
|
5, // +2
|
|
1, // +0
|
|
4, // -2
|
|
4} // -2
|
|
);
|
|
roundTrip({100, 0, 100},
|
|
{200, // 100
|
|
0, // 0
|
|
1} // +0
|
|
);
|
|
|
|
roundTrip({1, Big}, {2, ((Big - 1) << 2) + 1});
|
|
roundTrip({2, MacroBit | Big}, {4, ((Big - 1) << 2) - 1});
|
|
|
|
roundTrip({3, MacroBit | 5, MacroBit | 4, 3},
|
|
{6, // 3
|
|
11, // +5 (+2 of non-raw + set macro bit)
|
|
4, // -2
|
|
6} // -3 (-2 of non-raw, clear macro bit)
|
|
);
|
|
|
|
roundTrip(
|
|
{123 | MacroBit, 1, 9, Biggest, Big, Big + 1, 0, MacroBit | Big, 0});
|
|
}
|
|
|
|
} // namespace
|