Skip to content

Commit

Permalink
[Support] Add stream tie function and use it for errs()
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jh7370 committed Jun 9, 2020
1 parent 4515d35 commit 1ce8319
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 7 deletions.
18 changes: 16 additions & 2 deletions llvm/include/llvm/Support/raw_ostream.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ class raw_ostream {
char *OutBufStart, *OutBufEnd, *OutBufCur;
bool ColorEnabled = false;

/// Optional stream this stream is tied to. If this stream is written to, the
/// tied-to stream will be flushed first.
raw_ostream *TiedStream = nullptr;

enum class BufferKind {
Unbuffered = 0,
InternalBuffer,
Expand Down Expand Up @@ -294,6 +298,10 @@ class raw_ostream {
// changeColor() has no effect until enable_colors(true) is called.
virtual void enable_colors(bool enable) { ColorEnabled = enable; }

/// Tie this stream to the specified stream. Replaces any existing tied-to
/// stream. Specifying a nullptr unties the stream.
void tie(raw_ostream *TieTo) { TiedStream = TieTo; }

//===--------------------------------------------------------------------===//
// Subclass Interface
//===--------------------------------------------------------------------===//
Expand Down Expand Up @@ -352,6 +360,9 @@ class raw_ostream {
/// flushing. The result is affected by calls to enable_color().
bool prepare_colors();

/// Flush the tied-to stream (if present) and then write the required data.
void flush_tied_then_write(const char *Ptr, size_t Size);

virtual void anchor();
};

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

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

/// This returns a reference to a raw_ostream which simply discards output.
Expand Down
17 changes: 12 additions & 5 deletions llvm/lib/Support/raw_ostream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,15 +216,15 @@ void raw_ostream::flush_nonempty() {
assert(OutBufCur > OutBufStart && "Invalid call to flush_nonempty.");
size_t Length = OutBufCur - OutBufStart;
OutBufCur = OutBufStart;
write_impl(OutBufStart, Length);
flush_tied_then_write(OutBufStart, Length);
}

raw_ostream &raw_ostream::write(unsigned char C) {
// Group exceptional cases into a single branch.
if (LLVM_UNLIKELY(OutBufCur >= OutBufEnd)) {
if (LLVM_UNLIKELY(!OutBufStart)) {
if (BufferMode == BufferKind::Unbuffered) {
write_impl(reinterpret_cast<char*>(&C), 1);
flush_tied_then_write(reinterpret_cast<char *>(&C), 1);
return *this;
}
// Set up a buffer and start over.
Expand All @@ -244,7 +244,7 @@ raw_ostream &raw_ostream::write(const char *Ptr, size_t Size) {
if (LLVM_UNLIKELY(size_t(OutBufEnd - OutBufCur) < Size)) {
if (LLVM_UNLIKELY(!OutBufStart)) {
if (BufferMode == BufferKind::Unbuffered) {
write_impl(Ptr, Size);
flush_tied_then_write(Ptr, Size);
return *this;
}
// Set up a buffer and start over.
Expand All @@ -260,7 +260,7 @@ raw_ostream &raw_ostream::write(const char *Ptr, size_t Size) {
if (LLVM_UNLIKELY(OutBufCur == OutBufStart)) {
assert(NumBytes != 0 && "undefined behavior");
size_t BytesToWrite = Size - (Size % NumBytes);
write_impl(Ptr, BytesToWrite);
flush_tied_then_write(Ptr, BytesToWrite);
size_t BytesRemaining = Size - BytesToWrite;
if (BytesRemaining > size_t(OutBufEnd - OutBufCur)) {
// Too much left over to copy into our buffer.
Expand Down Expand Up @@ -301,6 +301,12 @@ void raw_ostream::copy_to_buffer(const char *Ptr, size_t Size) {
OutBufCur += Size;
}

void raw_ostream::flush_tied_then_write(const char *Ptr, size_t Size) {
if (TiedStream)
TiedStream->flush();
write_impl(Ptr, Size);
}

// Formatted output.
raw_ostream &raw_ostream::operator<<(const format_object_base &Fmt) {
// If we have more than a few bytes left in our output buffer, try
Expand Down Expand Up @@ -870,8 +876,9 @@ raw_fd_ostream &llvm::outs() {
}

raw_fd_ostream &llvm::errs() {
// Set standard error to be unbuffered by default.
// Set standard error to be unbuffered and tied to outs() by default.
static raw_fd_ostream S(STDERR_FILENO, false, true);
S.tie(&outs());
return S;
}

Expand Down
78 changes: 78 additions & 0 deletions llvm/unittests/Support/raw_ostream_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,4 +376,82 @@ TEST(raw_fd_ostreamTest, multiple_raw_fd_ostream_to_stdout) {
{ raw_fd_ostream("-", EC, sys::fs::OpenFlags::OF_None); }
{ raw_fd_ostream("-", EC, sys::fs::OpenFlags::OF_None); }
}

TEST(raw_ostreamTest, flush_tied_to_stream_on_write) {
std::string TiedToBuffer;
raw_string_ostream TiedTo(TiedToBuffer);
TiedTo.SetBuffered();
TiedTo << "a";

std::string Buffer;
raw_string_ostream TiedStream(Buffer);
TiedStream.tie(&TiedTo);
// Sanity check that the stream hasn't already been flushed.
EXPECT_EQ("", TiedToBuffer);

// Empty string doesn't cause a flush of TiedTo.
TiedStream << "";
EXPECT_EQ("", TiedToBuffer);

// Non-empty strings trigger flush of TiedTo.
TiedStream << "abc";
EXPECT_EQ("a", TiedToBuffer);

// Single char write flushes TiedTo.
TiedTo << "c";
TiedStream << 'd';
EXPECT_EQ("ac", TiedToBuffer);

// Write to buffered stream without flush does not flush TiedTo.
TiedStream.SetBuffered();
TiedStream.SetBufferSize(2);
TiedTo << "e";
TiedStream << "f";
EXPECT_EQ("ac", TiedToBuffer);

// Explicit flush of buffered stream flushes TiedTo.
TiedStream.flush();
EXPECT_EQ("ace", TiedToBuffer);

// Explicit flush of buffered stream with empty buffer does not flush TiedTo.
TiedTo << "g";
TiedStream.flush();
EXPECT_EQ("ace", TiedToBuffer);

// Write of data to empty buffer that is greater than buffer size flushes
// TiedTo.
TiedStream << "hijklm";
EXPECT_EQ("aceg", TiedToBuffer);

// Write of data that overflows buffer size also flushes TiedTo.
TiedStream.flush();
TiedStream << "n";
TiedTo << "o";
TiedStream << "pq";
EXPECT_EQ("acego", TiedToBuffer);

// Streams can be tied to each other safely.
TiedStream.flush();
Buffer = "";
TiedTo.tie(&TiedStream);
TiedTo.SetBufferSize(2);
TiedStream << "r";
TiedTo << "s";
EXPECT_EQ("", Buffer);
EXPECT_EQ("acego", TiedToBuffer);
TiedTo << "tuv";
EXPECT_EQ("r", Buffer);
TiedStream << "wxy";
EXPECT_EQ("acegostuv", TiedToBuffer);
// The x remains in the buffer, since it was written after the flush of
// TiedTo.
EXPECT_EQ("rwx", Buffer);

// Calling tie with nullptr unties stream.
TiedStream.SetUnbuffered();
TiedStream.tie(nullptr);
TiedTo << "y";
TiedStream << "0";
EXPECT_EQ("acegostuv", TiedToBuffer);
}
}

0 comments on commit 1ce8319

Please sign in to comment.