[libc-commits] [libc] [libc] Template the writing mode for the writer class (PR #111559)

Joseph Huber via libc-commits libc-commits at lists.llvm.org
Tue Mar 11 11:30:44 PDT 2025


https://github.com/jhuber6 updated https://github.com/llvm/llvm-project/pull/111559

>From aa832ddf4bf13f6bdbe3699fd65368054ef4df33 Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Tue, 8 Oct 2024 12:04:13 -0500
Subject: [PATCH] [libc] Template the writing mode for the writer class

Summary:
Currently we dispatch the writing mode off of a runtime enum passed in
by the constructor. This causes very unfortunate codegen for the GPU
targets where we get worst-case codegen because of the unused function
pointer for `sprintf`. Instead, this patch moves all of this to a
template so it can be masked out. This results in no dynamic stack and
uses 60 VGPRs instead of 117. It also compiles about 5x as fast.

WIP

Update
---
 libc/config/config.json                       |   4 +
 libc/config/gpu/amdgpu/config.json            |   3 +
 libc/config/gpu/nvptx/config.json             |   3 +
 libc/docs/configure.rst                       |   1 +
 libc/src/stdio/printf_core/CMakeLists.txt     |  15 +-
 libc/src/stdio/printf_core/char_converter.h   |   4 +-
 libc/src/stdio/printf_core/converter.cpp      | 105 --------------
 libc/src/stdio/printf_core/converter.h        |  85 ++++++++++-
 libc/src/stdio/printf_core/fixed_converter.h  |   4 +-
 .../stdio/printf_core/float_dec_converter.h   |  60 +++++---
 .../stdio/printf_core/float_hex_converter.h   |   3 +-
 .../printf_core/float_inf_nan_converter.h     |   4 +-
 libc/src/stdio/printf_core/int_converter.h    |   4 +-
 libc/src/stdio/printf_core/printf_main.cpp    |  43 ------
 libc/src/stdio/printf_core/printf_main.h      |  24 +++-
 libc/src/stdio/printf_core/ptr_converter.h    |   4 +-
 .../stdio/printf_core/strerror_converter.h    |   4 +-
 libc/src/stdio/printf_core/string_converter.h |   4 +-
 .../stdio/printf_core/vasprintf_internal.h    |  11 +-
 .../src/stdio/printf_core/vfprintf_internal.h |   6 +-
 .../stdio/printf_core/write_int_converter.h   |   3 +-
 libc/src/stdio/printf_core/writer.cpp         |  46 ------
 libc/src/stdio/printf_core/writer.h           | 132 ++++++++++++------
 libc/src/stdio/snprintf.cpp                   |   6 +-
 libc/src/stdio/sprintf.cpp                    |   6 +-
 libc/src/stdio/vsnprintf.cpp                  |   6 +-
 libc/src/stdio/vsprintf.cpp                   |   6 +-
 libc/src/stdlib/str_from_util.h               |   4 +-
 libc/src/stdlib/strfromd.cpp                  |   6 +-
 libc/src/stdlib/strfromf.cpp                  |   6 +-
 libc/src/stdlib/strfroml.cpp                  |   6 +-
 libc/src/time/strftime.cpp                    |   6 +-
 libc/src/time/strftime_core/CMakeLists.txt    |   8 +-
 .../time/strftime_core/composite_converter.h  |  21 ++-
 libc/src/time/strftime_core/converter.cpp     |  96 -------------
 libc/src/time/strftime_core/converter.h       |  81 ++++++++++-
 libc/src/time/strftime_core/num_converter.h   |   6 +-
 libc/src/time/strftime_core/str_converter.h   |   3 +-
 libc/src/time/strftime_core/strftime_main.cpp |  40 ------
 libc/src/time/strftime_core/strftime_main.h   |  24 +++-
 libc/src/time/strftime_l.cpp                  |   6 +-
 .../src/stdio/printf_core/converter_test.cpp  |  12 +-
 .../src/stdio/printf_core/writer_test.cpp     |  80 ++++++-----
 libc/utils/gpu/server/CMakeLists.txt          |   6 +-
 libc/utils/gpu/server/rpc_server.cpp          |  13 +-
 45 files changed, 510 insertions(+), 510 deletions(-)
 delete mode 100644 libc/src/stdio/printf_core/converter.cpp
 delete mode 100644 libc/src/stdio/printf_core/printf_main.cpp
 delete mode 100644 libc/src/stdio/printf_core/writer.cpp
 delete mode 100644 libc/src/time/strftime_core/converter.cpp
 delete mode 100644 libc/src/time/strftime_core/strftime_main.cpp

diff --git a/libc/config/config.json b/libc/config/config.json
index c38d424229218..d738aade74427 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -41,6 +41,10 @@
     "LIBC_CONF_PRINTF_DISABLE_STRERROR": {
       "value": false,
       "doc": "Disable handling of %m to print strerror in printf and friends."
+    },
+    "LIBC_CONF_PRINTF_RUNTIME_DISPATCH": {
+      "value": true,
+      "doc": "Use dynamic dispatch for the output mechanism to reduce code size."
     }
   },
   "scanf": {
diff --git a/libc/config/gpu/amdgpu/config.json b/libc/config/gpu/amdgpu/config.json
index d99f48ecbede1..30ae10e2cfd61 100644
--- a/libc/config/gpu/amdgpu/config.json
+++ b/libc/config/gpu/amdgpu/config.json
@@ -19,6 +19,9 @@
     },
     "LIBC_CONF_PRINTF_DISABLE_STRERROR": {
       "value": true
+    },
+    "LIBC_CONF_PRINTF_RUNTIME_DISPATCH": {
+      "value": false
     }
   },
   "scanf": {
diff --git a/libc/config/gpu/nvptx/config.json b/libc/config/gpu/nvptx/config.json
index d99f48ecbede1..30ae10e2cfd61 100644
--- a/libc/config/gpu/nvptx/config.json
+++ b/libc/config/gpu/nvptx/config.json
@@ -19,6 +19,9 @@
     },
     "LIBC_CONF_PRINTF_DISABLE_STRERROR": {
       "value": true
+    },
+    "LIBC_CONF_PRINTF_RUNTIME_DISPATCH": {
+      "value": false
     }
   },
   "scanf": {
diff --git a/libc/docs/configure.rst b/libc/docs/configure.rst
index 940a07754c458..a4b6237ef1e05 100644
--- a/libc/docs/configure.rst
+++ b/libc/docs/configure.rst
@@ -45,6 +45,7 @@ to learn about the defaults for your platform and target.
     - ``LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_DYADIC_FLOAT``: Use dyadic float for faster and smaller but less accurate printf doubles.
     - ``LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_FLOAT320``: Use an alternative printf float implementation based on 320-bit floats
     - ``LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE``: Use large table for better printf long double performance.
+    - ``LIBC_CONF_PRINTF_RUNTIME_DISPATCH``: Use dynamic distpatch for conversion functions to reduce code size.
 * **"pthread" options**
     - ``LIBC_CONF_RAW_MUTEX_DEFAULT_SPIN_COUNT``: Default number of spins before blocking if a mutex is in contention (default to 100).
     - ``LIBC_CONF_RWLOCK_DEFAULT_SPIN_COUNT``: Default number of spins before blocking if a rwlock is in contention (default to 100).
diff --git a/libc/src/stdio/printf_core/CMakeLists.txt b/libc/src/stdio/printf_core/CMakeLists.txt
index ea58067c7070a..c22f9858f3b1e 100644
--- a/libc/src/stdio/printf_core/CMakeLists.txt
+++ b/libc/src/stdio/printf_core/CMakeLists.txt
@@ -25,6 +25,9 @@ endif()
 if(LIBC_CONF_PRINTF_DISABLE_STRERROR)
   list(APPEND printf_config_copts "-DLIBC_COPT_PRINTF_DISABLE_STRERROR")
 endif()
+if(LIBC_CONF_PRINTF_RUNTIME_DISPATCH)
+  list(APPEND printf_config_copts "-DLIBC_COPT_PRINTF_RUNTIME_DISPATCH")
+endif()
 if(printf_config_copts)
   list(PREPEND printf_config_copts "COMPILE_OPTIONS")
 endif()
@@ -62,10 +65,8 @@ add_header_library(
     libc.src.__support.common
 )
 
-add_object_library(
+add_header_library(
   writer
-  SRCS
-    writer.cpp
   HDRS
     writer.h
   DEPENDS
@@ -76,10 +77,8 @@ add_object_library(
     libc.src.string.memory_utils.inline_memset
 )
 
-add_object_library(
+add_header_library(
   converter
-  SRCS
-    converter.cpp
   HDRS
     converter.h
     converter_atlas.h
@@ -113,10 +112,8 @@ add_object_library(
     libc.src.__support.StringUtil.error_to_string
 )
 
-add_object_library(
+add_header_library(
   printf_main
-  SRCS
-    printf_main.cpp
   HDRS
     printf_main.h
   DEPENDS
diff --git a/libc/src/stdio/printf_core/char_converter.h b/libc/src/stdio/printf_core/char_converter.h
index 2596cba813c2e..fd2eb2553887a 100644
--- a/libc/src/stdio/printf_core/char_converter.h
+++ b/libc/src/stdio/printf_core/char_converter.h
@@ -17,7 +17,9 @@
 namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
-LIBC_INLINE int convert_char(Writer *writer, const FormatSection &to_conv) {
+template <WriteMode write_mode>
+LIBC_INLINE int convert_char(Writer<write_mode> *writer,
+                             const FormatSection &to_conv) {
   char c = static_cast<char>(to_conv.conv_val_raw);
 
   constexpr int STRING_LEN = 1;
diff --git a/libc/src/stdio/printf_core/converter.cpp b/libc/src/stdio/printf_core/converter.cpp
deleted file mode 100644
index b1c66451f53f0..0000000000000
--- a/libc/src/stdio/printf_core/converter.cpp
+++ /dev/null
@@ -1,105 +0,0 @@
-//===-- Format specifier converter implmentation for printf -----*- C++ -*-===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "src/stdio/printf_core/converter.h"
-
-#include "src/__support/macros/config.h"
-#include "src/stdio/printf_core/core_structs.h"
-#include "src/stdio/printf_core/printf_config.h"
-#include "src/stdio/printf_core/strerror_converter.h"
-#include "src/stdio/printf_core/writer.h"
-
-// This option allows for replacing all of the conversion functions with custom
-// replacements. This allows conversions to be replaced at compile time.
-#ifndef LIBC_COPT_PRINTF_CONV_ATLAS
-#include "src/stdio/printf_core/converter_atlas.h"
-#else
-#include LIBC_COPT_PRINTF_CONV_ATLAS
-#endif
-
-#include <stddef.h>
-
-namespace LIBC_NAMESPACE_DECL {
-namespace printf_core {
-
-int convert(Writer *writer, const FormatSection &to_conv) {
-  if (!to_conv.has_conv)
-    return writer->write(to_conv.raw_string);
-
-#if !defined(LIBC_COPT_PRINTF_DISABLE_FLOAT) &&                                \
-    defined(LIBC_COPT_PRINTF_HEX_LONG_DOUBLE)
-  if (to_conv.length_modifier == LengthModifier::L) {
-    switch (to_conv.conv_name) {
-    case 'f':
-    case 'F':
-    case 'e':
-    case 'E':
-    case 'g':
-    case 'G':
-      return convert_float_hex_exp(writer, to_conv);
-    default:
-      break;
-    }
-  }
-#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
-
-  switch (to_conv.conv_name) {
-  case '%':
-    return writer->write("%");
-  case 'c':
-    return convert_char(writer, to_conv);
-  case 's':
-    return convert_string(writer, to_conv);
-  case 'd':
-  case 'i':
-  case 'u':
-  case 'o':
-  case 'x':
-  case 'X':
-  case 'b':
-  case 'B':
-    return convert_int(writer, to_conv);
-#ifndef LIBC_COPT_PRINTF_DISABLE_FLOAT
-  case 'f':
-  case 'F':
-    return convert_float_decimal(writer, to_conv);
-  case 'e':
-  case 'E':
-    return convert_float_dec_exp(writer, to_conv);
-  case 'a':
-  case 'A':
-    return convert_float_hex_exp(writer, to_conv);
-  case 'g':
-  case 'G':
-    return convert_float_dec_auto(writer, to_conv);
-#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
-#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
-  case 'r':
-  case 'R':
-  case 'k':
-  case 'K':
-    return convert_fixed(writer, to_conv);
-#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
-#ifndef LIBC_COPT_PRINTF_DISABLE_STRERROR
-  case 'm':
-    return convert_strerror(writer, to_conv);
-#endif // LIBC_COPT_PRINTF_DISABLE_STRERROR
-#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
-  case 'n':
-    return convert_write_int(writer, to_conv);
-#endif // LIBC_COPT_PRINTF_DISABLE_WRITE_INT
-  case 'p':
-    return convert_pointer(writer, to_conv);
-  default:
-    return writer->write(to_conv.raw_string);
-  }
-  return -1;
-}
-
-} // namespace printf_core
-} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/printf_core/converter.h b/libc/src/stdio/printf_core/converter.h
index 2b3f06d0aa7a3..f26ed727f05f4 100644
--- a/libc/src/stdio/printf_core/converter.h
+++ b/libc/src/stdio/printf_core/converter.h
@@ -11,8 +11,18 @@
 
 #include "src/__support/macros/config.h"
 #include "src/stdio/printf_core/core_structs.h"
+#include "src/stdio/printf_core/printf_config.h"
+#include "src/stdio/printf_core/strerror_converter.h"
 #include "src/stdio/printf_core/writer.h"
 
+// This option allows for replacing all of the conversion functions with custom
+// replacements. This allows conversions to be replaced at compile time.
+#ifndef LIBC_COPT_PRINTF_CONV_ATLAS
+#include "src/stdio/printf_core/converter_atlas.h"
+#else
+#include LIBC_COPT_PRINTF_CONV_ATLAS
+#endif
+
 #include <stddef.h>
 
 namespace LIBC_NAMESPACE_DECL {
@@ -21,7 +31,80 @@ namespace printf_core {
 // convert will call a conversion function to convert the FormatSection into
 // its string representation, and then that will write the result to the
 // writer.
-int convert(Writer *writer, const FormatSection &to_conv);
+template <WriteMode write_mode>
+int convert(Writer<write_mode> *writer, const FormatSection &to_conv) {
+  if (!to_conv.has_conv)
+    return writer->write(to_conv.raw_string);
+
+#if !defined(LIBC_COPT_PRINTF_DISABLE_FLOAT) &&                                \
+    defined(LIBC_COPT_PRINTF_HEX_LONG_DOUBLE)
+  if (to_conv.length_modifier == LengthModifier::L) {
+    switch (to_conv.conv_name) {
+    case 'f':
+    case 'F':
+    case 'e':
+    case 'E':
+    case 'g':
+    case 'G':
+      return convert_float_hex_exp(writer, to_conv);
+    default:
+      break;
+    }
+  }
+#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
+
+  switch (to_conv.conv_name) {
+  case '%':
+    return writer->write("%");
+  case 'c':
+    return convert_char(writer, to_conv);
+  case 's':
+    return convert_string(writer, to_conv);
+  case 'd':
+  case 'i':
+  case 'u':
+  case 'o':
+  case 'x':
+  case 'X':
+  case 'b':
+  case 'B':
+    return convert_int(writer, to_conv);
+#ifndef LIBC_COPT_PRINTF_DISABLE_FLOAT
+  case 'f':
+  case 'F':
+    return convert_float_decimal(writer, to_conv);
+  case 'e':
+  case 'E':
+    return convert_float_dec_exp(writer, to_conv);
+  case 'a':
+  case 'A':
+    return convert_float_hex_exp(writer, to_conv);
+  case 'g':
+  case 'G':
+    return convert_float_dec_auto(writer, to_conv);
+#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
+#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+  case 'r':
+  case 'R':
+  case 'k':
+  case 'K':
+    return convert_fixed(writer, to_conv);
+#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+#ifndef LIBC_COPT_PRINTF_DISABLE_STRERROR
+  case 'm':
+    return convert_strerror(writer, to_conv);
+#endif // LIBC_COPT_PRINTF_DISABLE_STRERROR
+#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
+  case 'n':
+    return convert_write_int(writer, to_conv);
+#endif // LIBC_COPT_PRINTF_DISABLE_WRITE_INT
+  case 'p':
+    return convert_pointer(writer, to_conv);
+  default:
+    return writer->write(to_conv.raw_string);
+  }
+  return -1;
+}
 
 } // namespace printf_core
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/printf_core/fixed_converter.h b/libc/src/stdio/printf_core/fixed_converter.h
index ba0a62d9fcb87..e8a3314967562 100644
--- a/libc/src/stdio/printf_core/fixed_converter.h
+++ b/libc/src/stdio/printf_core/fixed_converter.h
@@ -63,7 +63,9 @@ LIBC_INLINE constexpr uint32_t const_ten_exp(uint32_t exponent) {
     }                                                                          \
   } while (false)
 
-LIBC_INLINE int convert_fixed(Writer *writer, const FormatSection &to_conv) {
+template <WriteMode write_mode>
+LIBC_INLINE int convert_fixed(Writer<write_mode> *writer,
+                              const FormatSection &to_conv) {
   // Long accum should be the largest type, so we can store all the smaller
   // numbers in things sized for it.
   using LARep = fixed_point::FXRep<unsigned long accum>;
diff --git a/libc/src/stdio/printf_core/float_dec_converter.h b/libc/src/stdio/printf_core/float_dec_converter.h
index d93457fcafd7f..ee5549825a6f2 100644
--- a/libc/src/stdio/printf_core/float_dec_converter.h
+++ b/libc/src/stdio/printf_core/float_dec_converter.h
@@ -93,7 +93,7 @@ zero_after_digits(int32_t base_2_exp, int32_t digits_after_point, T mantissa,
   return has_trailing_zeros;
 }
 
-class PaddingWriter {
+template <WriteMode write_mode> class PaddingWriter {
   bool left_justified = false;
   bool leading_zeroes = false;
   char sign_char = 0;
@@ -107,7 +107,8 @@ class PaddingWriter {
         sign_char(init_sign_char),
         min_width(to_conv.min_width > 0 ? to_conv.min_width : 0) {}
 
-  LIBC_INLINE int write_left_padding(Writer *writer, size_t total_digits) {
+  LIBC_INLINE int write_left_padding(Writer<write_mode> *writer,
+                                     size_t total_digits) {
     // The pattern is (spaces) (sign) (zeroes), but only one of spaces and
     // zeroes can be written, and only if the padding amount is positive.
     int padding_amount =
@@ -130,7 +131,8 @@ class PaddingWriter {
     return 0;
   }
 
-  LIBC_INLINE int write_right_padding(Writer *writer, size_t total_digits) {
+  LIBC_INLINE int write_right_padding(Writer<write_mode> *writer,
+                                      size_t total_digits) {
     // If and only if the conversion is left justified, there may be trailing
     // spaces.
     int padding_amount =
@@ -155,7 +157,7 @@ class PaddingWriter {
   This FloatWriter class does the buffering and counting, and writes to the
   output when necessary.
 */
-class FloatWriter {
+template <WriteMode write_mode> class FloatWriter {
   char block_buffer[BLOCK_SIZE]; // The buffer that holds a block.
   size_t buffered_digits = 0;    // The number of digits held in the buffer.
   bool has_written = false;      // True once any digits have been output.
@@ -164,8 +166,9 @@ class FloatWriter {
   size_t digits_before_decimal = 0; // The # of digits to write before the '.'
   size_t total_digits_written = 0;  // The # of digits that have been output.
   bool has_decimal_point;           // True if the number has a decimal point.
-  Writer *writer;                   // Writes to the final output.
-  PaddingWriter padding_writer; // Handles prefixes/padding, uses total_digits.
+  Writer<write_mode> *writer;       // Writes to the final output.
+  PaddingWriter<write_mode>
+      padding_writer; // Handles prefixes/padding, uses total_digits.
 
   LIBC_INLINE int flush_buffer(bool round_up_max_blocks = false) {
     const char MAX_BLOCK_DIGIT = (round_up_max_blocks ? '0' : '9');
@@ -245,8 +248,9 @@ class FloatWriter {
   static_assert(fputil::FPBits<long double>::EXP_LEN < (sizeof(int) * 8));
 
 public:
-  LIBC_INLINE FloatWriter(Writer *init_writer, bool init_has_decimal_point,
-                          const PaddingWriter &init_padding_writer)
+  LIBC_INLINE FloatWriter(Writer<write_mode> *init_writer,
+                          bool init_has_decimal_point,
+                          const PaddingWriter<write_mode> &init_padding_writer)
       : has_decimal_point(init_has_decimal_point), writer(init_writer),
         padding_writer(init_padding_writer) {}
 
@@ -466,12 +470,24 @@ class FloatWriter {
   }
 };
 
+// Class-template auto deduction helpers, add more if needed.
+FloatWriter(Writer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>, bool,
+            const PaddingWriter<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>)
+    -> FloatWriter<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>;
+FloatWriter(Writer<WriteMode::RESIZE_AND_FILL_BUFF>, bool,
+            const PaddingWriter<WriteMode::RESIZE_AND_FILL_BUFF>)
+    -> FloatWriter<WriteMode::RESIZE_AND_FILL_BUFF>;
+FloatWriter(Writer<WriteMode::FLUSH_TO_STREAM>, bool,
+            const PaddingWriter<WriteMode::FLUSH_TO_STREAM>)
+    -> FloatWriter<WriteMode::FLUSH_TO_STREAM>;
+
 // This implementation is based on the Ryu Printf algorithm by Ulf Adams:
 // Ulf Adams. 2019. Ryƫ revisited: printf floating point conversion.
 // Proc. ACM Program. Lang. 3, OOPSLA, Article 169 (October 2019), 23 pages.
 // https://doi.org/10.1145/3360595
-template <typename T, cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
-LIBC_INLINE int convert_float_decimal_typed(Writer *writer,
+template <typename T, WriteMode write_mode,
+          cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
+LIBC_INLINE int convert_float_decimal_typed(Writer<write_mode> *writer,
                                             const FormatSection &to_conv,
                                             fputil::FPBits<T> float_bits) {
   // signed because later we use -FRACTION_LEN
@@ -498,7 +514,7 @@ LIBC_INLINE int convert_float_decimal_typed(Writer *writer,
   // ignored.
   bool nonzero = false;
 
-  PaddingWriter padding_writer(to_conv, sign_char);
+  PaddingWriter<write_mode> padding_writer(to_conv, sign_char);
   FloatWriter float_writer(writer, has_decimal_point, padding_writer);
   FloatToString<T> float_converter(float_bits.get_val());
 
@@ -579,8 +595,9 @@ LIBC_INLINE int convert_float_decimal_typed(Writer *writer,
   return WRITE_OK;
 }
 
-template <typename T, cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
-LIBC_INLINE int convert_float_dec_exp_typed(Writer *writer,
+template <typename T, WriteMode write_mode,
+          cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
+LIBC_INLINE int convert_float_dec_exp_typed(Writer<write_mode> *writer,
                                             const FormatSection &to_conv,
                                             fputil::FPBits<T> float_bits) {
   // signed because later we use -FRACTION_LEN
@@ -603,7 +620,7 @@ LIBC_INLINE int convert_float_dec_exp_typed(Writer *writer,
   bool has_decimal_point =
       (precision > 0) || ((to_conv.flags & FormatFlags::ALTERNATE_FORM) != 0);
 
-  PaddingWriter padding_writer(to_conv, sign_char);
+  PaddingWriter<write_mode> padding_writer(to_conv, sign_char);
   FloatWriter float_writer(writer, has_decimal_point, padding_writer);
   FloatToString<T> float_converter(float_bits.get_val());
 
@@ -740,8 +757,9 @@ LIBC_INLINE int convert_float_dec_exp_typed(Writer *writer,
   return WRITE_OK;
 }
 
-template <typename T, cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
-LIBC_INLINE int convert_float_dec_auto_typed(Writer *writer,
+template <typename T, WriteMode write_mode,
+          cpp::enable_if_t<cpp::is_floating_point_v<T>, int> = 0>
+LIBC_INLINE int convert_float_dec_auto_typed(Writer<write_mode> *writer,
                                              const FormatSection &to_conv,
                                              fputil::FPBits<T> float_bits) {
   // signed because later we use -FRACTION_LEN
@@ -1107,7 +1125,9 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer *writer,
 }
 
 // TODO: unify the float converters to remove the duplicated checks for inf/nan.
-LIBC_INLINE int convert_float_decimal(Writer *writer,
+
+template <WriteMode write_mode>
+LIBC_INLINE int convert_float_decimal(Writer<write_mode> *writer,
                                       const FormatSection &to_conv) {
   if (to_conv.length_modifier == LengthModifier::L) {
     fputil::FPBits<long double>::StorageType float_raw = to_conv.conv_val_raw;
@@ -1128,7 +1148,8 @@ LIBC_INLINE int convert_float_decimal(Writer *writer,
   return convert_inf_nan(writer, to_conv);
 }
 
-LIBC_INLINE int convert_float_dec_exp(Writer *writer,
+template <WriteMode write_mode>
+LIBC_INLINE int convert_float_dec_exp(Writer<write_mode> *writer,
                                       const FormatSection &to_conv) {
   if (to_conv.length_modifier == LengthModifier::L) {
     fputil::FPBits<long double>::StorageType float_raw = to_conv.conv_val_raw;
@@ -1149,7 +1170,8 @@ LIBC_INLINE int convert_float_dec_exp(Writer *writer,
   return convert_inf_nan(writer, to_conv);
 }
 
-LIBC_INLINE int convert_float_dec_auto(Writer *writer,
+template <WriteMode write_mode>
+LIBC_INLINE int convert_float_dec_auto(Writer<write_mode> *writer,
                                        const FormatSection &to_conv) {
   if (to_conv.length_modifier == LengthModifier::L) {
     fputil::FPBits<long double>::StorageType float_raw = to_conv.conv_val_raw;
diff --git a/libc/src/stdio/printf_core/float_hex_converter.h b/libc/src/stdio/printf_core/float_hex_converter.h
index b264b5cf20728..16592e7bac932 100644
--- a/libc/src/stdio/printf_core/float_hex_converter.h
+++ b/libc/src/stdio/printf_core/float_hex_converter.h
@@ -25,7 +25,8 @@
 namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
-LIBC_INLINE int convert_float_hex_exp(Writer *writer,
+template <WriteMode write_mode>
+LIBC_INLINE int convert_float_hex_exp(Writer<write_mode> *writer,
                                       const FormatSection &to_conv) {
   using LDBits = fputil::FPBits<long double>;
   using StorageType = LDBits::StorageType;
diff --git a/libc/src/stdio/printf_core/float_inf_nan_converter.h b/libc/src/stdio/printf_core/float_inf_nan_converter.h
index 3e41612e21c9f..ce31d7ae55499 100644
--- a/libc/src/stdio/printf_core/float_inf_nan_converter.h
+++ b/libc/src/stdio/printf_core/float_inf_nan_converter.h
@@ -24,7 +24,9 @@ namespace printf_core {
 
 using StorageType = fputil::FPBits<long double>::StorageType;
 
-LIBC_INLINE int convert_inf_nan(Writer *writer, const FormatSection &to_conv) {
+template <WriteMode write_mode>
+LIBC_INLINE int convert_inf_nan(Writer<write_mode> *writer,
+                                const FormatSection &to_conv) {
   // All of the letters will be defined relative to variable a, which will be
   // the appropriate case based on the case of the conversion.
   bool is_negative;
diff --git a/libc/src/stdio/printf_core/int_converter.h b/libc/src/stdio/printf_core/int_converter.h
index d0af229f89be5..11234c32ce997 100644
--- a/libc/src/stdio/printf_core/int_converter.h
+++ b/libc/src/stdio/printf_core/int_converter.h
@@ -61,7 +61,9 @@ num_to_strview(uintmax_t num, cpp::span<char> bufref, char conv_name) {
 
 } // namespace details
 
-LIBC_INLINE int convert_int(Writer *writer, const FormatSection &to_conv) {
+template <WriteMode write_mode>
+LIBC_INLINE int convert_int(Writer<write_mode> *writer,
+                            const FormatSection &to_conv) {
   static constexpr size_t BITS_IN_BYTE = 8;
   static constexpr size_t BITS_IN_NUM = sizeof(uintmax_t) * BITS_IN_BYTE;
 
diff --git a/libc/src/stdio/printf_core/printf_main.cpp b/libc/src/stdio/printf_core/printf_main.cpp
deleted file mode 100644
index bd4a5a168bd23..0000000000000
--- a/libc/src/stdio/printf_core/printf_main.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-//===-- Starting point for printf -------------------------------*- C++ -*-===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "src/stdio/printf_core/printf_main.h"
-
-#include "src/__support/arg_list.h"
-#include "src/__support/macros/config.h"
-#include "src/stdio/printf_core/converter.h"
-#include "src/stdio/printf_core/core_structs.h"
-#include "src/stdio/printf_core/parser.h"
-#include "src/stdio/printf_core/writer.h"
-
-#include <stddef.h>
-
-namespace LIBC_NAMESPACE_DECL {
-namespace printf_core {
-
-int printf_main(Writer *writer, const char *__restrict str,
-                internal::ArgList &args) {
-  Parser<internal::ArgList> parser(str, args);
-  int result = 0;
-  for (FormatSection cur_section = parser.get_next_section();
-       !cur_section.raw_string.empty();
-       cur_section = parser.get_next_section()) {
-    if (cur_section.has_conv)
-      result = convert(writer, cur_section);
-    else
-      result = writer->write(cur_section.raw_string);
-
-    if (result < 0)
-      return result;
-  }
-
-  return writer->get_chars_written();
-}
-
-} // namespace printf_core
-} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/printf_core/printf_main.h b/libc/src/stdio/printf_core/printf_main.h
index 3e73bf36e0e30..57f29858d5298 100644
--- a/libc/src/stdio/printf_core/printf_main.h
+++ b/libc/src/stdio/printf_core/printf_main.h
@@ -11,6 +11,9 @@
 
 #include "src/__support/arg_list.h"
 #include "src/__support/macros/config.h"
+#include "src/stdio/printf_core/converter.h"
+#include "src/stdio/printf_core/core_structs.h"
+#include "src/stdio/printf_core/parser.h"
 #include "src/stdio/printf_core/writer.h"
 
 #include <stddef.h>
@@ -18,8 +21,25 @@
 namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
-int printf_main(Writer *writer, const char *__restrict str,
-                internal::ArgList &args);
+template <WriteMode write_mode>
+int printf_main(Writer<write_mode> *writer, const char *__restrict str,
+                internal::ArgList &args) {
+  Parser<internal::ArgList> parser(str, args);
+  int result = 0;
+  for (FormatSection cur_section = parser.get_next_section();
+       !cur_section.raw_string.empty();
+       cur_section = parser.get_next_section()) {
+    if (cur_section.has_conv)
+      result = convert(writer, cur_section);
+    else
+      result = writer->write(cur_section.raw_string);
+
+    if (result < 0)
+      return result;
+  }
+
+  return writer->get_chars_written();
+}
 
 } // namespace printf_core
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/printf_core/ptr_converter.h b/libc/src/stdio/printf_core/ptr_converter.h
index bf84718dfe6a8..c2a74e3043e6f 100644
--- a/libc/src/stdio/printf_core/ptr_converter.h
+++ b/libc/src/stdio/printf_core/ptr_converter.h
@@ -18,7 +18,9 @@
 namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
-LIBC_INLINE int convert_pointer(Writer *writer, const FormatSection &to_conv) {
+template <WriteMode write_mode>
+LIBC_INLINE int convert_pointer(Writer<write_mode> *writer,
+                                const FormatSection &to_conv) {
   FormatSection new_conv = to_conv;
 
   if (to_conv.conv_val_ptr == nullptr) {
diff --git a/libc/src/stdio/printf_core/strerror_converter.h b/libc/src/stdio/printf_core/strerror_converter.h
index 2902fd37c31ae..2cd6df0c01d45 100644
--- a/libc/src/stdio/printf_core/strerror_converter.h
+++ b/libc/src/stdio/printf_core/strerror_converter.h
@@ -19,7 +19,9 @@
 namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
-LIBC_INLINE int convert_strerror(Writer *writer, const FormatSection &to_conv) {
+template <WriteMode write_mode>
+LIBC_INLINE int convert_strerror(Writer<write_mode> *writer,
+                                 const FormatSection &to_conv) {
   FormatSection new_conv = to_conv;
   const int error_num = static_cast<int>(to_conv.conv_val_raw);
 
diff --git a/libc/src/stdio/printf_core/string_converter.h b/libc/src/stdio/printf_core/string_converter.h
index 1f36d51124107..74c9f598210f7 100644
--- a/libc/src/stdio/printf_core/string_converter.h
+++ b/libc/src/stdio/printf_core/string_converter.h
@@ -20,7 +20,9 @@
 namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
-LIBC_INLINE int convert_string(Writer *writer, const FormatSection &to_conv) {
+template <WriteMode write_mode>
+LIBC_INLINE int convert_string(Writer<write_mode> *writer,
+                               const FormatSection &to_conv) {
   size_t string_len = 0;
   const char *str_ptr = reinterpret_cast<const char *>(to_conv.conv_val_ptr);
 
diff --git a/libc/src/stdio/printf_core/vasprintf_internal.h b/libc/src/stdio/printf_core/vasprintf_internal.h
index 0e446f856e438..9d46617da7751 100644
--- a/libc/src/stdio/printf_core/vasprintf_internal.h
+++ b/libc/src/stdio/printf_core/vasprintf_internal.h
@@ -19,8 +19,9 @@ namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
 LIBC_INLINE int resize_overflow_hook(cpp::string_view new_str, void *target) {
-  printf_core::WriteBuffer *wb =
-      reinterpret_cast<printf_core::WriteBuffer *>(target);
+  WriteBuffer<Mode<WriteMode::RESIZE_AND_FILL_BUFF>::value> *wb =
+      reinterpret_cast<
+          WriteBuffer<Mode<WriteMode::RESIZE_AND_FILL_BUFF>::value> *>(target);
   size_t new_size = new_str.size() + wb->buff_cur;
   const bool isBuffOnStack = (wb->buff == wb->init_buff);
   char *new_buff = static_cast<char *>(
@@ -45,9 +46,9 @@ constexpr size_t DEFAULT_BUFFER_SIZE = 200;
 LIBC_INLINE int vasprintf_internal(char **ret, const char *__restrict format,
                                    internal::ArgList args) {
   char init_buff_on_stack[DEFAULT_BUFFER_SIZE];
-  printf_core::WriteBuffer wb(init_buff_on_stack, DEFAULT_BUFFER_SIZE,
-                              resize_overflow_hook);
-  printf_core::Writer writer(&wb);
+  printf_core::WriteBuffer<Mode<WriteMode::RESIZE_AND_FILL_BUFF>::value> wb(
+      init_buff_on_stack, DEFAULT_BUFFER_SIZE, resize_overflow_hook);
+  printf_core::Writer writer(wb);
 
   auto ret_val = printf_core::printf_main(&writer, format, args);
   if (ret_val < 0) {
diff --git a/libc/src/stdio/printf_core/vfprintf_internal.h b/libc/src/stdio/printf_core/vfprintf_internal.h
index 3becfee71dd27..630de9d9d43dd 100644
--- a/libc/src/stdio/printf_core/vfprintf_internal.h
+++ b/libc/src/stdio/printf_core/vfprintf_internal.h
@@ -72,9 +72,9 @@ LIBC_INLINE int vfprintf_internal(::FILE *__restrict stream,
                                   internal::ArgList &args) {
   constexpr size_t BUFF_SIZE = 1024;
   char buffer[BUFF_SIZE];
-  printf_core::WriteBuffer wb(buffer, BUFF_SIZE, &file_write_hook,
-                              reinterpret_cast<void *>(stream));
-  Writer writer(&wb);
+  printf_core::WriteBuffer<Mode<WriteMode::FLUSH_TO_STREAM>::value> wb(
+      buffer, BUFF_SIZE, &file_write_hook, reinterpret_cast<void *>(stream));
+  Writer writer(wb);
   internal::flockfile(stream);
   int retval = printf_main(&writer, format, args);
   int flushval = wb.overflow_write("");
diff --git a/libc/src/stdio/printf_core/write_int_converter.h b/libc/src/stdio/printf_core/write_int_converter.h
index a47cb41cb3287..efcff278bd284 100644
--- a/libc/src/stdio/printf_core/write_int_converter.h
+++ b/libc/src/stdio/printf_core/write_int_converter.h
@@ -19,7 +19,8 @@
 namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
-LIBC_INLINE int convert_write_int(Writer *writer,
+template <WriteMode write_mode>
+LIBC_INLINE int convert_write_int(Writer<write_mode> *writer,
                                   const FormatSection &to_conv) {
 
 #ifndef LIBC_COPT_PRINTF_NO_NULLPTR_CHECKS
diff --git a/libc/src/stdio/printf_core/writer.cpp b/libc/src/stdio/printf_core/writer.cpp
deleted file mode 100644
index d1cf85df1c8f8..0000000000000
--- a/libc/src/stdio/printf_core/writer.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-//===-- Writer definition for printf ----------------------------*- C++ -*-===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "writer.h"
-#include "src/__support/CPP/string_view.h"
-#include "src/__support/macros/config.h"
-#include "src/stdio/printf_core/core_structs.h"
-#include "src/string/memory_utils/inline_memset.h"
-#include <stddef.h>
-
-namespace LIBC_NAMESPACE_DECL {
-namespace printf_core {
-
-int Writer::pad(char new_char, size_t length) {
-  // First, fill as much of the buffer as possible with the padding char.
-  size_t written = 0;
-  const size_t buff_space = wb->buff_len - wb->buff_cur;
-  // ASSERT: length > buff_space
-  if (buff_space > 0) {
-    inline_memset(wb->buff + wb->buff_cur, new_char, buff_space);
-    wb->buff_cur += buff_space;
-    written = buff_space;
-  }
-
-  // Next, overflow write the rest of length using the mini_buff.
-  constexpr size_t MINI_BUFF_SIZE = 64;
-  char mini_buff[MINI_BUFF_SIZE];
-  inline_memset(mini_buff, new_char, MINI_BUFF_SIZE);
-  cpp::string_view mb_string_view(mini_buff, MINI_BUFF_SIZE);
-  while (written + MINI_BUFF_SIZE < length) {
-    int result = wb->overflow_write(mb_string_view);
-    if (result != WRITE_OK)
-      return result;
-    written += MINI_BUFF_SIZE;
-  }
-  cpp::string_view mb_substr = mb_string_view.substr(0, length - written);
-  return wb->overflow_write(mb_substr);
-}
-
-} // namespace printf_core
-} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/printf_core/writer.h b/libc/src/stdio/printf_core/writer.h
index 28c9a28270875..40aa0a07c6322 100644
--- a/libc/src/stdio/printf_core/writer.h
+++ b/libc/src/stdio/printf_core/writer.h
@@ -21,12 +21,24 @@
 namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
-struct WriteBuffer {
-  enum class WriteMode {
-    FILL_BUFF_AND_DROP_OVERFLOW,
-    FLUSH_TO_STREAM,
-    RESIZE_AND_FILL_BUFF,
-  };
+enum class WriteMode {
+  FILL_BUFF_AND_DROP_OVERFLOW,
+  FLUSH_TO_STREAM,
+  RESIZE_AND_FILL_BUFF,
+  RUNTIME_DISPATCH,
+};
+
+// Helper to omit the template argument if we are using runtime dispatch and
+// avoid multiple copies of the converter functions.
+template <WriteMode write_mode> struct Mode {
+#ifdef LIBC_COPT_PRINTF_RUNTIME_DISPATCH
+  static constexpr WriteMode value = WriteMode::RUNTIME_DISPATCH;
+#else
+  static constexpr WriteMode value = write_mode;
+#endif
+};
+
+template <WriteMode write_mode> struct WriteBuffer {
   using StreamWriter = int (*)(cpp::string_view, void *);
   char *buff;
   const char *init_buff; // for checking when resize.
@@ -35,23 +47,26 @@ struct WriteBuffer {
 
   // The stream writer will be called when the buffer is full. It will be passed
   // string_views to write to the stream.
-  StreamWriter stream_writer;
+  const StreamWriter stream_writer;
   void *output_target;
-  WriteMode write_mode;
 
-  LIBC_INLINE WriteBuffer(char *Buff, size_t Buff_len, StreamWriter hook,
+  // The current writing mode in case the user wants runtime dispatch of the
+  // stream writer with function pointers.
+  [[maybe_unused]] WriteMode write_mode_;
+
+  LIBC_INLINE WriteBuffer(char *buff, size_t buff_len, StreamWriter hook,
                           void *target)
-      : buff(Buff), init_buff(Buff), buff_len(Buff_len), stream_writer(hook),
-        output_target(target), write_mode(WriteMode::FLUSH_TO_STREAM) {}
+      : buff(buff), init_buff(buff), buff_len(buff_len), stream_writer(hook),
+        output_target(target), write_mode_(WriteMode::FLUSH_TO_STREAM) {}
 
-  LIBC_INLINE WriteBuffer(char *Buff, size_t Buff_len)
-      : buff(Buff), init_buff(Buff), buff_len(Buff_len), stream_writer(nullptr),
+  LIBC_INLINE WriteBuffer(char *buff, size_t buff_len)
+      : buff(buff), init_buff(buff), buff_len(buff_len), stream_writer(nullptr),
         output_target(nullptr),
-        write_mode(WriteMode::FILL_BUFF_AND_DROP_OVERFLOW) {}
+        write_mode_(WriteMode::FILL_BUFF_AND_DROP_OVERFLOW) {}
 
-  LIBC_INLINE WriteBuffer(char *Buff, size_t Buff_len, StreamWriter hook)
-      : buff(Buff), init_buff(Buff), buff_len(Buff_len), stream_writer(hook),
-        output_target(this), write_mode(WriteMode::RESIZE_AND_FILL_BUFF) {}
+  LIBC_INLINE WriteBuffer(char *buff, size_t buff_len, StreamWriter hook)
+      : buff(buff), init_buff(buff), buff_len(buff_len), stream_writer(hook),
+        output_target(this), write_mode_(WriteMode::RESIZE_AND_FILL_BUFF) {}
 
   LIBC_INLINE int flush_to_stream(cpp::string_view new_str) {
     if (buff_cur > 0) {
@@ -92,40 +107,69 @@ struct WriteBuffer {
   // this with an empty string will flush the buffer if relevant.
 
   LIBC_INLINE int overflow_write(cpp::string_view new_str) {
-    switch (write_mode) {
-    case WriteMode::FILL_BUFF_AND_DROP_OVERFLOW:
+#ifdef LIBC_COPT_PRINTF_RUNTIME_DISPATCH
+    if (write_mode_ == WriteMode::FILL_BUFF_AND_DROP_OVERFLOW)
       return fill_remaining_to_buff(new_str);
-    case WriteMode::FLUSH_TO_STREAM:
+    else if (write_mode_ == WriteMode::FLUSH_TO_STREAM)
       return flush_to_stream(new_str);
-    case WriteMode::RESIZE_AND_FILL_BUFF:
+    else if (write_mode_ == WriteMode::RESIZE_AND_FILL_BUFF)
       return resize_and_write(new_str);
-    }
+#else
+    if constexpr (write_mode == WriteMode::FILL_BUFF_AND_DROP_OVERFLOW)
+      return fill_remaining_to_buff(new_str);
+    else if constexpr (write_mode == WriteMode::FLUSH_TO_STREAM)
+      return flush_to_stream(new_str);
+    else if constexpr (write_mode == WriteMode::RESIZE_AND_FILL_BUFF)
+      return resize_and_write(new_str);
+#endif
     __builtin_unreachable();
   }
 };
 
-class Writer final {
-  WriteBuffer *wb;
+template <WriteMode write_mode> class Writer final {
+  WriteBuffer<write_mode> &wb;
   int chars_written = 0;
 
-  // This is a separate, non-inlined function so that the inlined part of the
-  // write function is shorter.
-  int pad(char new_char, size_t length);
+  LIBC_INLINE int pad(char new_char, size_t length) {
+    // First, fill as much of the buffer as possible with the padding char.
+    size_t written = 0;
+    const size_t buff_space = wb.buff_len - wb.buff_cur;
+    // ASSERT: length > buff_space
+    if (buff_space > 0) {
+      inline_memset(wb.buff + wb.buff_cur, new_char, buff_space);
+      wb.buff_cur += buff_space;
+      written = buff_space;
+    }
+
+    // Next, overflow write the rest of length using the mini_buff.
+    constexpr size_t MINI_BUFF_SIZE = 64;
+    char mini_buff[MINI_BUFF_SIZE];
+    inline_memset(mini_buff, new_char, MINI_BUFF_SIZE);
+    cpp::string_view mb_string_view(mini_buff, MINI_BUFF_SIZE);
+    while (written + MINI_BUFF_SIZE < length) {
+      int result = wb.overflow_write(mb_string_view);
+      if (result != WRITE_OK)
+        return result;
+      written += MINI_BUFF_SIZE;
+    }
+    cpp::string_view mb_substr = mb_string_view.substr(0, length - written);
+    return wb.overflow_write(mb_substr);
+  }
 
 public:
-  LIBC_INLINE Writer(WriteBuffer *WB) : wb(WB) {}
+  LIBC_INLINE Writer(WriteBuffer<write_mode> &wb) : wb(wb) {}
 
   // Takes a string, copies it into the buffer if there is space, else passes it
   // to the overflow mechanism to be handled separately.
   LIBC_INLINE int write(cpp::string_view new_string) {
     chars_written += static_cast<int>(new_string.size());
-    if (LIBC_LIKELY(wb->buff_cur + new_string.size() <= wb->buff_len)) {
-      inline_memcpy(wb->buff + wb->buff_cur, new_string.data(),
+    if (LIBC_LIKELY(wb.buff_cur + new_string.size() <= wb.buff_len)) {
+      inline_memcpy(wb.buff + wb.buff_cur, new_string.data(),
                     new_string.size());
-      wb->buff_cur += new_string.size();
+      wb.buff_cur += new_string.size();
       return WRITE_OK;
     }
-    return wb->overflow_write(new_string);
+    return wb.overflow_write(new_string);
   }
 
   // Takes a char and a length, memsets the next length characters of the buffer
@@ -134,10 +178,10 @@ class Writer final {
   LIBC_INLINE int write(char new_char, size_t length) {
     chars_written += static_cast<int>(length);
 
-    if (LIBC_LIKELY(wb->buff_cur + length <= wb->buff_len)) {
-      inline_memset(wb->buff + wb->buff_cur,
-                    static_cast<unsigned char>(new_char), length);
-      wb->buff_cur += length;
+    if (LIBC_LIKELY(wb.buff_cur + length <= wb.buff_len)) {
+      inline_memset(wb.buff + wb.buff_cur, static_cast<unsigned char>(new_char),
+                    length);
+      wb.buff_cur += length;
       return WRITE_OK;
     }
     return pad(new_char, length);
@@ -147,18 +191,26 @@ class Writer final {
   // to the overflow mechanism to be handled separately.
   LIBC_INLINE int write(char new_char) {
     chars_written += 1;
-    if (LIBC_LIKELY(wb->buff_cur + 1 <= wb->buff_len)) {
-      wb->buff[wb->buff_cur] = new_char;
-      wb->buff_cur += 1;
+    if (LIBC_LIKELY(wb.buff_cur + 1 <= wb.buff_len)) {
+      wb.buff[wb.buff_cur] = new_char;
+      wb.buff_cur += 1;
       return WRITE_OK;
     }
     cpp::string_view char_string_view(&new_char, 1);
-    return wb->overflow_write(char_string_view);
+    return wb.overflow_write(char_string_view);
   }
 
   LIBC_INLINE int get_chars_written() { return chars_written; }
 };
 
+// Class-template auto deduction helpers.
+Writer(WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>)
+    -> Writer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>;
+Writer(WriteBuffer<WriteMode::RESIZE_AND_FILL_BUFF>)
+    -> Writer<WriteMode::RESIZE_AND_FILL_BUFF>;
+Writer(WriteBuffer<WriteMode::FLUSH_TO_STREAM>)
+    -> Writer<WriteMode::FLUSH_TO_STREAM>;
+
 } // namespace printf_core
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/stdio/snprintf.cpp b/libc/src/stdio/snprintf.cpp
index 12ad3cd1f762b..c8940862f711f 100644
--- a/libc/src/stdio/snprintf.cpp
+++ b/libc/src/stdio/snprintf.cpp
@@ -27,8 +27,10 @@ LLVM_LIBC_FUNCTION(int, snprintf,
                                  // and pointer semantics, as well as handling
                                  // destruction automatically.
   va_end(vlist);
-  printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
-  printf_core::Writer writer(&wb);
+  printf_core::WriteBuffer<printf_core::Mode<
+      printf_core::WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>::value>
+      wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
+  printf_core::Writer writer(wb);
 
   int ret_val = printf_core::printf_main(&writer, format, args);
   if (buffsz > 0) // if the buffsz is 0 the buffer may be a null pointer.
diff --git a/libc/src/stdio/sprintf.cpp b/libc/src/stdio/sprintf.cpp
index 1f59e6bae4723..7be97d3591aaf 100644
--- a/libc/src/stdio/sprintf.cpp
+++ b/libc/src/stdio/sprintf.cpp
@@ -28,8 +28,10 @@ LLVM_LIBC_FUNCTION(int, sprintf,
                                  // destruction automatically.
   va_end(vlist);
 
-  printf_core::WriteBuffer wb(buffer, cpp::numeric_limits<size_t>::max());
-  printf_core::Writer writer(&wb);
+  printf_core::WriteBuffer<
+      printf_core::Mode<printf_core::WriteMode::RESIZE_AND_FILL_BUFF>::value>
+      wb(buffer, cpp::numeric_limits<size_t>::max());
+  printf_core::Writer writer(wb);
 
   int ret_val = printf_core::printf_main(&writer, format, args);
   wb.buff[wb.buff_cur] = '\0';
diff --git a/libc/src/stdio/vsnprintf.cpp b/libc/src/stdio/vsnprintf.cpp
index a584c76833a2d..b07a2499a0dd3 100644
--- a/libc/src/stdio/vsnprintf.cpp
+++ b/libc/src/stdio/vsnprintf.cpp
@@ -24,8 +24,10 @@ LLVM_LIBC_FUNCTION(int, vsnprintf,
   internal::ArgList args(vlist); // This holder class allows for easier copying
                                  // and pointer semantics, as well as handling
                                  // destruction automatically.
-  printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
-  printf_core::Writer writer(&wb);
+  printf_core::WriteBuffer<printf_core::Mode<
+      printf_core::WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>::value>
+      wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
+  printf_core::Writer writer(wb);
 
   int ret_val = printf_core::printf_main(&writer, format, args);
   if (buffsz > 0) // if the buffsz is 0 the buffer may be a null pointer.
diff --git a/libc/src/stdio/vsprintf.cpp b/libc/src/stdio/vsprintf.cpp
index b3978a09c671a..26d497be42125 100644
--- a/libc/src/stdio/vsprintf.cpp
+++ b/libc/src/stdio/vsprintf.cpp
@@ -25,8 +25,10 @@ LLVM_LIBC_FUNCTION(int, vsprintf,
                                  // and pointer semantics, as well as handling
                                  // destruction automatically.
 
-  printf_core::WriteBuffer wb(buffer, cpp::numeric_limits<size_t>::max());
-  printf_core::Writer writer(&wb);
+  printf_core::WriteBuffer<printf_core::Mode<
+      printf_core::WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>::value>
+      wb(buffer, cpp::numeric_limits<size_t>::max());
+  printf_core::Writer writer(wb);
 
   int ret_val = printf_core::printf_main(&writer, format, args);
   wb.buff[wb.buff_cur] = '\0';
diff --git a/libc/src/stdlib/str_from_util.h b/libc/src/stdlib/str_from_util.h
index 7f54bdf71a018..61e6ba24b3817 100644
--- a/libc/src/stdlib/str_from_util.h
+++ b/libc/src/stdlib/str_from_util.h
@@ -104,8 +104,8 @@ printf_core::FormatSection parse_format_string(const char *__restrict format,
   return section;
 }
 
-template <typename T>
-int strfromfloat_convert(printf_core::Writer *writer,
+template <typename T, printf_core::WriteMode write_mode>
+int strfromfloat_convert(printf_core::Writer<write_mode> *writer,
                          const printf_core::FormatSection &section) {
   if (!section.has_conv)
     return writer->write(section.raw_string);
diff --git a/libc/src/stdlib/strfromd.cpp b/libc/src/stdlib/strfromd.cpp
index 4c51e4c5c8a01..f51e6d4c7f1df 100644
--- a/libc/src/stdlib/strfromd.cpp
+++ b/libc/src/stdlib/strfromd.cpp
@@ -19,8 +19,10 @@ LLVM_LIBC_FUNCTION(int, strfromd,
 
   printf_core::FormatSection section =
       internal::parse_format_string(format, fp);
-  printf_core::WriteBuffer wb(s, (n > 0 ? n - 1 : 0));
-  printf_core::Writer writer(&wb);
+  printf_core::WriteBuffer<printf_core::Mode<
+      printf_core::WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>::value>
+      wb(s, (n > 0 ? n - 1 : 0));
+  printf_core::Writer writer(wb);
 
   int result = 0;
   if (section.has_conv)
diff --git a/libc/src/stdlib/strfromf.cpp b/libc/src/stdlib/strfromf.cpp
index ea98a69ee4d60..14dbfdb25bab6 100644
--- a/libc/src/stdlib/strfromf.cpp
+++ b/libc/src/stdlib/strfromf.cpp
@@ -19,8 +19,10 @@ LLVM_LIBC_FUNCTION(int, strfromf,
 
   printf_core::FormatSection section =
       internal::parse_format_string(format, fp);
-  printf_core::WriteBuffer wb(s, (n > 0 ? n - 1 : 0));
-  printf_core::Writer writer(&wb);
+  printf_core::WriteBuffer<printf_core::Mode<
+      printf_core::WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>::value>
+      wb(s, (n > 0 ? n - 1 : 0));
+  printf_core::Writer writer(wb);
 
   int result = 0;
   if (section.has_conv)
diff --git a/libc/src/stdlib/strfroml.cpp b/libc/src/stdlib/strfroml.cpp
index d5bee7609f69c..12f22a8a2fb65 100644
--- a/libc/src/stdlib/strfroml.cpp
+++ b/libc/src/stdlib/strfroml.cpp
@@ -24,8 +24,10 @@ LLVM_LIBC_FUNCTION(int, strfroml,
   // the length modifier has to be set to LenghtModifier::L
   section.length_modifier = printf_core::LengthModifier::L;
 
-  printf_core::WriteBuffer wb(s, (n > 0 ? n - 1 : 0));
-  printf_core::Writer writer(&wb);
+  printf_core::WriteBuffer<printf_core::Mode<
+      printf_core::WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>::value>
+      wb(s, (n > 0 ? n - 1 : 0));
+  printf_core::Writer writer(wb);
 
   int result = 0;
   if (section.has_conv)
diff --git a/libc/src/time/strftime.cpp b/libc/src/time/strftime.cpp
index c19e58fbadf71..f36091bc9736e 100644
--- a/libc/src/time/strftime.cpp
+++ b/libc/src/time/strftime.cpp
@@ -19,8 +19,10 @@ namespace LIBC_NAMESPACE_DECL {
 LLVM_LIBC_FUNCTION(size_t, strftime,
                    (char *__restrict buffer, size_t buffsz,
                     const char *__restrict format, const tm *timeptr)) {
-  printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
-  printf_core::Writer writer(&wb);
+  printf_core::WriteBuffer<printf_core::Mode<
+      printf_core::WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>::value>
+      wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
+  printf_core::Writer writer(wb);
   int ret = strftime_core::strftime_main(&writer, format, timeptr);
   if (buffsz > 0) // if the buffsz is 0 the buffer may be a null pointer.
     wb.buff[wb.buff_cur] = '\0';
diff --git a/libc/src/time/strftime_core/CMakeLists.txt b/libc/src/time/strftime_core/CMakeLists.txt
index a12a26b2aee0f..5e40e662ac791 100644
--- a/libc/src/time/strftime_core/CMakeLists.txt
+++ b/libc/src/time/strftime_core/CMakeLists.txt
@@ -18,10 +18,8 @@ add_header_library(
     libc.src.__support.str_to_integer
 )
 
-add_object_library(
+add_header_library(
   converter
-  SRCS
-    converter.cpp
   HDRS
     converter.h
     num_converter.h
@@ -36,10 +34,8 @@ add_object_library(
     libc.src.__support.integer_to_string
 )
 
-add_object_library(
+add_header_library(
   strftime_main
-  SRCS
-    strftime_main.cpp
   HDRS
     strftime_main.h
   DEPENDS
diff --git a/libc/src/time/strftime_core/composite_converter.h b/libc/src/time/strftime_core/composite_converter.h
index 3530075cfe9a9..53cb7e536a0e5 100644
--- a/libc/src/time/strftime_core/composite_converter.h
+++ b/libc/src/time/strftime_core/composite_converter.h
@@ -42,7 +42,8 @@ get_specific_int_format(const tm *timeptr, const FormatSection &base_to_conv,
   return result;
 }
 
-LIBC_INLINE int convert_date_us(printf_core::Writer *writer,
+template <printf_core::WriteMode write_mode>
+LIBC_INLINE int convert_date_us(printf_core::Writer<write_mode> *writer,
                                 const FormatSection &to_conv,
                                 const tm *timeptr) {
   // format is %m/%d/%y (month/day/year)
@@ -66,7 +67,8 @@ LIBC_INLINE int convert_date_us(printf_core::Writer *writer,
   return WRITE_OK;
 }
 
-LIBC_INLINE int convert_date_iso(printf_core::Writer *writer,
+template <printf_core::WriteMode write_mode>
+LIBC_INLINE int convert_date_iso(printf_core::Writer<write_mode> *writer,
                                  const FormatSection &to_conv,
                                  const tm *timeptr) {
   // format is "%Y-%m-%d" (year-month-day)
@@ -90,7 +92,8 @@ LIBC_INLINE int convert_date_iso(printf_core::Writer *writer,
   return WRITE_OK;
 }
 
-LIBC_INLINE int convert_time_am_pm(printf_core::Writer *writer,
+template <printf_core::WriteMode write_mode>
+LIBC_INLINE int convert_time_am_pm(printf_core::Writer<write_mode> *writer,
                                    const FormatSection &to_conv,
                                    const tm *timeptr) {
   // format is "%I:%M:%S %p" (hour:minute:second AM/PM)
@@ -119,7 +122,8 @@ LIBC_INLINE int convert_time_am_pm(printf_core::Writer *writer,
   return WRITE_OK;
 }
 
-LIBC_INLINE int convert_time_minute(printf_core::Writer *writer,
+template <printf_core::WriteMode write_mode>
+LIBC_INLINE int convert_time_minute(printf_core::Writer<write_mode> *writer,
                                     const FormatSection &to_conv,
                                     const tm *timeptr) {
   // format is "%H:%M" (hour:minute)
@@ -139,7 +143,8 @@ LIBC_INLINE int convert_time_minute(printf_core::Writer *writer,
   return WRITE_OK;
 }
 
-LIBC_INLINE int convert_time_second(printf_core::Writer *writer,
+template <printf_core::WriteMode write_mode>
+LIBC_INLINE int convert_time_second(printf_core::Writer<write_mode> *writer,
                                     const FormatSection &to_conv,
                                     const tm *timeptr) {
   // format is "%H:%M:%S" (hour:minute:second)
@@ -163,7 +168,8 @@ LIBC_INLINE int convert_time_second(printf_core::Writer *writer,
   return WRITE_OK;
 }
 
-LIBC_INLINE int convert_full_date_time(printf_core::Writer *writer,
+template <printf_core::WriteMode write_mode>
+LIBC_INLINE int convert_full_date_time(printf_core::Writer<write_mode> *writer,
                                        const FormatSection &to_conv,
                                        const tm *timeptr) {
   const time_utils::TMReader time_reader(timeptr);
@@ -204,7 +210,8 @@ LIBC_INLINE int convert_full_date_time(printf_core::Writer *writer,
   return WRITE_OK;
 }
 
-LIBC_INLINE int convert_composite(printf_core::Writer *writer,
+template <printf_core::WriteMode write_mode>
+LIBC_INLINE int convert_composite(printf_core::Writer<write_mode> *writer,
                                   const FormatSection &to_conv,
                                   const tm *timeptr) {
   switch (to_conv.conv_name) {
diff --git a/libc/src/time/strftime_core/converter.cpp b/libc/src/time/strftime_core/converter.cpp
deleted file mode 100644
index e9263af3d0e55..0000000000000
--- a/libc/src/time/strftime_core/converter.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-//===-- Format specifier converter implmentation for strftime -------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See htto_conv.times://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "hdr/types/struct_tm.h"
-#include "src/__support/macros/config.h"
-#include "src/stdio/printf_core/writer.h"
-#include "src/time/strftime_core/core_structs.h"
-
-#include "composite_converter.h"
-#include "num_converter.h"
-#include "str_converter.h"
-
-namespace LIBC_NAMESPACE_DECL {
-namespace strftime_core {
-
-int convert(printf_core::Writer *writer, const FormatSection &to_conv,
-            const tm *timeptr) {
-  // TODO: Implement the locale support.
-  // Currently locale flags are ignored, as described by the posix standard for
-  // the default locale.
-
-  if (!to_conv.has_conv)
-    return writer->write(to_conv.raw_string);
-  switch (to_conv.conv_name) {
-    // The cases are grouped by type, then alphabetized with lowercase before
-    // uppercase.
-
-    // raw conversions
-  case '%':
-    return writer->write("%");
-  case 'n':
-    return writer->write("\n");
-  case 't':
-    return writer->write("\t");
-
-    // numeric conversions
-  case 'C': // Century [00-99]
-  case 'd': // Day of the month [01-31]
-  case 'e': // Day of the month [1-31]
-  case 'g': // last 2 digits of ISO year [00-99]
-  case 'G': // ISO year
-  case 'H': // 24-hour format [00-23]
-  case 'I': // 12-hour format [01-12]
-  case 'j': // Day of the year [001-366]
-  case 'm': // Month of the year [01-12]
-  case 'M': // Minute of the hour [00-59]
-  case 's': // Seconds since the epoch
-  case 'S': // Second of the minute [00-60]
-  case 'u': // ISO day of the week ([1-7] starting Monday)
-  case 'U': // Week of the year ([00-53] week 1 starts on first *Sunday*)
-  case 'V': // ISO week number ([01-53], 01 is first week majority in this year)
-  case 'w': // Day of week ([0-6] starting Sunday)
-  case 'W': // Week of the year ([00-53] week 1 starts on first *Monday*)
-  case 'y': // Year of the Century [00-99]
-  case 'Y': // Full year
-    return convert_int(writer, to_conv, timeptr);
-
-    // string conversions
-  case 'a': // Abbreviated weekday name
-  case 'A': // Full weekday name
-  case 'b': // Abbreviated month name
-  case 'B': // Full month name
-  case 'h': // same as %b
-  case 'p': // AM/PM designation
-    return convert_str(writer, to_conv, timeptr);
-
-    // composite conversions
-  case 'c': // locale specified date and time
-  case 'D': // %m/%d/%y (month/day/year)
-  case 'F': // %Y-%m-%d (year-month-day)
-  case 'r': // %I:%M:%S %p (hour:minute:second AM/PM)
-  case 'R': // %H:%M (hour:minute)
-  case 'T': // %H:%M:%S (hour:minute:second)
-  case 'x': // locale specified date
-  case 'X': // locale specified time
-    return convert_composite(writer, to_conv, timeptr);
-
-    // timezone conversions
-  case 'z': // Timezone offset (+/-hhmm) (num conv)
-  case 'Z': // Timezone name (string conv)
-    // the standard says if no time zone is determinable, write no characters.
-    // Leave this here until time zones are implemented.
-    return 0;
-  default:
-    return writer->write(to_conv.raw_string);
-  }
-  return 0;
-}
-
-} // namespace strftime_core
-} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/strftime_core/converter.h b/libc/src/time/strftime_core/converter.h
index 154ee38d9f05a..ff0faf35bbd90 100644
--- a/libc/src/time/strftime_core/converter.h
+++ b/libc/src/time/strftime_core/converter.h
@@ -10,17 +10,94 @@
 #define LLVM_LIBC_SRC_STDIO_STRFTIME_CORE_CONVERTER_H
 
 #include "hdr/types/struct_tm.h"
+#include "src/__support/macros/config.h"
 #include "src/stdio/printf_core/writer.h"
 #include "src/time/strftime_core/core_structs.h"
 
+#include "composite_converter.h"
+#include "num_converter.h"
+#include "str_converter.h"
+
 namespace LIBC_NAMESPACE_DECL {
 namespace strftime_core {
 
 // convert will call a conversion function to convert the FormatSection into
 // its string representation, and then that will write the result to the
 // writer.
-int convert(printf_core::Writer *writer, const FormatSection &to_conv,
-            const tm *timeptr);
+template <printf_core::WriteMode write_mode>
+int convert(printf_core::Writer<write_mode> *writer,
+            const FormatSection &to_conv, const tm *timeptr) {
+  // TODO: Implement the locale support.
+  // Currently locale flags are ignored, as described by the posix standard for
+  // the default locale.
+
+  if (!to_conv.has_conv)
+    return writer->write(to_conv.raw_string);
+  switch (to_conv.conv_name) {
+    // The cases are grouped by type, then alphabetized with lowercase before
+    // uppercase.
+
+    // raw conversions
+  case '%':
+    return writer->write("%");
+  case 'n':
+    return writer->write("\n");
+  case 't':
+    return writer->write("\t");
+
+    // numeric conversions
+  case 'C': // Century [00-99]
+  case 'd': // Day of the month [01-31]
+  case 'e': // Day of the month [1-31]
+  case 'g': // last 2 digits of ISO year [00-99]
+  case 'G': // ISO year
+  case 'H': // 24-hour format [00-23]
+  case 'I': // 12-hour format [01-12]
+  case 'j': // Day of the year [001-366]
+  case 'm': // Month of the year [01-12]
+  case 'M': // Minute of the hour [00-59]
+  case 's': // Seconds since the epoch
+  case 'S': // Second of the minute [00-60]
+  case 'u': // ISO day of the week ([1-7] starting Monday)
+  case 'U': // Week of the year ([00-53] week 1 starts on first *Sunday*)
+  case 'V': // ISO week number ([01-53], 01 is first week majority in this year)
+  case 'w': // Day of week ([0-6] starting Sunday)
+  case 'W': // Week of the year ([00-53] week 1 starts on first *Monday*)
+  case 'y': // Year of the Century [00-99]
+  case 'Y': // Full year
+    return convert_int(writer, to_conv, timeptr);
+
+    // string conversions
+  case 'a': // Abbreviated weekday name
+  case 'A': // Full weekday name
+  case 'b': // Abbreviated month name
+  case 'B': // Full month name
+  case 'h': // same as %b
+  case 'p': // AM/PM designation
+    return convert_str(writer, to_conv, timeptr);
+
+    // composite conversions
+  case 'c': // locale specified date and time
+  case 'D': // %m/%d/%y (month/day/year)
+  case 'F': // %Y-%m-%d (year-month-day)
+  case 'r': // %I:%M:%S %p (hour:minute:second AM/PM)
+  case 'R': // %H:%M (hour:minute)
+  case 'T': // %H:%M:%S (hour:minute:second)
+  case 'x': // locale specified date
+  case 'X': // locale specified time
+    return convert_composite(writer, to_conv, timeptr);
+
+    // timezone conversions
+  case 'z': // Timezone offset (+/-hhmm) (num conv)
+  case 'Z': // Timezone name (string conv)
+    // the standard says if no time zone is determinable, write no characters.
+    // Leave this here until time zones are implemented.
+    return 0;
+  default:
+    return writer->write(to_conv.raw_string);
+  }
+  return 0;
+}
 
 } // namespace strftime_core
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/strftime_core/num_converter.h b/libc/src/time/strftime_core/num_converter.h
index aef9ddbf791b2..7da9195aa4885 100644
--- a/libc/src/time/strftime_core/num_converter.h
+++ b/libc/src/time/strftime_core/num_converter.h
@@ -30,7 +30,8 @@ struct IntFormatSection {
   char padding_char = '0';
 };
 
-LIBC_INLINE int write_padded_int(printf_core::Writer *writer,
+template <printf_core::WriteMode write_mode>
+LIBC_INLINE int write_padded_int(printf_core::Writer<write_mode> *writer,
                                  const IntFormatSection &num_info) {
 
   DecFmt d(num_info.num);
@@ -187,7 +188,8 @@ LIBC_INLINE IntFormatSection get_int_format(const FormatSection &to_conv,
   return result;
 }
 
-LIBC_INLINE int convert_int(printf_core::Writer *writer,
+template <printf_core::WriteMode write_mode>
+LIBC_INLINE int convert_int(printf_core::Writer<write_mode> *writer,
                             const FormatSection &to_conv, const tm *timeptr) {
 
   return write_padded_int(writer, get_int_format(to_conv, timeptr));
diff --git a/libc/src/time/strftime_core/str_converter.h b/libc/src/time/strftime_core/str_converter.h
index f0d5bf59102f3..13eccd3979ea6 100644
--- a/libc/src/time/strftime_core/str_converter.h
+++ b/libc/src/time/strftime_core/str_converter.h
@@ -27,7 +27,8 @@ unwrap_opt(cpp::optional<cpp::string_view> str_opt) {
   return str_opt.has_value() ? *str_opt : OUT_OF_BOUNDS_STR;
 }
 
-LIBC_INLINE int convert_str(printf_core::Writer *writer,
+template <printf_core::WriteMode write_mode>
+LIBC_INLINE int convert_str(printf_core::Writer<write_mode> *writer,
                             const FormatSection &to_conv, const tm *timeptr) {
   cpp::string_view str;
   cpp::optional<cpp::string_view> str_opt;
diff --git a/libc/src/time/strftime_core/strftime_main.cpp b/libc/src/time/strftime_core/strftime_main.cpp
deleted file mode 100644
index 00839e5a3f4da..0000000000000
--- a/libc/src/time/strftime_core/strftime_main.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-//===-- Starting point for strftime ---------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "src/time/strftime_core/strftime_main.h"
-
-#include "hdr/types/struct_tm.h"
-#include "src/stdio/printf_core/writer.h"
-#include "src/time/strftime_core/converter.h"
-#include "src/time/strftime_core/core_structs.h"
-#include "src/time/strftime_core/parser.h"
-
-namespace LIBC_NAMESPACE_DECL {
-namespace strftime_core {
-
-int strftime_main(printf_core::Writer *writer, const char *__restrict str,
-                  const tm *timeptr) {
-  Parser parser(str);
-  int result = 0;
-  for (FormatSection cur_section = parser.get_next_section();
-       !cur_section.raw_string.empty();
-       cur_section = parser.get_next_section()) {
-    if (cur_section.has_conv)
-      result = convert(writer, cur_section, timeptr);
-    else
-      result = writer->write(cur_section.raw_string);
-
-    if (result < 0)
-      return result;
-  }
-
-  return writer->get_chars_written();
-}
-
-} // namespace strftime_core
-} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/strftime_core/strftime_main.h b/libc/src/time/strftime_core/strftime_main.h
index ae706828df44c..c7e590627094a 100644
--- a/libc/src/time/strftime_core/strftime_main.h
+++ b/libc/src/time/strftime_core/strftime_main.h
@@ -12,12 +12,32 @@
 #include "hdr/types/struct_tm.h"
 #include "src/__support/macros/config.h"
 #include "src/stdio/printf_core/writer.h"
+#include "src/time/strftime_core/converter.h"
+#include "src/time/strftime_core/core_structs.h"
+#include "src/time/strftime_core/parser.h"
 
 namespace LIBC_NAMESPACE_DECL {
 namespace strftime_core {
 
-int strftime_main(printf_core::Writer *writer, const char *__restrict str,
-                  const tm *timeptr);
+template <printf_core::WriteMode write_mode>
+int strftime_main(printf_core::Writer<write_mode> *writer,
+                  const char *__restrict str, const tm *timeptr) {
+  Parser parser(str);
+  int result = 0;
+  for (strftime_core::FormatSection cur_section = parser.get_next_section();
+       !cur_section.raw_string.empty();
+       cur_section = parser.get_next_section()) {
+    if (cur_section.has_conv)
+      result = convert(writer, cur_section, timeptr);
+    else
+      result = writer->write(cur_section.raw_string);
+
+    if (result < 0)
+      return result;
+  }
+
+  return writer->get_chars_written();
+}
 
 } // namespace strftime_core
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/strftime_l.cpp b/libc/src/time/strftime_l.cpp
index e19c7c605cd4a..201b85da39ee2 100644
--- a/libc/src/time/strftime_l.cpp
+++ b/libc/src/time/strftime_l.cpp
@@ -22,8 +22,10 @@ LLVM_LIBC_FUNCTION(size_t, strftime_l,
                    (char *__restrict buffer, size_t buffsz,
                     const char *__restrict format, const tm *timeptr,
                     locale_t)) {
-  printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
-  printf_core::Writer writer(&wb);
+  printf_core::WriteBuffer<printf_core::Mode<
+      printf_core::WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>::value>
+      wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
+  printf_core::Writer writer(wb);
   int ret = strftime_core::strftime_main(&writer, format, timeptr);
   if (buffsz > 0) // if the buffsz is 0 the buffer may be a null pointer.
     wb.buff[wb.buff_cur] = '\0';
diff --git a/libc/test/src/stdio/printf_core/converter_test.cpp b/libc/test/src/stdio/printf_core/converter_test.cpp
index 9da749f3b8ad1..96a00ae598ec2 100644
--- a/libc/test/src/stdio/printf_core/converter_test.cpp
+++ b/libc/test/src/stdio/printf_core/converter_test.cpp
@@ -18,10 +18,14 @@ class LlvmLibcPrintfConverterTest : public LIBC_NAMESPACE::testing::Test {
   // void TearDown() override {}
 
   char str[60];
-  LIBC_NAMESPACE::printf_core::WriteBuffer wb =
-      LIBC_NAMESPACE::printf_core::WriteBuffer(str, sizeof(str) - 1);
-  LIBC_NAMESPACE::printf_core::Writer writer =
-      LIBC_NAMESPACE::printf_core::Writer(&wb);
+  LIBC_NAMESPACE::printf_core::WriteBuffer<
+      LIBC_NAMESPACE::printf_core::WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>
+      wb = LIBC_NAMESPACE::printf_core::WriteBuffer<
+          LIBC_NAMESPACE::printf_core::WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>(
+          str, sizeof(str) - 1);
+  LIBC_NAMESPACE::printf_core::Writer<
+      LIBC_NAMESPACE::printf_core::WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>
+      writer = LIBC_NAMESPACE::printf_core::Writer(wb);
 };
 
 TEST_F(LlvmLibcPrintfConverterTest, SimpleRawConversion) {
diff --git a/libc/test/src/stdio/printf_core/writer_test.cpp b/libc/test/src/stdio/printf_core/writer_test.cpp
index 4fe5ffb4aa989..8611caa2dfa58 100644
--- a/libc/test/src/stdio/printf_core/writer_test.cpp
+++ b/libc/test/src/stdio/printf_core/writer_test.cpp
@@ -15,19 +15,20 @@
 
 using LIBC_NAMESPACE::cpp::string_view;
 using LIBC_NAMESPACE::printf_core::WriteBuffer;
+using LIBC_NAMESPACE::printf_core::WriteMode;
 using LIBC_NAMESPACE::printf_core::Writer;
 
 TEST(LlvmLibcPrintfWriterTest, Constructor) {
   char str[10];
-  WriteBuffer wb(str, sizeof(str) - 1);
-  Writer writer(&wb);
+  WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(str, sizeof(str) - 1);
+  Writer writer(wb);
   (void)writer;
 }
 
 TEST(LlvmLibcPrintfWriterTest, Write) {
   char str[4] = {'D', 'E', 'F', 'G'};
-  WriteBuffer wb(str, sizeof(str) - 1);
-  Writer writer(&wb);
+  WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(str, sizeof(str) - 1);
+  Writer writer(wb);
   writer.write({"abc", 3});
 
   EXPECT_EQ(str[3], 'G');
@@ -42,8 +43,8 @@ TEST(LlvmLibcPrintfWriterTest, Write) {
 
 TEST(LlvmLibcPrintfWriterTest, WriteMultipleTimes) {
   char str[10];
-  WriteBuffer wb(str, sizeof(str) - 1);
-  Writer writer(&wb);
+  WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(str, sizeof(str) - 1);
+  Writer writer(wb);
   writer.write({"abc", 3});
   writer.write({"DEF", 3});
   writer.write({"1234", 3});
@@ -56,8 +57,8 @@ TEST(LlvmLibcPrintfWriterTest, WriteMultipleTimes) {
 
 TEST(LlvmLibcPrintfWriterTest, WriteChars) {
   char str[4] = {'D', 'E', 'F', 'G'};
-  WriteBuffer wb(str, sizeof(str) - 1);
-  Writer writer(&wb);
+  WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(str, sizeof(str) - 1);
+  Writer writer(wb);
   writer.write('a', 3);
 
   EXPECT_EQ(str[3], 'G');
@@ -69,8 +70,8 @@ TEST(LlvmLibcPrintfWriterTest, WriteChars) {
 
 TEST(LlvmLibcPrintfWriterTest, WriteCharsMultipleTimes) {
   char str[10];
-  WriteBuffer wb(str, sizeof(str) - 1);
-  Writer writer(&wb);
+  WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(str, sizeof(str) - 1);
+  Writer writer(wb);
   writer.write('a', 3);
   writer.write('D', 3);
   writer.write('1', 3);
@@ -83,8 +84,8 @@ TEST(LlvmLibcPrintfWriterTest, WriteCharsMultipleTimes) {
 
 TEST(LlvmLibcPrintfWriterTest, WriteManyChars) {
   char str[100];
-  WriteBuffer wb(str, sizeof(str) - 1);
-  Writer writer(&wb);
+  WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(str, sizeof(str) - 1);
+  Writer writer(wb);
   writer.write('Z', 99);
 
   wb.buff[wb.buff_cur] = '\0';
@@ -105,8 +106,8 @@ TEST(LlvmLibcPrintfWriterTest, WriteManyChars) {
 
 TEST(LlvmLibcPrintfWriterTest, MixedWrites) {
   char str[13];
-  WriteBuffer wb(str, sizeof(str) - 1);
-  Writer writer(&wb);
+  WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(str, sizeof(str) - 1);
+  Writer writer(wb);
   writer.write('a', 3);
   writer.write({"DEF", 3});
   writer.write('1', 3);
@@ -120,8 +121,8 @@ TEST(LlvmLibcPrintfWriterTest, MixedWrites) {
 
 TEST(LlvmLibcPrintfWriterTest, WriteWithMaxLength) {
   char str[11];
-  WriteBuffer wb(str, sizeof(str) - 1);
-  Writer writer(&wb);
+  WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(str, sizeof(str) - 1);
+  Writer writer(wb);
   writer.write({"abcDEF123456", 12});
 
   wb.buff[wb.buff_cur] = '\0';
@@ -132,8 +133,8 @@ TEST(LlvmLibcPrintfWriterTest, WriteWithMaxLength) {
 
 TEST(LlvmLibcPrintfWriterTest, WriteCharsWithMaxLength) {
   char str[11];
-  WriteBuffer wb(str, sizeof(str) - 1);
-  Writer writer(&wb);
+  WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(str, sizeof(str) - 1);
+  Writer writer(wb);
   writer.write('1', 15);
 
   wb.buff[wb.buff_cur] = '\0';
@@ -144,9 +145,9 @@ TEST(LlvmLibcPrintfWriterTest, WriteCharsWithMaxLength) {
 
 TEST(LlvmLibcPrintfWriterTest, MixedWriteWithMaxLength) {
   char str[11];
-  WriteBuffer wb(str, sizeof(str) - 1);
+  WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(str, sizeof(str) - 1);
 
-  Writer writer(&wb);
+  Writer writer(wb);
   writer.write('a', 3);
   writer.write({"DEF", 3});
   writer.write('1', 3);
@@ -162,9 +163,9 @@ TEST(LlvmLibcPrintfWriterTest, StringWithMaxLengthOne) {
   char str[1];
   // This is because the max length should be at most 1 less than the size of
   // the buffer it's writing to.
-  WriteBuffer wb(str, 0);
+  WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(str, 0);
 
-  Writer writer(&wb);
+  Writer writer(wb);
   writer.write('a', 3);
   writer.write({"DEF", 3});
   writer.write('1', 3);
@@ -177,9 +178,9 @@ TEST(LlvmLibcPrintfWriterTest, StringWithMaxLengthOne) {
 }
 
 TEST(LlvmLibcPrintfWriterTest, NullStringWithZeroMaxLength) {
-  WriteBuffer wb(nullptr, 0);
+  WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(nullptr, 0);
 
-  Writer writer(&wb);
+  Writer writer(wb);
   writer.write('a', 3);
   writer.write({"DEF", 3});
   writer.write('1', 3);
@@ -213,9 +214,10 @@ TEST(LlvmLibcPrintfWriterTest, WriteWithMaxLengthWithCallback) {
   OutBuff out_buff = {str, 0};
 
   char wb_buff[8];
-  WriteBuffer wb(wb_buff, sizeof(wb_buff), &copy_to_out,
-                 reinterpret_cast<void *>(&out_buff));
-  Writer writer(&wb);
+  WriteBuffer<WriteMode::FLUSH_TO_STREAM> wb(
+      wb_buff, sizeof(wb_buff), &copy_to_out,
+      reinterpret_cast<void *>(&out_buff));
+  Writer writer(wb);
   writer.write({"abcDEF123456", 12});
 
   // Flush the buffer
@@ -232,9 +234,10 @@ TEST(LlvmLibcPrintfWriterTest, WriteCharsWithMaxLengthWithCallback) {
   OutBuff out_buff = {str, 0};
 
   char wb_buff[8];
-  WriteBuffer wb(wb_buff, sizeof(wb_buff), &copy_to_out,
-                 reinterpret_cast<void *>(&out_buff));
-  Writer writer(&wb);
+  WriteBuffer<WriteMode::FLUSH_TO_STREAM> wb(
+      wb_buff, sizeof(wb_buff), &copy_to_out,
+      reinterpret_cast<void *>(&out_buff));
+  Writer writer(wb);
   writer.write('1', 15);
 
   // Flush the buffer
@@ -251,9 +254,10 @@ TEST(LlvmLibcPrintfWriterTest, MixedWriteWithMaxLengthWithCallback) {
   OutBuff out_buff = {str, 0};
 
   char wb_buff[8];
-  WriteBuffer wb(wb_buff, sizeof(wb_buff), &copy_to_out,
-                 reinterpret_cast<void *>(&out_buff));
-  Writer writer(&wb);
+  WriteBuffer<WriteMode::FLUSH_TO_STREAM> wb(
+      wb_buff, sizeof(wb_buff), &copy_to_out,
+      reinterpret_cast<void *>(&out_buff));
+  Writer writer(wb);
   writer.write('a', 3);
   writer.write({"DEF", 3});
   writer.write('1', 3);
@@ -273,9 +277,10 @@ TEST(LlvmLibcPrintfWriterTest, ZeroLengthBufferWithCallback) {
   OutBuff out_buff = {str, 0};
 
   char wb_buff[1];
-  WriteBuffer wb(wb_buff, 0, &copy_to_out, reinterpret_cast<void *>(&out_buff));
+  WriteBuffer<WriteMode::FLUSH_TO_STREAM> wb(
+      wb_buff, 0, &copy_to_out, reinterpret_cast<void *>(&out_buff));
 
-  Writer writer(&wb);
+  Writer writer(wb);
   writer.write('a', 3);
   writer.write({"DEF", 3});
   writer.write('1', 3);
@@ -294,9 +299,10 @@ TEST(LlvmLibcPrintfWriterTest, NullStringWithZeroMaxLengthWithCallback) {
 
   OutBuff out_buff = {str, 0};
 
-  WriteBuffer wb(nullptr, 0, &copy_to_out, reinterpret_cast<void *>(&out_buff));
+  WriteBuffer<WriteMode::FLUSH_TO_STREAM> wb(
+      nullptr, 0, &copy_to_out, reinterpret_cast<void *>(&out_buff));
 
-  Writer writer(&wb);
+  Writer writer(wb);
   writer.write('a', 3);
   writer.write({"DEF", 3});
   writer.write('1', 3);
diff --git a/libc/utils/gpu/server/CMakeLists.txt b/libc/utils/gpu/server/CMakeLists.txt
index ae8a0d902f459..7ca101e42a0af 100644
--- a/libc/utils/gpu/server/CMakeLists.txt
+++ b/libc/utils/gpu/server/CMakeLists.txt
@@ -1,8 +1,4 @@
-add_library(llvmlibc_rpc_server STATIC
-  ${LIBC_SOURCE_DIR}/src/stdio/printf_core/writer.cpp
-  ${LIBC_SOURCE_DIR}/src/stdio/printf_core/converter.cpp
-  rpc_server.cpp
-)
+add_library(llvmlibc_rpc_server STATIC rpc_server.cpp)
 
 # Include the RPC implemenation from libc.
 target_include_directories(llvmlibc_rpc_server PRIVATE ${LIBC_SOURCE_DIR})
diff --git a/libc/utils/gpu/server/rpc_server.cpp b/libc/utils/gpu/server/rpc_server.cpp
index 1faee531fa20d..6dc81a5ff805d 100644
--- a/libc/utils/gpu/server/rpc_server.cpp
+++ b/libc/utils/gpu/server/rpc_server.cpp
@@ -96,8 +96,8 @@ static void handle_printf(rpc::Server::Port &port, TempStorage &temp_storage) {
     if (!format[lane])
       continue;
 
-    WriteBuffer wb(nullptr, 0);
-    Writer writer(&wb);
+    WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(nullptr, 0);
+    Writer writer(wb);
 
     internal::DummyArgList<packed> printf_args;
     Parser<internal::DummyArgList<packed> &> parser(
@@ -123,8 +123,8 @@ static void handle_printf(rpc::Server::Port &port, TempStorage &temp_storage) {
     if (!format[lane])
       continue;
 
-    WriteBuffer wb(nullptr, 0);
-    Writer writer(&wb);
+    WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(nullptr, 0);
+    Writer writer(wb);
 
     internal::StructArgList<packed> printf_args(args[lane], args_sizes[lane]);
     Parser<internal::StructArgList<packed>> parser(
@@ -180,8 +180,9 @@ static void handle_printf(rpc::Server::Port &port, TempStorage &temp_storage) {
       continue;
 
     char *buffer = temp_storage.alloc(buffer_size[lane]);
-    WriteBuffer wb(buffer, buffer_size[lane]);
-    Writer writer(&wb);
+    WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> wb(buffer,
+                                                           buffer_size[lane]);
+    Writer writer(wb);
 
     internal::StructArgList<packed> printf_args(args[lane], args_sizes[lane]);
     Parser<internal::StructArgList<packed>> parser(



More information about the libc-commits mailing list