Skip to content

Commit 1ce8319

Browse files
committed
[Support] Add stream tie function and use it for errs()
errs() is now tied to outs() so that if something prints to errs(), outs() will be flushed before the printing occurs. This avoids interleaving output between the two and is consistent with standard cout and cerr behaviour. Reviewed by: labath, JDevlieghere, MaskRay Differential Revision: https://reviews.llvm.org/D81156
1 parent 4515d35 commit 1ce8319

File tree

3 files changed

+106
-7
lines changed

3 files changed

+106
-7
lines changed

llvm/include/llvm/Support/raw_ostream.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ class raw_ostream {
6666
char *OutBufStart, *OutBufEnd, *OutBufCur;
6767
bool ColorEnabled = false;
6868

69+
/// Optional stream this stream is tied to. If this stream is written to, the
70+
/// tied-to stream will be flushed first.
71+
raw_ostream *TiedStream = nullptr;
72+
6973
enum class BufferKind {
7074
Unbuffered = 0,
7175
InternalBuffer,
@@ -294,6 +298,10 @@ class raw_ostream {
294298
// changeColor() has no effect until enable_colors(true) is called.
295299
virtual void enable_colors(bool enable) { ColorEnabled = enable; }
296300

301+
/// Tie this stream to the specified stream. Replaces any existing tied-to
302+
/// stream. Specifying a nullptr unties the stream.
303+
void tie(raw_ostream *TieTo) { TiedStream = TieTo; }
304+
297305
//===--------------------------------------------------------------------===//
298306
// Subclass Interface
299307
//===--------------------------------------------------------------------===//
@@ -352,6 +360,9 @@ class raw_ostream {
352360
/// flushing. The result is affected by calls to enable_color().
353361
bool prepare_colors();
354362

363+
/// Flush the tied-to stream (if present) and then write the required data.
364+
void flush_tied_then_write(const char *Ptr, size_t Size);
365+
355366
virtual void anchor();
356367
};
357368

@@ -491,8 +502,11 @@ class raw_fd_ostream : public raw_pwrite_stream {
491502
/// like: outs() << "foo" << "bar";
492503
raw_fd_ostream &outs();
493504

494-
/// This returns a reference to a raw_fd_ostream for standard error. Use it
495-
/// like: errs() << "foo" << "bar";
505+
/// This returns a reference to a raw_ostream for standard error.
506+
/// Use it like: errs() << "foo" << "bar";
507+
/// By default, the stream is tied to stdout to ensure stdout is flushed before
508+
/// stderr is written, to ensure the error messages are written in their
509+
/// expected place.
496510
raw_fd_ostream &errs();
497511

498512
/// This returns a reference to a raw_ostream which simply discards output.

llvm/lib/Support/raw_ostream.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,15 +216,15 @@ void raw_ostream::flush_nonempty() {
216216
assert(OutBufCur > OutBufStart && "Invalid call to flush_nonempty.");
217217
size_t Length = OutBufCur - OutBufStart;
218218
OutBufCur = OutBufStart;
219-
write_impl(OutBufStart, Length);
219+
flush_tied_then_write(OutBufStart, Length);
220220
}
221221

222222
raw_ostream &raw_ostream::write(unsigned char C) {
223223
// Group exceptional cases into a single branch.
224224
if (LLVM_UNLIKELY(OutBufCur >= OutBufEnd)) {
225225
if (LLVM_UNLIKELY(!OutBufStart)) {
226226
if (BufferMode == BufferKind::Unbuffered) {
227-
write_impl(reinterpret_cast<char*>(&C), 1);
227+
flush_tied_then_write(reinterpret_cast<char *>(&C), 1);
228228
return *this;
229229
}
230230
// Set up a buffer and start over.
@@ -244,7 +244,7 @@ raw_ostream &raw_ostream::write(const char *Ptr, size_t Size) {
244244
if (LLVM_UNLIKELY(size_t(OutBufEnd - OutBufCur) < Size)) {
245245
if (LLVM_UNLIKELY(!OutBufStart)) {
246246
if (BufferMode == BufferKind::Unbuffered) {
247-
write_impl(Ptr, Size);
247+
flush_tied_then_write(Ptr, Size);
248248
return *this;
249249
}
250250
// Set up a buffer and start over.
@@ -260,7 +260,7 @@ raw_ostream &raw_ostream::write(const char *Ptr, size_t Size) {
260260
if (LLVM_UNLIKELY(OutBufCur == OutBufStart)) {
261261
assert(NumBytes != 0 && "undefined behavior");
262262
size_t BytesToWrite = Size - (Size % NumBytes);
263-
write_impl(Ptr, BytesToWrite);
263+
flush_tied_then_write(Ptr, BytesToWrite);
264264
size_t BytesRemaining = Size - BytesToWrite;
265265
if (BytesRemaining > size_t(OutBufEnd - OutBufCur)) {
266266
// Too much left over to copy into our buffer.
@@ -301,6 +301,12 @@ void raw_ostream::copy_to_buffer(const char *Ptr, size_t Size) {
301301
OutBufCur += Size;
302302
}
303303

304+
void raw_ostream::flush_tied_then_write(const char *Ptr, size_t Size) {
305+
if (TiedStream)
306+
TiedStream->flush();
307+
write_impl(Ptr, Size);
308+
}
309+
304310
// Formatted output.
305311
raw_ostream &raw_ostream::operator<<(const format_object_base &Fmt) {
306312
// If we have more than a few bytes left in our output buffer, try
@@ -870,8 +876,9 @@ raw_fd_ostream &llvm::outs() {
870876
}
871877

872878
raw_fd_ostream &llvm::errs() {
873-
// Set standard error to be unbuffered by default.
879+
// Set standard error to be unbuffered and tied to outs() by default.
874880
static raw_fd_ostream S(STDERR_FILENO, false, true);
881+
S.tie(&outs());
875882
return S;
876883
}
877884

llvm/unittests/Support/raw_ostream_test.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,4 +376,82 @@ TEST(raw_fd_ostreamTest, multiple_raw_fd_ostream_to_stdout) {
376376
{ raw_fd_ostream("-", EC, sys::fs::OpenFlags::OF_None); }
377377
{ raw_fd_ostream("-", EC, sys::fs::OpenFlags::OF_None); }
378378
}
379+
380+
TEST(raw_ostreamTest, flush_tied_to_stream_on_write) {
381+
std::string TiedToBuffer;
382+
raw_string_ostream TiedTo(TiedToBuffer);
383+
TiedTo.SetBuffered();
384+
TiedTo << "a";
385+
386+
std::string Buffer;
387+
raw_string_ostream TiedStream(Buffer);
388+
TiedStream.tie(&TiedTo);
389+
// Sanity check that the stream hasn't already been flushed.
390+
EXPECT_EQ("", TiedToBuffer);
391+
392+
// Empty string doesn't cause a flush of TiedTo.
393+
TiedStream << "";
394+
EXPECT_EQ("", TiedToBuffer);
395+
396+
// Non-empty strings trigger flush of TiedTo.
397+
TiedStream << "abc";
398+
EXPECT_EQ("a", TiedToBuffer);
399+
400+
// Single char write flushes TiedTo.
401+
TiedTo << "c";
402+
TiedStream << 'd';
403+
EXPECT_EQ("ac", TiedToBuffer);
404+
405+
// Write to buffered stream without flush does not flush TiedTo.
406+
TiedStream.SetBuffered();
407+
TiedStream.SetBufferSize(2);
408+
TiedTo << "e";
409+
TiedStream << "f";
410+
EXPECT_EQ("ac", TiedToBuffer);
411+
412+
// Explicit flush of buffered stream flushes TiedTo.
413+
TiedStream.flush();
414+
EXPECT_EQ("ace", TiedToBuffer);
415+
416+
// Explicit flush of buffered stream with empty buffer does not flush TiedTo.
417+
TiedTo << "g";
418+
TiedStream.flush();
419+
EXPECT_EQ("ace", TiedToBuffer);
420+
421+
// Write of data to empty buffer that is greater than buffer size flushes
422+
// TiedTo.
423+
TiedStream << "hijklm";
424+
EXPECT_EQ("aceg", TiedToBuffer);
425+
426+
// Write of data that overflows buffer size also flushes TiedTo.
427+
TiedStream.flush();
428+
TiedStream << "n";
429+
TiedTo << "o";
430+
TiedStream << "pq";
431+
EXPECT_EQ("acego", TiedToBuffer);
432+
433+
// Streams can be tied to each other safely.
434+
TiedStream.flush();
435+
Buffer = "";
436+
TiedTo.tie(&TiedStream);
437+
TiedTo.SetBufferSize(2);
438+
TiedStream << "r";
439+
TiedTo << "s";
440+
EXPECT_EQ("", Buffer);
441+
EXPECT_EQ("acego", TiedToBuffer);
442+
TiedTo << "tuv";
443+
EXPECT_EQ("r", Buffer);
444+
TiedStream << "wxy";
445+
EXPECT_EQ("acegostuv", TiedToBuffer);
446+
// The x remains in the buffer, since it was written after the flush of
447+
// TiedTo.
448+
EXPECT_EQ("rwx", Buffer);
449+
450+
// Calling tie with nullptr unties stream.
451+
TiedStream.SetUnbuffered();
452+
TiedStream.tie(nullptr);
453+
TiedTo << "y";
454+
TiedStream << "0";
455+
EXPECT_EQ("acegostuv", TiedToBuffer);
456+
}
379457
}

0 commit comments

Comments
 (0)