[clang] [llvm] [Support] Make format_object non-virtual (PR #206319)
Alexis Engelke via cfe-commits
cfe-commits at lists.llvm.org
Sun Jun 28 01:45:59 PDT 2026
https://github.com/aengelke created https://github.com/llvm/llvm-project/pull/206319
Currently, format_object creates a 32B vtable for every instantiation.
This is costs space and dynamic relocations. Make format_object
non-virtual and adapt the two printing users to use a function_ref
instead.
>From 6cdebc210ce08f67eb7cafa24b2e63c14df77080 Mon Sep 17 00:00:00 2001
From: Alexis Engelke <engelke at in.tum.de>
Date: Sun, 28 Jun 2026 08:45:20 +0000
Subject: [PATCH] [spr] initial version
Created using spr 1.3.8-wip
---
clang/tools/libclang/CIndex.cpp | 5 --
clang/tools/libclang/CLog.h | 8 ++-
llvm/include/llvm/Support/Format.h | 74 +++++++++++--------------
llvm/include/llvm/Support/raw_ostream.h | 3 +-
llvm/lib/Support/raw_ostream.cpp | 15 ++---
5 files changed, 44 insertions(+), 61 deletions(-)
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index ac2fad38a1348..9bc2f66d7f75f 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -10179,11 +10179,6 @@ Logger &cxindex::Logger::operator<<(CXString Str) {
return *this;
}
-Logger &cxindex::Logger::operator<<(const llvm::format_object_base &Fmt) {
- LogOS << Fmt;
- return *this;
-}
-
static llvm::ManagedStatic<std::mutex> LoggingMutex;
cxindex::Logger::~Logger() {
diff --git a/clang/tools/libclang/CLog.h b/clang/tools/libclang/CLog.h
index 6ce43a01ee8f2..a880fb03b4e87 100644
--- a/clang/tools/libclang/CLog.h
+++ b/clang/tools/libclang/CLog.h
@@ -16,6 +16,7 @@
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Compiler.h"
+#include "llvm/Support/Format.h"
#include "llvm/Support/raw_ostream.h"
#include <string>
@@ -82,7 +83,12 @@ class Logger : public RefCountedBase<Logger> {
Logger &operator<<(char C) { LogOS << C; return *this; }
Logger &operator<<(unsigned char C) { LogOS << C; return *this; }
Logger &operator<<(signed char C) { LogOS << C; return *this; }
- Logger &operator<<(const llvm::format_object_base &Fmt);
+
+ template <typename... Ts>
+ Logger &operator<<(const llvm::format_object<Ts...> &Fmt) {
+ LogOS << Fmt;
+ return *this;
+ }
};
}
diff --git a/llvm/include/llvm/Support/Format.h b/llvm/include/llvm/Support/Format.h
index b549341a40e7f..fff1bc5464c57 100644
--- a/llvm/include/llvm/Support/Format.h
+++ b/llvm/include/llvm/Support/Format.h
@@ -27,6 +27,7 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/DataTypes.h"
+#include "llvm/Support/raw_ostream.h"
#include <cassert>
#include <cstdio>
#include <optional>
@@ -35,44 +36,6 @@
namespace llvm {
-/// This is a helper class used for handling formatted output. It is the
-/// abstract base class of a templated derived class.
-class LLVM_ABI format_object_base {
-protected:
- const char *Fmt;
- ~format_object_base() = default; // Disallow polymorphic deletion.
- format_object_base(const format_object_base &) = default;
- virtual void home(); // Out of line virtual method.
-
- /// Call snprintf() for this object, on the given buffer and size.
- virtual int snprint(char *Buffer, unsigned BufferSize) const = 0;
-
-public:
- format_object_base(const char *fmt) : Fmt(fmt) {}
-
- /// Format the object into the specified buffer. On success, this returns
- /// the length of the formatted string. If the buffer is too small, this
- /// returns a length to retry with, which will be larger than BufferSize.
- 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;
- }
-};
-
/// These are templated helper classes used by the format function that
/// capture the object to be formatted and the format string. When actually
/// printed, this synthesizes the string into a temporary buffer provided and
@@ -89,8 +52,8 @@ template <typename T>
using decay_if_c_char_array_t = typename decay_if_c_char_array<T>::type;
} // namespace detail
-template <typename... Ts>
-class format_object final : public format_object_base {
+template <typename... Ts> class format_object {
+ const char *Fmt;
std::tuple<detail::decay_if_c_char_array_t<Ts>...> Vals;
template <std::size_t... Is>
@@ -104,18 +67,43 @@ class format_object final : public format_object_base {
}
public:
- format_object(const char *fmt, const Ts &... vals)
- : format_object_base(fmt), Vals(vals...) {
+ format_object(const char *fmt, const Ts &...vals) : Fmt(fmt), Vals(vals...) {
static_assert(
(std::is_scalar_v<detail::decay_if_c_char_array_t<Ts>> && ...),
"format can't be used with non fundamental / non pointer type");
}
- int snprint(char *Buffer, unsigned BufferSize) const override {
+ 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); };
+ return OS;
+}
+
/// These are helper functions used to produce formatted output. They use
/// template type deduction to construct the appropriate instance of the
/// format_object class to simplify their construction.
diff --git a/llvm/include/llvm/Support/raw_ostream.h b/llvm/include/llvm/Support/raw_ostream.h
index 1e66052c849b7..afd45465bcd1c 100644
--- a/llvm/include/llvm/Support/raw_ostream.h
+++ b/llvm/include/llvm/Support/raw_ostream.h
@@ -13,6 +13,7 @@
#ifndef LLVM_SUPPORT_RAW_OSTREAM_H
#define LLVM_SUPPORT_RAW_OSTREAM_H
+#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Compiler.h"
@@ -305,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<<(const format_object_base &Fmt);
+ raw_ostream &operator<<(function_ref<size_t(char *, size_t)> Print);
// 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 9b590dbc2ae27..4c99854dd9bd9 100644
--- a/llvm/lib/Support/raw_ostream.cpp
+++ b/llvm/lib/Support/raw_ostream.cpp
@@ -295,13 +295,14 @@ void raw_ostream::copy_to_buffer(const char *Ptr, size_t Size) {
}
// Formatted output.
-raw_ostream &raw_ostream::operator<<(const format_object_base &Fmt) {
+raw_ostream &
+raw_ostream::operator<<(function_ref<size_t(char *, size_t)> Print) {
// 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 BufferBytesLeft = OutBufEnd - OutBufCur;
if (BufferBytesLeft > 3) {
- size_t BytesUsed = Fmt.print(OutBufCur, BufferBytesLeft);
+ size_t BytesUsed = Print(OutBufCur, BufferBytesLeft);
// Common case is that we have plenty of space.
if (BytesUsed <= BufferBytesLeft) {
@@ -323,7 +324,7 @@ raw_ostream &raw_ostream::operator<<(const format_object_base &Fmt) {
V.resize(NextBufferSize);
// Try formatting into the SmallVector.
- size_t BytesUsed = Fmt.print(V.data(), NextBufferSize);
+ size_t BytesUsed = Print(V.data(), NextBufferSize);
// If BytesUsed fit into the vector, we win.
if (BytesUsed <= NextBufferSize)
@@ -540,14 +541,6 @@ raw_ostream &raw_ostream::reverseColor() {
void raw_ostream::anchor() {}
-//===----------------------------------------------------------------------===//
-// Formatted Output
-//===----------------------------------------------------------------------===//
-
-// Out of line virtual method.
-void format_object_base::home() {
-}
-
//===----------------------------------------------------------------------===//
// raw_fd_ostream
//===----------------------------------------------------------------------===//
More information about the cfe-commits
mailing list