[llvm] Add thread-local overrides for `llvm::errs()` and `llvm::outs()`. (PR #90374)

Chandler Carruth via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 30 21:53:42 PDT 2024


https://github.com/chandlerc updated https://github.com/llvm/llvm-project/pull/90374

>From 5e528fbffd6b87bf21e7e3a18bd6645d46d54270 Mon Sep 17 00:00:00 2001
From: Chandler Carruth <chandlerc at gmail.com>
Date: Sun, 28 Apr 2024 04:54:07 +0000
Subject: [PATCH 1/4] Add thread-local overrides for `llvm::errs()` and
 `llvm::outs()`.

This allows library users of LLVM to intercept these streams and control
where they write. While some components of LLVM already accept stream
parameters to control this, not all do and so it seems useful to add
this as a fallback. The motivating case is the verbose output in Clang's
Driver library.
---
 llvm/include/llvm/Support/raw_ostream.h       | 24 ++++++++++++++++
 llvm/lib/Support/raw_ostream.cpp              | 23 +++++++++++++++
 llvm/unittests/Support/raw_fd_stream_test.cpp | 28 +++++++++++++++++++
 3 files changed, 75 insertions(+)

diff --git a/llvm/include/llvm/Support/raw_ostream.h b/llvm/include/llvm/Support/raw_ostream.h
index 696290a5d99cf5..f32400d9a1e0c5 100644
--- a/llvm/include/llvm/Support/raw_ostream.h
+++ b/llvm/include/llvm/Support/raw_ostream.h
@@ -618,6 +618,30 @@ raw_fd_ostream &errs();
 /// This returns a reference to a raw_ostream which simply discards output.
 raw_ostream &nulls();
 
+/// Scoped thread-local override of `outs` and `errs`.
+///
+/// This can be used by libraries using LLVM that want to control where output
+/// is sent. While some systems explicitly accept a stream in their API, others
+/// directly use these and so we provide a scoped override as a fallback.
+///
+/// Note that these overrides rely on thread-local override, and so much be
+/// carefully instantiated on each thread where the override should be applied.
+class ScopedOutsAndErrsOverride {
+public:
+  // Install overrides for `outs` and `errs`. If the new stream is a null
+  // pointer it will cause that stream to use the system one as if no override
+  // is in place.
+  ScopedOutsAndErrsOverride(raw_fd_ostream *NewOuts, raw_fd_ostream *NewErrs);
+
+  // Restore any previous overrides when destroyed so that these overrides can
+  // be nested.
+  ~ScopedOutsAndErrsOverride();
+
+private:
+  raw_fd_ostream *PrevOuts;
+  raw_fd_ostream *PrevErrs;
+};
+
 //===----------------------------------------------------------------------===//
 // File Streams
 //===----------------------------------------------------------------------===//
diff --git a/llvm/lib/Support/raw_ostream.cpp b/llvm/lib/Support/raw_ostream.cpp
index 8cb7b5ac68ea7e..19aae8f55bef8c 100644
--- a/llvm/lib/Support/raw_ostream.cpp
+++ b/llvm/lib/Support/raw_ostream.cpp
@@ -893,7 +893,14 @@ void raw_fd_ostream::anchor() {}
 //  outs(), errs(), nulls()
 //===----------------------------------------------------------------------===//
 
+static thread_local raw_fd_ostream *OutsOverride = nullptr;
+static thread_local raw_fd_ostream *ErrsOverride = nullptr;
+
 raw_fd_ostream &llvm::outs() {
+  if (auto *TLSOuts = OutsOverride; TLSOuts != nullptr) {
+    return *TLSOuts;
+  }
+
   // Set buffer settings to model stdout behavior.
   std::error_code EC;
 #ifdef __MVS__
@@ -906,6 +913,10 @@ raw_fd_ostream &llvm::outs() {
 }
 
 raw_fd_ostream &llvm::errs() {
+  if (auto *TLSErrs = OutsOverride; TLSErrs != nullptr) {
+    return *TLSErrs;
+  }
+
   // Set standard error to be unbuffered and tied to outs() by default.
 #ifdef __MVS__
   std::error_code EC = enableAutoConversion(STDERR_FILENO);
@@ -921,6 +932,18 @@ raw_ostream &llvm::nulls() {
   return S;
 }
 
+ScopedOutsAndErrsOverride::ScopedOutsAndErrsOverride(raw_fd_ostream *NewOuts,
+                                                     raw_fd_ostream *NewErrs)
+    : PrevOuts(OutsOverride), PrevErrs(ErrsOverride) {
+  OutsOverride = NewOuts;
+  ErrsOverride = NewErrs;
+}
+
+ScopedOutsAndErrsOverride::~ScopedOutsAndErrsOverride() {
+  OutsOverride = PrevOuts;
+  ErrsOverride = PrevErrs;
+}
+
 //===----------------------------------------------------------------------===//
 // File Streams
 //===----------------------------------------------------------------------===//
diff --git a/llvm/unittests/Support/raw_fd_stream_test.cpp b/llvm/unittests/Support/raw_fd_stream_test.cpp
index 00d834da32101c..b7784f3b625ad4 100644
--- a/llvm/unittests/Support/raw_fd_stream_test.cpp
+++ b/llvm/unittests/Support/raw_fd_stream_test.cpp
@@ -64,4 +64,32 @@ TEST(raw_fd_streamTest, DynCast) {
   }
 }
 
+TEST(raw_fd_streamTest, OverrideOutsAndErrs) {
+  SmallString<64> Path;
+  int FD;
+  ASSERT_FALSE(sys::fs::createTemporaryFile("foo", "bar", FD, Path));
+  FileRemover Cleanup(Path);
+  std::error_code EC;
+  raw_fd_stream OS(Path, EC);
+  ASSERT_TRUE(!EC);
+
+  ScopedOutsAndErrsOverride Overrides(&OS, &OS);
+
+  // First test `outs`.
+  llvm::outs() << "outs";
+  llvm::outs().flush();
+  char Buffer[4];
+  OS.seek(0);
+  OS.read(Buffer, sizeof(Buffer));
+  EXPECT_EQ("outs", StringRef(Buffer, sizeof(Buffer)));
+
+  // Now test `errs`.
+  OS.seek(0);
+  llvm::errs() << "errs";
+  llvm::errs().flush();
+  OS.seek(0);
+  OS.read(Buffer, sizeof(Buffer));
+  EXPECT_EQ("errs", StringRef(Buffer, sizeof(Buffer)));
+}
+
 } // namespace

>From 3ddc1dc8bb4a60b68401e889bfb9b8d448603ce9 Mon Sep 17 00:00:00 2001
From: Chandler Carruth <chandlerc at gmail.com>
Date: Tue, 30 Apr 2024 21:16:39 -0600
Subject: [PATCH 2/4] Add crash recovery and start better testing

---
 llvm/include/llvm/Support/raw_ostream.h       |  5 +++++
 llvm/lib/Support/CrashRecoveryContext.cpp     | 10 +++++++++-
 llvm/lib/Support/raw_ostream.cpp              | 11 ++++++----
 llvm/unittests/Support/raw_fd_stream_test.cpp | 20 +++++++++++++++++++
 4 files changed, 41 insertions(+), 5 deletions(-)

diff --git a/llvm/include/llvm/Support/raw_ostream.h b/llvm/include/llvm/Support/raw_ostream.h
index f32400d9a1e0c5..053298992c2fd9 100644
--- a/llvm/include/llvm/Support/raw_ostream.h
+++ b/llvm/include/llvm/Support/raw_ostream.h
@@ -637,6 +637,11 @@ class ScopedOutsAndErrsOverride {
   // be nested.
   ~ScopedOutsAndErrsOverride();
 
+  // Extract any current overrides. This is mostly useful for propagating one
+  // thread's overrides to another thread. The `outs` override is first and the
+  // `errs` second.
+  static std::pair<raw_fd_ostream *, raw_fd_ostream *> getActiveOverrides();
+
 private:
   raw_fd_ostream *PrevOuts;
   raw_fd_ostream *PrevErrs;
diff --git a/llvm/lib/Support/CrashRecoveryContext.cpp b/llvm/lib/Support/CrashRecoveryContext.cpp
index f53aea177d6127..e478845d409084 100644
--- a/llvm/lib/Support/CrashRecoveryContext.cpp
+++ b/llvm/lib/Support/CrashRecoveryContext.cpp
@@ -11,6 +11,7 @@
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/ExitCodes.h"
 #include "llvm/Support/Signals.h"
+#include "llvm/Support/raw_ostream.h"
 #include "llvm/Support/thread.h"
 #include <cassert>
 #include <mutex>
@@ -495,6 +496,8 @@ namespace {
 struct RunSafelyOnThreadInfo {
   function_ref<void()> Fn;
   CrashRecoveryContext *CRC;
+  raw_fd_ostream *OutsOverride;
+  raw_fd_ostream *ErrsOverride;
   bool UseBackgroundPriority;
   bool Result;
 };
@@ -507,12 +510,17 @@ static void RunSafelyOnThread_Dispatch(void *UserData) {
   if (Info->UseBackgroundPriority)
     setThreadBackgroundPriority();
 
+  ScopedOutsAndErrsOverride StreamOverrides(Info->OutsOverride,
+                                            Info->ErrsOverride);
+
   Info->Result = Info->CRC->RunSafely(Info->Fn);
 }
 bool CrashRecoveryContext::RunSafelyOnThread(function_ref<void()> Fn,
                                              unsigned RequestedStackSize) {
   bool UseBackgroundPriority = hasThreadBackgroundPriority();
-  RunSafelyOnThreadInfo Info = { Fn, this, UseBackgroundPriority, false };
+  auto [OutsOverride, ErrsOverride] = ScopedOutsAndErrsOverride::getActiveOverrides();
+  RunSafelyOnThreadInfo Info = {
+      Fn, this, OutsOverride, ErrsOverride, UseBackgroundPriority, false};
   llvm::thread Thread(RequestedStackSize == 0
                           ? std::nullopt
                           : std::optional<unsigned>(RequestedStackSize),
diff --git a/llvm/lib/Support/raw_ostream.cpp b/llvm/lib/Support/raw_ostream.cpp
index 19aae8f55bef8c..0d45b6a294cb6c 100644
--- a/llvm/lib/Support/raw_ostream.cpp
+++ b/llvm/lib/Support/raw_ostream.cpp
@@ -897,9 +897,8 @@ static thread_local raw_fd_ostream *OutsOverride = nullptr;
 static thread_local raw_fd_ostream *ErrsOverride = nullptr;
 
 raw_fd_ostream &llvm::outs() {
-  if (auto *TLSOuts = OutsOverride; TLSOuts != nullptr) {
+  if (auto *TLSOuts = OutsOverride; TLSOuts != nullptr)
     return *TLSOuts;
-  }
 
   // Set buffer settings to model stdout behavior.
   std::error_code EC;
@@ -913,9 +912,8 @@ raw_fd_ostream &llvm::outs() {
 }
 
 raw_fd_ostream &llvm::errs() {
-  if (auto *TLSErrs = OutsOverride; TLSErrs != nullptr) {
+  if (auto *TLSErrs = OutsOverride; TLSErrs != nullptr)
     return *TLSErrs;
-  }
 
   // Set standard error to be unbuffered and tied to outs() by default.
 #ifdef __MVS__
@@ -944,6 +942,11 @@ ScopedOutsAndErrsOverride::~ScopedOutsAndErrsOverride() {
   ErrsOverride = PrevErrs;
 }
 
+std::pair<raw_fd_ostream *, raw_fd_ostream *>
+ScopedOutsAndErrsOverride::getActiveOverrides() {
+  return {OutsOverride, ErrsOverride};
+}
+
 //===----------------------------------------------------------------------===//
 // File Streams
 //===----------------------------------------------------------------------===//
diff --git a/llvm/unittests/Support/raw_fd_stream_test.cpp b/llvm/unittests/Support/raw_fd_stream_test.cpp
index b7784f3b625ad4..290c4bbda7f1ce 100644
--- a/llvm/unittests/Support/raw_fd_stream_test.cpp
+++ b/llvm/unittests/Support/raw_fd_stream_test.cpp
@@ -9,6 +9,7 @@
 #include "llvm/ADT/SmallString.h"
 #include "llvm/Config/llvm-config.h"
 #include "llvm/Support/Casting.h"
+#include "llvm/Support/CrashRecoveryContext.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/FileUtilities.h"
 #include "llvm/Support/raw_ostream.h"
@@ -90,6 +91,25 @@ TEST(raw_fd_streamTest, OverrideOutsAndErrs) {
   OS.seek(0);
   OS.read(Buffer, sizeof(Buffer));
   EXPECT_EQ("errs", StringRef(Buffer, sizeof(Buffer)));
+
+  // Now try nesting a new set of redirects.
+  {
+    SmallString<64> Path2;
+    int FD2;
+    ASSERT_FALSE(sys::fs::createTemporaryFile("foo2", "bar2", FD2, Path2));
+    FileRemover Cleanup(Path2);
+    raw_fd_stream OS2(Path, EC);
+    ASSERT_TRUE(!EC);
+
+    llvm::outs() << "te";
+    llvm::outs().flush();
+    llvm::errs() << "st";
+    llvm::errs().flush();
+    char Buffer[4];
+    OS2.seek(0);
+    OS2.read(Buffer, sizeof(Buffer));
+    EXPECT_EQ("test", StringRef(Buffer, sizeof(Buffer)));
+  }
 }
 
 } // namespace

>From 88d6302d7fb67cddb5775b151764fb957c4eea3c Mon Sep 17 00:00:00 2001
From: Chandler Carruth <chandlerc at gmail.com>
Date: Wed, 1 May 2024 04:34:08 +0000
Subject: [PATCH 3/4] finish testing

---
 llvm/unittests/Support/raw_fd_stream_test.cpp | 54 ++++++++++++++++++-
 1 file changed, 53 insertions(+), 1 deletion(-)

diff --git a/llvm/unittests/Support/raw_fd_stream_test.cpp b/llvm/unittests/Support/raw_fd_stream_test.cpp
index 290c4bbda7f1ce..5fe770fe5d3108 100644
--- a/llvm/unittests/Support/raw_fd_stream_test.cpp
+++ b/llvm/unittests/Support/raw_fd_stream_test.cpp
@@ -19,6 +19,13 @@ using namespace llvm;
 
 namespace {
 
+// While these are marked as "internal" APIs, they seem to work and be pretty
+// widely used for their exact documented behavior.
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
 TEST(raw_fd_streamTest, ReadAfterWrite) {
   SmallString<64> Path;
   int FD;
@@ -92,6 +99,9 @@ TEST(raw_fd_streamTest, OverrideOutsAndErrs) {
   OS.read(Buffer, sizeof(Buffer));
   EXPECT_EQ("errs", StringRef(Buffer, sizeof(Buffer)));
 
+  // Seek back before overrides to improve the restore test.
+  OS.seek(0);
+
   // Now try nesting a new set of redirects.
   {
     SmallString<64> Path2;
@@ -101,15 +111,57 @@ TEST(raw_fd_streamTest, OverrideOutsAndErrs) {
     raw_fd_stream OS2(Path, EC);
     ASSERT_TRUE(!EC);
 
+    ScopedOutsAndErrsOverride Overrides(&OS2, &OS2);
+
     llvm::outs() << "te";
     llvm::outs().flush();
     llvm::errs() << "st";
     llvm::errs().flush();
-    char Buffer[4];
     OS2.seek(0);
     OS2.read(Buffer, sizeof(Buffer));
     EXPECT_EQ("test", StringRef(Buffer, sizeof(Buffer)));
   }
+
+  // Nest and un-override to ensure we can reach stdout and stderr again.
+  {
+    ScopedOutsAndErrsOverride Overrides(nullptr, nullptr);
+
+    CaptureStdout();
+    CaptureStderr();
+    llvm::outs() << "llvm";
+    llvm::outs().flush();
+    llvm::errs() << "clang";
+    llvm::errs().flush();
+    std::string OutResult = GetCapturedStdout();
+    std::string ErrResult = GetCapturedStderr();
+
+    EXPECT_EQ("llvm", OutResult);
+    EXPECT_EQ("clang", ErrResult);
+  }
+
+  // Make sure the prior override is restored.
+  llvm::outs() << "sw";
+  llvm::outs().flush();
+  llvm::errs() << "im";
+  llvm::errs().flush();
+  OS.seek(0);
+  OS.read(Buffer, sizeof(Buffer));
+  EXPECT_EQ("swim", StringRef(Buffer, sizeof(Buffer)));
+
+  // Last but not least, make sure our overrides are propagated into the crash
+  // recovery context.
+  OS.seek(0);
+  CrashRecoveryContext CRC;
+
+  ASSERT_TRUE(CRC.RunSafelyOnThread([] {
+    llvm::outs() << "bo";
+    llvm::outs().flush();
+    llvm::errs() << "om";
+    llvm::errs().flush();
+  }));
+  OS.seek(0);
+  OS.read(Buffer, sizeof(Buffer));
+  EXPECT_EQ("boom", StringRef(Buffer, sizeof(Buffer)));
 }
 
 } // namespace

>From c85a9838b0799d178fe3e71193b0eb2f14c12e60 Mon Sep 17 00:00:00 2001
From: Chandler Carruth <chandlerc at gmail.com>
Date: Wed, 1 May 2024 04:43:59 +0000
Subject: [PATCH 4/4] tweak formatting

---
 llvm/lib/Support/CrashRecoveryContext.cpp | 3 ++-
 llvm/lib/Support/raw_ostream.cpp          | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Support/CrashRecoveryContext.cpp b/llvm/lib/Support/CrashRecoveryContext.cpp
index e478845d409084..05b3624e6b2604 100644
--- a/llvm/lib/Support/CrashRecoveryContext.cpp
+++ b/llvm/lib/Support/CrashRecoveryContext.cpp
@@ -518,7 +518,8 @@ static void RunSafelyOnThread_Dispatch(void *UserData) {
 bool CrashRecoveryContext::RunSafelyOnThread(function_ref<void()> Fn,
                                              unsigned RequestedStackSize) {
   bool UseBackgroundPriority = hasThreadBackgroundPriority();
-  auto [OutsOverride, ErrsOverride] = ScopedOutsAndErrsOverride::getActiveOverrides();
+  auto [OutsOverride, ErrsOverride] =
+      ScopedOutsAndErrsOverride::getActiveOverrides();
   RunSafelyOnThreadInfo Info = {
       Fn, this, OutsOverride, ErrsOverride, UseBackgroundPriority, false};
   llvm::thread Thread(RequestedStackSize == 0
diff --git a/llvm/lib/Support/raw_ostream.cpp b/llvm/lib/Support/raw_ostream.cpp
index 0d45b6a294cb6c..e83f6982d71abb 100644
--- a/llvm/lib/Support/raw_ostream.cpp
+++ b/llvm/lib/Support/raw_ostream.cpp
@@ -915,11 +915,11 @@ raw_fd_ostream &llvm::errs() {
   if (auto *TLSErrs = OutsOverride; TLSErrs != nullptr)
     return *TLSErrs;
 
-  // Set standard error to be unbuffered and tied to outs() by default.
 #ifdef __MVS__
   std::error_code EC = enableAutoConversion(STDERR_FILENO);
   assert(!EC);
 #endif
+  // Set standard error to be unbuffered and tied to stderr by default.
   static raw_fd_ostream S(STDERR_FILENO, false, true);
   return S;
 }



More information about the llvm-commits mailing list