[llvm] [raw_ostream, Format] Shrink format() callback thunks (PR #206374)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Sun Jun 28 14:22:31 PDT 2026


https://github.com/MaskRay created https://github.com/llvm/llvm-project/pull/206374

Follow-up to #206319: `operator<<(raw_ostream &OS, format_object<Ts...>
Fmt)` calls `snprintf` and then normalizes the result. Move the
renormalization into `raw_ostream::operator<<`, shrinking each
per-signature callback thunk to a bare snprintf tail call.

`snprintf` is C99-conformant everywhere (glibc<2.1, which was
non-conformant, is unsupported; On Windows, the minimum requirement,
Visual Studio 2019 16.8, provides `snprintf`). Drop the `_snprintf`
path, size the retry buffer to the exact N + 1 it reports, and bail on a
negative (genuine error) result instead of doubling forever.


>From 94eb678eeb03a8807ce484c0badaae78d99cf727 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sun, 28 Jun 2026 13:00:54 -0700
Subject: [PATCH] [raw_ostream,Format] Shrink format() callback thunks

Follow-up to #206319: `operator<<(raw_ostream &OS, format_object<Ts...>
Fmt)` calls `snprintf` and then normalizes the result. Move the
renormalization into `raw_ostream::operator<<`, shrinking each
per-signature callback thunk to a bare snprintf tail call.

`snprintf` is C99-conformant everywhere (glibc<2.1, which was
non-conformant, is unsupported; On Windows, the minimum requirement,
Visual Studio 2019 16.8, provides `snprintf`). Drop the `_snprintf`
path, size the retry buffer to the exact N + 1 it reports, and bail on a
negative (genuine error) result instead of doubling forever.
---
 llvm/include/llvm/Support/Format.h            | 25 +---------
 llvm/include/llvm/Support/raw_ostream.h       |  2 +-
 llvm/lib/Support/raw_ostream.cpp              | 49 ++++++++-----------
 .../X86/MCTargetDesc/X86MachObjectWriter.cpp  |  2 +-
 llvm/tools/llvm-nm/llvm-nm.cpp                |  9 ++--
 5 files changed, 29 insertions(+), 58 deletions(-)

diff --git a/llvm/include/llvm/Support/Format.h b/llvm/include/llvm/Support/Format.h
index fff1bc5464c57..9f8bd7eddaba5 100644
--- a/llvm/include/llvm/Support/Format.h
+++ b/llvm/include/llvm/Support/Format.h
@@ -59,11 +59,7 @@ template <typename... Ts> class format_object {
   template <std::size_t... Is>
   int snprint_tuple(char *Buffer, unsigned BufferSize,
                     std::index_sequence<Is...>) const {
-#ifdef _MSC_VER
-    return _snprintf(Buffer, BufferSize, Fmt, std::get<Is>(Vals)...);
-#else
     return snprintf(Buffer, BufferSize, Fmt, std::get<Is>(Vals)...);
-#endif
   }
 
 public:
@@ -76,31 +72,12 @@ template <typename... Ts> class format_object {
   int snprint(char *Buffer, unsigned BufferSize) const {
     return snprint_tuple(Buffer, BufferSize, std::index_sequence_for<Ts...>());
   }
-
-  unsigned print(char *Buffer, unsigned BufferSize) const {
-    assert(BufferSize && "Invalid buffer size!");
-
-    // Print the string, leaving room for the terminating null.
-    int N = snprint(Buffer, BufferSize);
-
-    // VC++ and old GlibC return negative on overflow, just double the size.
-    if (N < 0)
-      return BufferSize * 2;
-
-    // Other implementations yield number of bytes needed, not including the
-    // final '\0'.
-    if (unsigned(N) >= BufferSize)
-      return N + 1;
-
-    // Otherwise N is the length of output (not including the final '\0').
-    return N;
-  }
 };
 
 template <typename... Ts>
 raw_ostream &operator<<(raw_ostream &OS, format_object<Ts...> Fmt) {
   OS <<
-      [&Fmt](char *Buf, size_t Size) -> size_t { return Fmt.print(Buf, Size); };
+      [&Fmt](char *Buf, size_t Size) -> int { return Fmt.snprint(Buf, Size); };
   return OS;
 }
 
diff --git a/llvm/include/llvm/Support/raw_ostream.h b/llvm/include/llvm/Support/raw_ostream.h
index afd45465bcd1c..23acf743606b5 100644
--- a/llvm/include/llvm/Support/raw_ostream.h
+++ b/llvm/include/llvm/Support/raw_ostream.h
@@ -306,7 +306,7 @@ class LLVM_ABI raw_ostream {
   raw_ostream &write(const char *Ptr, size_t Size);
 
   // Formatted output, see the format() function in Support/Format.h.
-  raw_ostream &operator<<(function_ref<size_t(char *, size_t)> Print);
+  raw_ostream &operator<<(function_ref<int(char *, size_t)> Snprint);
 
   // Formatted output, see the leftJustify() function in Support/Format.h.
   raw_ostream &operator<<(const FormattedString &);
diff --git a/llvm/lib/Support/raw_ostream.cpp b/llvm/lib/Support/raw_ostream.cpp
index 4c99854dd9bd9..eebc4db5ac878 100644
--- a/llvm/lib/Support/raw_ostream.cpp
+++ b/llvm/lib/Support/raw_ostream.cpp
@@ -294,45 +294,38 @@ void raw_ostream::copy_to_buffer(const char *Ptr, size_t Size) {
   OutBufCur += Size;
 }
 
-// Formatted output.
+// Formatted output. Snprint returns the snprintf-style length the output
+// needs, excluding the '\0'. A negative value is a genuine error, which a
+// larger buffer can't fix, so give up and print nothing.
 raw_ostream &
-raw_ostream::operator<<(function_ref<size_t(char *, size_t)> Print) {
+raw_ostream::operator<<(function_ref<int(char *, size_t)> Snprint) {
   // If we have more than a few bytes left in our output buffer, try
   // formatting directly onto its end.
-  size_t NextBufferSize = 127;
+  size_t Size = 128;
   size_t BufferBytesLeft = OutBufEnd - OutBufCur;
   if (BufferBytesLeft > 3) {
-    size_t BytesUsed = Print(OutBufCur, BufferBytesLeft);
-
+    int N = Snprint(OutBufCur, BufferBytesLeft);
+    if (N < 0)
+      return *this;
     // Common case is that we have plenty of space.
-    if (BytesUsed <= BufferBytesLeft) {
-      OutBufCur += BytesUsed;
+    if (size_t(N) < BufferBytesLeft) {
+      OutBufCur += N;
       return *this;
     }
-
-    // Otherwise, we overflowed and the return value tells us the size to try
-    // again with.
-    NextBufferSize = BytesUsed;
+    // N excludes the '\0', so N + 1 bytes are exactly enough.
+    Size = size_t(N) + 1;
   }
 
-  // If we got here, we didn't have enough space in the output buffer for the
-  // string.  Try printing into a SmallVector that is resized to have enough
-  // space.  Iterate until we win.
+  // Otherwise format into a SmallVector resized to fit.
   SmallVector<char, 128> V;
-
-  while (true) {
-    V.resize(NextBufferSize);
-
-    // Try formatting into the SmallVector.
-    size_t BytesUsed = Print(V.data(), NextBufferSize);
-
-    // If BytesUsed fit into the vector, we win.
-    if (BytesUsed <= NextBufferSize)
-      return write(V.data(), BytesUsed);
-
-    // Otherwise, try again with a new size.
-    assert(BytesUsed > NextBufferSize && "Didn't grow buffer!?");
-    NextBufferSize = BytesUsed;
+  for (;;) {
+    V.resize(Size);
+    int N = Snprint(V.data(), Size);
+    if (N < 0)
+      return *this;
+    if (size_t(N) < Size)
+      return write(V.data(), N);
+    Size = size_t(N) + 1;
   }
 }
 
diff --git a/llvm/lib/Target/X86/MCTargetDesc/X86MachObjectWriter.cpp b/llvm/lib/Target/X86/MCTargetDesc/X86MachObjectWriter.cpp
index 25fcf81587399..38b104fd9e001 100644
--- a/llvm/lib/Target/X86/MCTargetDesc/X86MachObjectWriter.cpp
+++ b/llvm/lib/Target/X86/MCTargetDesc/X86MachObjectWriter.cpp
@@ -394,7 +394,7 @@ bool X86MachObjectWriter::recordScatteredRelocation(MachObjectWriter *Writer,
     // we're hosed. It's an unfortunate limitation of the MachO format.
     if (FixupOffset > 0xffffff) {
       char Buffer[32];
-      format("0x%x", FixupOffset).print(Buffer, sizeof(Buffer));
+      format("0x%x", FixupOffset).snprint(Buffer, sizeof(Buffer));
       reportError(Fixup.getLoc(), Twine("Section too large, can't encode "
                                         "r_address (") +
                                       Buffer +
diff --git a/llvm/tools/llvm-nm/llvm-nm.cpp b/llvm/tools/llvm-nm/llvm-nm.cpp
index a33c54914ee9d..4e3472154063a 100644
--- a/llvm/tools/llvm-nm/llvm-nm.cpp
+++ b/llvm/tools/llvm-nm/llvm-nm.cpp
@@ -801,8 +801,9 @@ static void printSymbolList(SymbolicFile &Obj,
     if (OutputFormat == sysv || !symbolIsDefined(S)) {
       if (OutputFormat == posix) {
         format(printFormat, S.Address)
-            .print(SymbolAddrStr, sizeof(SymbolAddrStr));
-        format(printFormat, S.Size).print(SymbolSizeStr, sizeof(SymbolSizeStr));
+            .snprint(SymbolAddrStr, sizeof(SymbolAddrStr));
+        format(printFormat, S.Size)
+            .snprint(SymbolSizeStr, sizeof(SymbolSizeStr));
       } else {
         strcpy(SymbolAddrStr, printBlanks);
         strcpy(SymbolSizeStr, printBlanks);
@@ -817,8 +818,8 @@ static void printSymbolList(SymbolicFile &Obj,
         strcpy(SymbolAddrStr, printBlanks);
       else
         format(printFormat, S.Address)
-            .print(SymbolAddrStr, sizeof(SymbolAddrStr));
-      format(printFormat, S.Size).print(SymbolSizeStr, sizeof(SymbolSizeStr));
+            .snprint(SymbolAddrStr, sizeof(SymbolAddrStr));
+      format(printFormat, S.Size).snprint(SymbolSizeStr, sizeof(SymbolSizeStr));
     }
 
     // If OutputFormat is darwin or we are printing Mach-O symbols in hex and



More information about the llvm-commits mailing list