Skip to content

Commit 1120131

Browse files
Flush bitcode incrementally for LTO output
Bitcode writer does not flush buffer until the end by default. This is fine to small bitcode files. When -flto,--plugin-opt=emit-llvm,-gmlt are used, the final bitcode file is large, for example, >8G. Keeping all data in memory consumes a lot of memory. This change allows bitcode writer flush data to disk early when buffered data size is above some threshold. This is only enabled when lld emits LLVM bitcode. One issue to address is backpatching bitcode: subblock length, function body indexes, meta data indexes need to backfill. If buffer can be flushed partially, we introduced raw_fd_stream that supports read/seek/write, and enables backpatching bitcode flushed in disk. Reviewed-by: tejohnson, MaskRay Differential Revision: https://reviews.llvm.org/D86905
1 parent 0dd4d70 commit 1120131

File tree

4 files changed

+115
-15
lines changed

4 files changed

+115
-15
lines changed

lld/ELF/LTO.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,19 @@ static std::unique_ptr<raw_fd_ostream> openFile(StringRef file) {
5757
return ret;
5858
}
5959

60+
// The merged bitcode after LTO is large. Try openning a file stream that
61+
// supports reading, seeking and writing. Such a file allows BitcodeWriter to
62+
// flush buffered data to reduce memory comsuption. If this fails, open a file
63+
// stream that supports only write.
64+
static std::unique_ptr<raw_fd_ostream> openLTOOutputFile(StringRef file) {
65+
std::error_code ec;
66+
std::unique_ptr<raw_fd_ostream> fs =
67+
std::make_unique<raw_fd_stream>(file, ec);
68+
if (!ec)
69+
return fs;
70+
return openFile(file);
71+
}
72+
6073
static std::string getThinLTOOutputFile(StringRef modulePath) {
6174
return lto::getThinLTOOutputFile(
6275
std::string(modulePath), std::string(config->thinLTOPrefixReplace.first),
@@ -151,7 +164,8 @@ static lto::Config createConfig() {
151164

152165
if (config->emitLLVM) {
153166
c.PostInternalizeModuleHook = [](size_t task, const Module &m) {
154-
if (std::unique_ptr<raw_fd_ostream> os = openFile(config->outputFile))
167+
if (std::unique_ptr<raw_fd_ostream> os =
168+
openLTOOutputFile(config->outputFile))
155169
WriteBitcodeToFile(m, *os, false);
156170
return false;
157171
};

llvm/include/llvm/Bitcode/BitcodeWriter.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class raw_ostream;
4747

4848
public:
4949
/// Create a BitcodeWriter that writes to Buffer.
50-
BitcodeWriter(SmallVectorImpl<char> &Buffer);
50+
BitcodeWriter(SmallVectorImpl<char> &Buffer, raw_fd_stream *FS = nullptr);
5151

5252
~BitcodeWriter();
5353

llvm/include/llvm/Bitstream/BitstreamWriter.h

Lines changed: 91 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,27 @@
2020
#include "llvm/ADT/StringRef.h"
2121
#include "llvm/Bitstream/BitCodes.h"
2222
#include "llvm/Support/Endian.h"
23+
#include "llvm/Support/raw_ostream.h"
2324
#include <vector>
2425

2526
namespace llvm {
2627

2728
class BitstreamWriter {
29+
/// Out - The buffer that keeps unflushed bytes.
2830
SmallVectorImpl<char> &Out;
2931

32+
/// FS - The file stream that Out flushes to. If FS is nullptr, it does not
33+
/// support read or seek, Out cannot be flushed until all data are written.
34+
raw_fd_stream *FS;
35+
36+
/// FlushThreshold - If FS is valid, this is the threshold (unit B) to flush
37+
/// FS.
38+
const uint64_t FlushThreshold;
39+
3040
/// CurBit - Always between 0 and 31 inclusive, specifies the next bit to use.
3141
unsigned CurBit;
3242

33-
/// CurValue - The current value. Only bits < CurBit are valid.
43+
/// CurValue - The current value. Only bits < CurBit are valid.
3444
uint32_t CurValue;
3545

3646
/// CurCodeSize - This is the declared size of code values used for the
@@ -64,25 +74,49 @@ class BitstreamWriter {
6474

6575
void WriteByte(unsigned char Value) {
6676
Out.push_back(Value);
77+
FlushToFile();
6778
}
6879

6980
void WriteWord(unsigned Value) {
7081
Value = support::endian::byte_swap<uint32_t, support::little>(Value);
7182
Out.append(reinterpret_cast<const char *>(&Value),
7283
reinterpret_cast<const char *>(&Value + 1));
84+
FlushToFile();
7385
}
7486

75-
size_t GetBufferOffset() const { return Out.size(); }
87+
uint64_t GetNumOfFlushedBytes() const { return FS ? FS->tell() : 0; }
88+
89+
size_t GetBufferOffset() const { return Out.size() + GetNumOfFlushedBytes(); }
7690

7791
size_t GetWordIndex() const {
7892
size_t Offset = GetBufferOffset();
7993
assert((Offset & 3) == 0 && "Not 32-bit aligned");
8094
return Offset / 4;
8195
}
8296

97+
/// If the related file stream supports reading, seeking and writing, flush
98+
/// the buffer if its size is above a threshold.
99+
void FlushToFile() {
100+
if (!FS)
101+
return;
102+
if (Out.size() < FlushThreshold)
103+
return;
104+
FS->write((char *)&Out.front(), Out.size());
105+
Out.clear();
106+
}
107+
83108
public:
84-
explicit BitstreamWriter(SmallVectorImpl<char> &O)
85-
: Out(O), CurBit(0), CurValue(0), CurCodeSize(2) {}
109+
/// Create a BitstreamWriter that writes to Buffer \p O.
110+
///
111+
/// \p FS is the file stream that \p O flushes to incrementally. If \p FS is
112+
/// null, \p O does not flush incrementially, but writes to disk at the end.
113+
///
114+
/// \p FlushThreshold is the threshold (unit M) to flush \p O if \p FS is
115+
/// valid.
116+
BitstreamWriter(SmallVectorImpl<char> &O, raw_fd_stream *FS = nullptr,
117+
uint32_t FlushThreshold = 512)
118+
: Out(O), FS(FS), FlushThreshold(FlushThreshold << 20), CurBit(0),
119+
CurValue(0), CurCodeSize(2) {}
86120

87121
~BitstreamWriter() {
88122
assert(CurBit == 0 && "Unflushed data remaining");
@@ -104,11 +138,59 @@ class BitstreamWriter {
104138
void BackpatchWord(uint64_t BitNo, unsigned NewWord) {
105139
using namespace llvm::support;
106140
uint64_t ByteNo = BitNo / 8;
107-
assert((!endian::readAtBitAlignment<uint32_t, little, unaligned>(
108-
&Out[ByteNo], BitNo & 7)) &&
109-
"Expected to be patching over 0-value placeholders");
110-
endian::writeAtBitAlignment<uint32_t, little, unaligned>(
111-
&Out[ByteNo], NewWord, BitNo & 7);
141+
uint64_t StartBit = BitNo & 7;
142+
uint64_t NumOfFlushedBytes = GetNumOfFlushedBytes();
143+
144+
if (ByteNo >= NumOfFlushedBytes) {
145+
assert((!endian::readAtBitAlignment<uint32_t, little, unaligned>(
146+
&Out[ByteNo - NumOfFlushedBytes], StartBit)) &&
147+
"Expected to be patching over 0-value placeholders");
148+
endian::writeAtBitAlignment<uint32_t, little, unaligned>(
149+
&Out[ByteNo - NumOfFlushedBytes], NewWord, StartBit);
150+
return;
151+
}
152+
153+
// If the byte offset to backpatch is flushed, use seek to backfill data.
154+
// First, save the file position to restore later.
155+
uint64_t CurPos = FS->tell();
156+
157+
// Copy data to update into Bytes from the file FS and the buffer Out.
158+
char Bytes[8];
159+
size_t BytesNum = StartBit ? 8 : 4;
160+
size_t BytesFromDisk = std::min(BytesNum, NumOfFlushedBytes - ByteNo);
161+
size_t BytesFromBuffer = BytesNum - BytesFromDisk;
162+
163+
// When unaligned, copy existing data into Bytes from the file FS and the
164+
// buffer Out so that it can be updated before writing. For debug builds
165+
// read bytes unconditionally in order to check that the existing value is 0
166+
// as expected.
167+
#ifdef NDEBUG
168+
if (StartBit)
169+
#endif
170+
{
171+
FS->seek(ByteNo);
172+
ssize_t BytesRead = FS->read(Bytes, BytesFromDisk);
173+
(void)BytesRead; // silence warning
174+
assert(BytesRead >= 0 && static_cast<size_t>(BytesRead) == BytesFromDisk);
175+
for (size_t i = 0; i < BytesFromBuffer; ++i)
176+
Bytes[BytesFromDisk + i] = Out[i];
177+
assert((!endian::readAtBitAlignment<uint32_t, little, unaligned>(
178+
Bytes, StartBit)) &&
179+
"Expected to be patching over 0-value placeholders");
180+
}
181+
182+
// Update Bytes in terms of bit offset and value.
183+
endian::writeAtBitAlignment<uint32_t, little, unaligned>(Bytes, NewWord,
184+
StartBit);
185+
186+
// Copy updated data back to the file FS and the buffer Out.
187+
FS->seek(ByteNo);
188+
FS->write(Bytes, BytesFromDisk);
189+
for (size_t i = 0; i < BytesFromBuffer; ++i)
190+
Out[i] = Bytes[BytesFromDisk + i];
191+
192+
// Restore the file position.
193+
FS->seek(CurPos);
112194
}
113195

114196
void BackpatchWord64(uint64_t BitNo, uint64_t Val) {

llvm/lib/Bitcode/Writer/BitcodeWriter.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ static cl::opt<unsigned>
8686
IndexThreshold("bitcode-mdindex-threshold", cl::Hidden, cl::init(25),
8787
cl::desc("Number of metadatas above which we emit an index "
8888
"to enable lazy-loading"));
89+
static cl::opt<uint32_t> FlushThreshold(
90+
"bitcode-flush-threshold", cl::Hidden, cl::init(512),
91+
cl::desc("The threshold (unit M) for flushing LLVM bitcode."));
8992

9093
static cl::opt<bool> WriteRelBFToSummary(
9194
"write-relbf-to-summary", cl::Hidden, cl::init(false),
@@ -4453,8 +4456,8 @@ static void writeBitcodeHeader(BitstreamWriter &Stream) {
44534456
Stream.Emit(0xD, 4);
44544457
}
44554458

4456-
BitcodeWriter::BitcodeWriter(SmallVectorImpl<char> &Buffer)
4457-
: Buffer(Buffer), Stream(new BitstreamWriter(Buffer)) {
4459+
BitcodeWriter::BitcodeWriter(SmallVectorImpl<char> &Buffer, raw_fd_stream *FS)
4460+
: Buffer(Buffer), Stream(new BitstreamWriter(Buffer, FS, FlushThreshold)) {
44584461
writeBitcodeHeader(*Stream);
44594462
}
44604463

@@ -4565,7 +4568,7 @@ void llvm::WriteBitcodeToFile(const Module &M, raw_ostream &Out,
45654568
if (TT.isOSDarwin() || TT.isOSBinFormatMachO())
45664569
Buffer.insert(Buffer.begin(), BWH_HeaderSize, 0);
45674570

4568-
BitcodeWriter Writer(Buffer);
4571+
BitcodeWriter Writer(Buffer, dyn_cast<raw_fd_stream>(&Out));
45694572
Writer.writeModule(M, ShouldPreserveUseListOrder, Index, GenerateHash,
45704573
ModHash);
45714574
Writer.writeSymtab();
@@ -4575,7 +4578,8 @@ void llvm::WriteBitcodeToFile(const Module &M, raw_ostream &Out,
45754578
emitDarwinBCHeaderAndTrailer(Buffer, TT);
45764579

45774580
// Write the generated bitstream to "Out".
4578-
Out.write((char*)&Buffer.front(), Buffer.size());
4581+
if (!Buffer.empty())
4582+
Out.write((char *)&Buffer.front(), Buffer.size());
45794583
}
45804584

45814585
void IndexBitcodeWriter::write() {

0 commit comments

Comments
 (0)