[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