[clang] [libc] [libc] Modular printf option (float only) (PR #147426)

Daniel Thornburgh via cfe-commits cfe-commits at lists.llvm.org
Thu Feb 5 14:56:01 PST 2026


https://github.com/mysterymath updated https://github.com/llvm/llvm-project/pull/147426

>From f58a20d34997107a2849c0c2111d850341f82fdf Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Thu, 19 Dec 2024 11:57:27 -0800
Subject: [PATCH 1/8] [libc] Modular printf option (float only)

This adds LIBC_CONF_PRINTF_MODULAR, which causes floating point support
(later, others) to be weakly linked into the implementation.
__printf_modular becomes the main entry point of the implementaiton, an
printf itself wraps __printf_modular. printf it also contains a
BFD_RELOC_NONE relocation to bring in the float aspect.

See issue #146159 for context.
---
 libc/config/config.json                       |  4 ++
 libc/docs/configure.rst                       |  1 +
 libc/include/CMakeLists.txt                   |  1 +
 libc/include/llvm-libc-macros/CMakeLists.txt  | 22 ++++++-
 .../_LIBC_MODULAR_FORMAT_PRINTF-disable.h     | 14 +++++
 .../_LIBC_MODULAR_FORMAT_PRINTF.h             | 15 +++++
 libc/include/stdio.yaml                       | 16 +++++
 libc/src/stdio/CMakeLists.txt                 | 40 +++++++++++--
 libc/src/stdio/asprintf.cpp                   |  5 ++
 libc/src/stdio/asprintf.h                     |  1 +
 libc/src/stdio/asprintf_modular.cpp           | 43 ++++++++++++++
 libc/src/stdio/baremetal/CMakeLists.txt       | 12 +++-
 libc/src/stdio/baremetal/printf.cpp           |  5 ++
 libc/src/stdio/baremetal/printf_modular.cpp   | 32 ++++++++++
 libc/src/stdio/baremetal/vfprintf.cpp         |  5 ++
 libc/src/stdio/baremetal/vfprintf_internal.h  |  5 +-
 libc/src/stdio/baremetal/vprintf.cpp          |  5 ++
 libc/src/stdio/baremetal/vprintf_modular.cpp  | 30 ++++++++++
 libc/src/stdio/generic/CMakeLists.txt         |  4 ++
 libc/src/stdio/gpu/CMakeLists.txt             |  4 ++
 libc/src/stdio/printf.h                       |  1 +
 libc/src/stdio/printf_core/CMakeLists.txt     |  7 ++-
 .../stdio/printf_core/float_dec_converter.h   | 13 +++++
 .../printf_core/float_dec_converter_limited.h | 13 +++++
 .../stdio/printf_core/float_hex_converter.h   |  6 ++
 libc/src/stdio/printf_core/float_impl.cpp     | 56 ++++++++++++++++++
 libc/src/stdio/printf_core/parser.h           | 58 ++++++++++++++-----
 libc/src/stdio/printf_core/printf_config.h    |  7 +++
 libc/src/stdio/printf_core/printf_main.h      | 16 ++++-
 .../stdio/printf_core/vasprintf_internal.h    |  5 +-
 .../src/stdio/printf_core/vfprintf_internal.h | 18 ++++--
 libc/src/stdio/snprintf.cpp                   |  5 ++
 libc/src/stdio/snprintf.h                     |  2 +
 libc/src/stdio/snprintf_modular.cpp           | 54 +++++++++++++++++
 libc/src/stdio/sprintf.cpp                    |  5 ++
 libc/src/stdio/sprintf.h                      |  2 +
 libc/src/stdio/sprintf_modular.cpp            | 54 +++++++++++++++++
 libc/src/stdio/vasprintf.cpp                  |  5 ++
 libc/src/stdio/vasprintf.h                    |  2 +
 libc/src/stdio/vasprintf_modular.cpp          | 38 ++++++++++++
 libc/src/stdio/vprintf.h                      |  1 +
 libc/src/stdio/vsnprintf.cpp                  |  5 ++
 libc/src/stdio/vsnprintf.h                    |  2 +
 libc/src/stdio/vsnprintf_modular.cpp          | 51 ++++++++++++++++
 libc/src/stdio/vsprintf.cpp                   |  5 ++
 libc/src/stdio/vsprintf.h                     |  2 +
 libc/src/stdio/vsprintf_modular.cpp           | 50 ++++++++++++++++
 libc/utils/hdrgen/hdrgen/header.py            |  2 +-
 .../tests/expected_output/test_header.h       |  3 +-
 .../tests/expected_output/test_small.json     |  1 +
 libc/utils/hdrgen/tests/input/merge1.yaml     |  1 +
 51 files changed, 720 insertions(+), 34 deletions(-)
 create mode 100644 libc/include/llvm-libc-macros/_LIBC_MODULAR_FORMAT_PRINTF-disable.h
 create mode 100644 libc/include/llvm-libc-macros/_LIBC_MODULAR_FORMAT_PRINTF.h
 create mode 100644 libc/src/stdio/asprintf_modular.cpp
 create mode 100644 libc/src/stdio/baremetal/printf_modular.cpp
 create mode 100644 libc/src/stdio/baremetal/vprintf_modular.cpp
 create mode 100644 libc/src/stdio/printf_core/float_impl.cpp
 create mode 100644 libc/src/stdio/snprintf_modular.cpp
 create mode 100644 libc/src/stdio/sprintf_modular.cpp
 create mode 100644 libc/src/stdio/vasprintf_modular.cpp
 create mode 100644 libc/src/stdio/vsnprintf_modular.cpp
 create mode 100644 libc/src/stdio/vsprintf_modular.cpp

diff --git a/libc/config/config.json b/libc/config/config.json
index b37154e294ec9..3d0cd2b64ad61 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -56,6 +56,10 @@
     "LIBC_CONF_PRINTF_DISABLE_WIDE": {
       "value": false,
       "doc": "Disable handling wide characters for printf and friends."
+    },
+    "LIBC_CONF_PRINTF_MODULAR": {
+      "value": false,
+      "doc": "Split printf implementation into modules that can be lazily linked in."
     }
   },
   "scanf": {
diff --git a/libc/docs/configure.rst b/libc/docs/configure.rst
index 0813ab3554216..524d2baafa3fc 100644
--- a/libc/docs/configure.rst
+++ b/libc/docs/configure.rst
@@ -49,6 +49,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_MODULAR``: Split printf implementation into modules that can be lazily linked in.
     - ``LIBC_CONF_PRINTF_RUNTIME_DISPATCH``: Use dynamic dispatch for the output mechanism 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).
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index c6254aafe3849..ca59c548d6948 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -359,6 +359,7 @@ add_header_macro(
   ../libc/include/stdio.yaml
   stdio.h
   DEPENDS
+    .llvm-libc-macros._LIBC_MODULAR_FORMAT_PRINTF
     .llvm-libc-macros.file_seek_macros
     .llvm-libc-macros.null_macro
     .llvm-libc-macros.stdio_macros
diff --git a/libc/include/llvm-libc-macros/CMakeLists.txt b/libc/include/llvm-libc-macros/CMakeLists.txt
index b16337cccd58b..120277edcb3a7 100644
--- a/libc/include/llvm-libc-macros/CMakeLists.txt
+++ b/libc/include/llvm-libc-macros/CMakeLists.txt
@@ -3,15 +3,19 @@ function(add_macro_header name)
   cmake_parse_arguments(
     "MACRO_HEADER"
     "" # Optional arguments
-    "HDR" # Single value arguments
+    "HDR;DEST_HDR" # Single value arguments
     "DEPENDS" # Multi-value arguments
     ${ARGN}
   )
+  if (MACRO_HEADER_DEST_HDR)
+    set(dest_header_arg DEST_HDR ${MACRO_HEADER_DEST_HDR})
+  endif()
   if(TARGET libc.include.llvm-libc-macros.${LIBC_TARGET_OS}.${name})
     add_header(
       ${name}
       HDR
         ${MACRO_HEADER_HDR}
+      ${dest_header_arg}
       DEPENDS
         .${LIBC_TARGET_OS}.${name}
         ${MACRO_HEADER_DEPENDS}
@@ -21,6 +25,7 @@ function(add_macro_header name)
       ${name}
       HDR
         ${MACRO_HEADER_HDR}
+      ${dest_header_arg}
       DEPENDS
         ${MACRO_HEADER_DEPENDS}
     )
@@ -383,3 +388,18 @@ add_macro_header(
     sysexits-macros.h
 )
 
+if (LIBC_CONF_MODULAR_FORMAT)
+  add_macro_header(
+    _LIBC_MODULAR_FORMAT_PRINTF
+    HDR
+      _LIBC_MODULAR_FORMAT_PRINTF.h
+  )
+else()
+  add_macro_header(
+    _LIBC_MODULAR_FORMAT_PRINTF
+    HDR
+      _LIBC_MODULAR_FORMAT_PRINTF-disable.h
+      DEST_HDR
+      _LIBC_MODULAR_FORMAT_PRINTF.h
+  )
+endif()
diff --git a/libc/include/llvm-libc-macros/_LIBC_MODULAR_FORMAT_PRINTF-disable.h b/libc/include/llvm-libc-macros/_LIBC_MODULAR_FORMAT_PRINTF-disable.h
new file mode 100644
index 0000000000000..e3238161b3808
--- /dev/null
+++ b/libc/include/llvm-libc-macros/_LIBC_MODULAR_FORMAT_PRINTF-disable.h
@@ -0,0 +1,14 @@
+//===-- Definition to disable modular format macro for printf -------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_MACROS_LIBC_MODULAR_FORMAT_PRINTF_H
+#define LLVM_LIBC_MACROS_LIBC_MODULAR_FORMAT_PRINTF_H
+
+#define _LIBC_MODULAR_FORMAT_PRINTF(MODULAR_IMPL_FN)
+
+#endif // LLVM_LIBC_MACROS_LIBC_MODULAR_FORMAT_PRINTF_H
diff --git a/libc/include/llvm-libc-macros/_LIBC_MODULAR_FORMAT_PRINTF.h b/libc/include/llvm-libc-macros/_LIBC_MODULAR_FORMAT_PRINTF.h
new file mode 100644
index 0000000000000..918241ab8f2ec
--- /dev/null
+++ b/libc/include/llvm-libc-macros/_LIBC_MODULAR_FORMAT_PRINTF.h
@@ -0,0 +1,15 @@
+//===-- Definition of modular format macro for printf ---------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_MACROS_LIBC_MODULAR_FORMAT_PRINTF_H
+#define LLVM_LIBC_MACROS_LIBC_MODULAR_FORMAT_PRINTF_H
+
+#define _LIBC_MODULAR_FORMAT_PRINTF(MODULAR_IMPL_FN)                           \
+  __attribute__((modular_format(MODULAR_IMPL_FN, "__printf", "float")))
+
+#endif // LLVM_LIBC_MACROS_LIBC_MODULAR_FORMAT_PRINTF_H
diff --git a/libc/include/stdio.yaml b/libc/include/stdio.yaml
index c50b4ecb0bf08..67eeeaf07bdd1 100644
--- a/libc/include/stdio.yaml
+++ b/libc/include/stdio.yaml
@@ -31,6 +31,8 @@ functions:
       - type: char **__restrict
       - type: const char *__restrict
       - type: '...'
+    attributes:
+      - _LIBC_MODULAR_FORMAT_PRINTF(__asprintf_modular)
   - name: clearerr
     standards:
       - stdc
@@ -276,6 +278,8 @@ functions:
     arguments:
       - type: const char *__restrict
       - type: '...'
+    attributes:
+      - _LIBC_MODULAR_FORMAT_PRINTF(__printf_modular)
   - name: putc
     standards:
       - stdc
@@ -347,6 +351,8 @@ functions:
       - type: size_t
       - type: const char *__restrict
       - type: '...'
+    attributes:
+      - _LIBC_MODULAR_FORMAT_PRINTF(__snprintf_modular)
   - name: sprintf
     standards:
       - stdc
@@ -355,6 +361,8 @@ functions:
       - type: char *__restrict
       - type: const char *__restrict
       - type: '...'
+    attributes:
+      - _LIBC_MODULAR_FORMAT_PRINTF(__sprintf_modular)
   - name: sscanf
     standards:
       - stdc
@@ -378,6 +386,8 @@ functions:
       - type: char **__restrict
       - type: const char *__restrict
       - type: va_list
+    attributes:
+      - _LIBC_MODULAR_FORMAT_PRINTF(__vasprintf_modular)
   - name: vfprintf
     standards:
       - stdc
@@ -393,6 +403,8 @@ functions:
     arguments:
       - type: const char *__restrict
       - type: va_list
+    attributes:
+      - _LIBC_MODULAR_FORMAT_PRINTF(__vprintf_modular)
   - name: vsnprintf
     standards:
       - stdc
@@ -402,6 +414,8 @@ functions:
       - type: size_t
       - type: const char *__restrict
       - type: va_list
+    attributes:
+      - _LIBC_MODULAR_FORMAT_PRINTF(__vsnprintf_modular)
   - name: vsprintf
     standards:
       - stdc
@@ -410,6 +424,8 @@ functions:
       - type: char *__restrict
       - type: const char *__restrict
       - type: va_list
+    attributes:
+      - _LIBC_MODULAR_FORMAT_PRINTF(__vsprintf_modular)
   - name: vsscanf
     standards:
       - stdc
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index c75c8b11be2b5..9486a53499792 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -116,10 +116,15 @@ add_entrypoint_object(
     libc.src.stdio.scanf_core.string_reader
 )
 
+set(sprintf_srcs sprintf.cpp)
+if(LIBC_CONF_PRINTF_MODULAR)
+  list(APPEND sprintf_srcs sprintf_modular.cpp)
+endif()
+
 add_entrypoint_object(
   sprintf
   SRCS
-    sprintf.cpp
+    ${sprintf_srcs}
   HDRS
     sprintf.h
   DEPENDS
@@ -131,10 +136,14 @@ add_entrypoint_object(
     libc.src.__support.CPP.limits
 )
 
+set(snprintf_srcs snprintf.cpp)
+if(LIBC_CONF_PRINTF_MODULAR)
+  list(APPEND snprintf_srcs snprintf_modular.cpp)
+endif()
 add_entrypoint_object(
   snprintf
   SRCS
-    snprintf.cpp
+    ${snprintf_srcs}
   HDRS
     snprintf.h
   DEPENDS
@@ -146,10 +155,14 @@ add_entrypoint_object(
     libc.src.__support.CPP.limits
 )
 
+set(asprintf_srcs asprintf.cpp)
+if(LIBC_CONF_PRINTF_MODULAR)
+  list(APPEND asprintf_srcs asprintf_modular.cpp)
+endif()
 add_entrypoint_object(
   asprintf
   SRCS
-    asprintf.cpp
+    ${asprintf_srcs}
   HDRS
     asprintf.h
   DEPENDS
@@ -160,10 +173,15 @@ add_entrypoint_object(
     libc.src.__support.CPP.limits
 )
 
+set(vsprintf_srcs vsprintf.cpp)
+if(LIBC_CONF_PRINTF_MODULAR)
+  list(APPEND vsprintf_srcs vsprintf_modular.cpp)
+endif()
+
 add_entrypoint_object(
   vsprintf
   SRCS
-    vsprintf.cpp
+    ${vsprintf_srcs}
   HDRS
     vsprintf.h
   DEPENDS
@@ -175,10 +193,15 @@ add_entrypoint_object(
     libc.src.__support.CPP.limits
 )
 
+set(vsnprintf_srcs vsnprintf.cpp)
+if(LIBC_CONF_PRINTF_MODULAR)
+  list(APPEND vsnprintf_srcs vsnprintf_modular.cpp)
+endif()
+
 add_entrypoint_object(
   vsnprintf
   SRCS
-    vsnprintf.cpp
+    ${vsnprintf_srcs}
   HDRS
     vsnprintf.h
   DEPENDS
@@ -190,10 +213,15 @@ add_entrypoint_object(
     libc.src.__support.CPP.limits
 )
 
+set(vasprintf_srcs vasprintf.cpp)
+if(LIBC_CONF_PRINTF_MODULAR)
+  list(APPEND vasprintf_srcs vasprintf_modular.cpp)
+endif()
+
 add_entrypoint_object(
   vasprintf
   SRCS
-    vasprintf.cpp
+    ${vasprintf_srcs}
   HDRS
     vasprintf.h
   DEPENDS
diff --git a/libc/src/stdio/asprintf.cpp b/libc/src/stdio/asprintf.cpp
index 0991dfca6a059..f80d2a5eb759d 100644
--- a/libc/src/stdio/asprintf.cpp
+++ b/libc/src/stdio/asprintf.cpp
@@ -26,7 +26,12 @@ LLVM_LIBC_FUNCTION(int, asprintf,
                                  // and pointer semantics, as well as handling
                                  // destruction automatically.
   va_end(vlist);
+#ifdef LIBC_COPT_PRINTF_MODULAR
+  LIBC_INLINE_ASM(".reloc ., BFD_RELOC_NONE, __printf_float");
+  auto ret_val = printf_core::vasprintf_internal<true>(buffer, format, args);
+#else
   auto ret_val = printf_core::vasprintf_internal(buffer, format, args);
+#endif
   if (!ret_val.has_value()) {
     libc_errno = printf_core::internal_error_to_errno(ret_val.error());
     return -1;
diff --git a/libc/src/stdio/asprintf.h b/libc/src/stdio/asprintf.h
index 168721c4f98b9..26a1f1687aedd 100644
--- a/libc/src/stdio/asprintf.h
+++ b/libc/src/stdio/asprintf.h
@@ -14,6 +14,7 @@
 namespace LIBC_NAMESPACE_DECL {
 
 int asprintf(char **__restrict s, const char *__restrict format, ...);
+int __asprintf_modular(char **__restrict s, const char *__restrict format, ...);
 
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/stdio/asprintf_modular.cpp b/libc/src/stdio/asprintf_modular.cpp
new file mode 100644
index 0000000000000..74b62cc0c2501
--- /dev/null
+++ b/libc/src/stdio/asprintf_modular.cpp
@@ -0,0 +1,43 @@
+//===-- Implementation of asprintf_modular ----------------------*- 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/__support/CPP/limits.h"
+#include "src/__support/arg_list.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/asprintf.h"
+#include "src/stdio/printf_core/core_structs.h"
+#include "src/stdio/printf_core/error_mapper.h"
+#include "src/stdio/printf_core/vasprintf_internal.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, __asprintf_modular,
+                   (char **__restrict buffer, const char *__restrict format,
+                    ...)) {
+  va_list vlist;
+  va_start(vlist, format);
+  internal::ArgList args(vlist); // This holder class allows for easier copying
+                                 // and pointer semantics, as well as handling
+                                 // destruction automatically.
+  va_end(vlist);
+  auto ret_val = printf_core::vasprintf_internal<true>(buffer, format, args);
+  if (!ret_val.has_value()) {
+    libc_errno = printf_core::internal_error_to_errno(ret_val.error());
+    return -1;
+  }
+  if (ret_val.value() > static_cast<size_t>(cpp::numeric_limits<int>::max())) {
+    libc_errno =
+        printf_core::internal_error_to_errno(-printf_core::OVERFLOW_ERROR);
+    return -1;
+  }
+
+  return static_cast<int>(ret_val.value());
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/baremetal/CMakeLists.txt b/libc/src/stdio/baremetal/CMakeLists.txt
index a706accecf152..878aca8ca0fc2 100644
--- a/libc/src/stdio/baremetal/CMakeLists.txt
+++ b/libc/src/stdio/baremetal/CMakeLists.txt
@@ -186,10 +186,14 @@ add_entrypoint_object(
     libc.include.stdio
 )
 
+set(printf_srcs printf.cpp)
+if(LIBC_CONF_PRINTF_MODULAR)
+  list(APPEND printf_srcs printf_modular.cpp)
+endif()
 add_entrypoint_object(
   printf
   SRCS
-    printf.cpp
+    ${printf_srcs}
   HDRS
     ../printf.h
   DEPENDS
@@ -274,10 +278,14 @@ add_entrypoint_object(
     libc.src.__support.arg_list
 )
 
+set(vprintf_srcs vprintf.cpp)
+if(LIBC_CONF_PRINTF_MODULAR)
+  list(APPEND vprintf_srcs vprintf_modular.cpp)
+endif()
 add_entrypoint_object(
   vprintf
   SRCS
-    vprintf.cpp
+    ${vprintf_srcs}
   HDRS
     ../vprintf.h
   DEPENDS
diff --git a/libc/src/stdio/baremetal/printf.cpp b/libc/src/stdio/baremetal/printf.cpp
index 5010810906e24..b43bc1e92528f 100644
--- a/libc/src/stdio/baremetal/printf.cpp
+++ b/libc/src/stdio/baremetal/printf.cpp
@@ -26,7 +26,12 @@ LLVM_LIBC_FUNCTION(int, printf, (const char *__restrict format, ...)) {
                                  // destruction automatically.
   va_end(vlist);
 
+#ifdef LIBC_COPT_PRINTF_MODULAR
+  LIBC_INLINE_ASM(".reloc ., BFD_RELOC_NONE, __printf_float");
+  return vfprintf_internal<true>(stdout, format, args);
+#else
   return vfprintf_internal(stdout, format, args);
+#endif
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/baremetal/printf_modular.cpp b/libc/src/stdio/baremetal/printf_modular.cpp
new file mode 100644
index 0000000000000..f03060a9c3ff5
--- /dev/null
+++ b/libc/src/stdio/baremetal/printf_modular.cpp
@@ -0,0 +1,32 @@
+//===-- Implementation of printf_modular for baremetal ----------*- 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.h"
+
+#include "hdr/stdio_macros.h"
+#include "src/__support/arg_list.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/baremetal/vfprintf_internal.h"
+
+#include <stdarg.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, __printf_modular, (const char *__restrict format, ...)) {
+  va_list vlist;
+  va_start(vlist, format);
+  internal::ArgList args(vlist); // This holder class allows for easier copying
+                                 // and pointer semantics, as well as handling
+                                 // destruction automatically.
+  va_end(vlist);
+
+  return vfprintf_internal<true>(stdout, format, args);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/baremetal/vfprintf.cpp b/libc/src/stdio/baremetal/vfprintf.cpp
index 2393ca4bcdb3e..c068a10181911 100644
--- a/libc/src/stdio/baremetal/vfprintf.cpp
+++ b/libc/src/stdio/baremetal/vfprintf.cpp
@@ -25,7 +25,12 @@ LLVM_LIBC_FUNCTION(int, vfprintf,
                                  // and pointer semantics, as well as handling
                                  // destruction automatically.
 
+#ifdef LIBC_COPT_PRINTF_MODULAR
+  LIBC_INLINE_ASM(".reloc ., BFD_RELOC_NONE, __printf_float");
+  return vfprintf_internal<true>(stream, format, args);
+#else
   return vfprintf_internal(stream, format, args);
+#endif
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/baremetal/vfprintf_internal.h b/libc/src/stdio/baremetal/vfprintf_internal.h
index d38a56481bc37..50eaef4a4a3e2 100644
--- a/libc/src/stdio/baremetal/vfprintf_internal.h
+++ b/libc/src/stdio/baremetal/vfprintf_internal.h
@@ -38,6 +38,7 @@ LIBC_INLINE int write_hook(cpp::string_view str_view, void *cookie) {
 
 } // namespace internal
 
+template <bool use_modular = false>
 LIBC_INLINE int vfprintf_internal(::FILE *__restrict stream,
                                   const char *__restrict format,
                                   internal::ArgList &args) {
@@ -48,7 +49,9 @@ LIBC_INLINE int vfprintf_internal(::FILE *__restrict stream,
                                  stream);
   printf_core::Writer writer(wb);
 
-  auto retval = printf_core::printf_main(&writer, format, args);
+  auto retval = use_modular
+                    ? printf_core::printf_main_modular(&writer, format, args)
+                    : printf_core::printf_main(&writer, format, args);
   if (!retval.has_value()) {
     libc_errno = printf_core::internal_error_to_errno(retval.error());
     return -1;
diff --git a/libc/src/stdio/baremetal/vprintf.cpp b/libc/src/stdio/baremetal/vprintf.cpp
index 38f12ab09fa25..e2144b8d8175b 100644
--- a/libc/src/stdio/baremetal/vprintf.cpp
+++ b/libc/src/stdio/baremetal/vprintf.cpp
@@ -24,7 +24,12 @@ LLVM_LIBC_FUNCTION(int, vprintf,
                                  // and pointer semantics, as well as handling
                                  // destruction automatically.
 
+#ifdef LIBC_COPT_PRINTF_MODULAR
+  LIBC_INLINE_ASM(".reloc ., BFD_RELOC_NONE, __printf_float");
+  return vfprintf_internal<true>(stdout, format, args);
+#else
   return vfprintf_internal(stdout, format, args);
+#endif
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/baremetal/vprintf_modular.cpp b/libc/src/stdio/baremetal/vprintf_modular.cpp
new file mode 100644
index 0000000000000..b14e0fae585bd
--- /dev/null
+++ b/libc/src/stdio/baremetal/vprintf_modular.cpp
@@ -0,0 +1,30 @@
+//===-- Implementation of vprintf_modular -----------------------*- 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/vprintf.h"
+
+#include "hdr/stdio_macros.h"
+#include "src/__support/arg_list.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/baremetal/vfprintf_internal.h"
+
+#include <stdarg.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, __vprintf_modular,
+                   (const char *__restrict format, va_list vlist)) {
+  internal::ArgList args(vlist); // This holder class allows for easier copying
+                                 // and pointer semantics, as well as handling
+                                 // destruction automatically.
+
+  return vfprintf_internal<true>(stdout, format, args);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/generic/CMakeLists.txt b/libc/src/stdio/generic/CMakeLists.txt
index 71055edea3d9e..18da01b19d27a 100644
--- a/libc/src/stdio/generic/CMakeLists.txt
+++ b/libc/src/stdio/generic/CMakeLists.txt
@@ -416,6 +416,10 @@ if(LLVM_LIBC_FULL_BUILD)
   )
 endif()
 
+if (LIBC_CONF_PRINTF_MODULAR AND NOT LIBC_TARGET_OS_IS_BAREMETAL)
+  message(FATAL_ERROR "modular printf is only supported in baremetal stdio")
+endif()
+
 add_generic_entrypoint_object(
   printf
   SRCS
diff --git a/libc/src/stdio/gpu/CMakeLists.txt b/libc/src/stdio/gpu/CMakeLists.txt
index 8412153bf5801..2aea66482997f 100644
--- a/libc/src/stdio/gpu/CMakeLists.txt
+++ b/libc/src/stdio/gpu/CMakeLists.txt
@@ -1,3 +1,7 @@
+if (LIBC_CONF_PRINTF_MODULAR)
+  message(FATAL_ERROR "modular printf is only supported in baremetal stdio")
+endif()
+
 add_entrypoint_object(
   stdin
   SRCS
diff --git a/libc/src/stdio/printf.h b/libc/src/stdio/printf.h
index 9e47ad8680f9c..81b7d866a6a59 100644
--- a/libc/src/stdio/printf.h
+++ b/libc/src/stdio/printf.h
@@ -15,6 +15,7 @@
 namespace LIBC_NAMESPACE_DECL {
 
 int printf(const char *__restrict format, ...);
+int __printf_modular(const char *__restrict format, ...);
 
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/stdio/printf_core/CMakeLists.txt b/libc/src/stdio/printf_core/CMakeLists.txt
index ae93cc754299b..1a6b6383bf4d4 100644
--- a/libc/src/stdio/printf_core/CMakeLists.txt
+++ b/libc/src/stdio/printf_core/CMakeLists.txt
@@ -28,6 +28,9 @@ endif()
 if(LIBC_CONF_PRINTF_RUNTIME_DISPATCH)
   list(APPEND printf_config_copts "-DLIBC_COPT_PRINTF_RUNTIME_DISPATCH")
 endif()
+if(LIBC_CONF_PRINTF_MODULAR)
+  list(APPEND printf_config_copts "-DLIBC_COPT_PRINTF_MODULAR")
+endif()
 if(printf_config_copts)
   list(PREPEND printf_config_copts "COMPILE_OPTIONS")
 endif()
@@ -146,10 +149,12 @@ add_header_library(
     ${wchar_deps}
 )
 
-add_header_library(
+add_object_library(
   printf_main
   HDRS
     printf_main.h
+  SRCS
+    float_impl.cpp
   DEPENDS
     .parser
     .converter
diff --git a/libc/src/stdio/printf_core/float_dec_converter.h b/libc/src/stdio/printf_core/float_dec_converter.h
index ed004f9a26a13..1b0a02ed426c1 100644
--- a/libc/src/stdio/printf_core/float_dec_converter.h
+++ b/libc/src/stdio/printf_core/float_dec_converter.h
@@ -1122,6 +1122,18 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<write_mode> *writer,
   }
 }
 
+template <WriteMode write_mode>
+LIBC_PRINTF_MODULAR_DECL int
+convert_float_decimal(Writer<write_mode> *writer, const FormatSection &to_conv);
+template <WriteMode write_mode>
+LIBC_PRINTF_MODULAR_DECL int
+convert_float_dec_exp(Writer<write_mode> *writer, const FormatSection &to_conv);
+template <WriteMode write_mode>
+LIBC_PRINTF_MODULAR_DECL int
+convert_float_dec_auto(Writer<write_mode> *writer,
+                       const FormatSection &to_conv);
+
+#ifdef LIBC_PRINTF_DEFINE_MODULAR
 // TODO: unify the float converters to remove the duplicated checks for inf/nan.
 
 template <WriteMode write_mode>
@@ -1189,6 +1201,7 @@ LIBC_INLINE int convert_float_dec_auto(Writer<write_mode> *writer,
 
   return convert_inf_nan(writer, to_conv);
 }
+#endif
 
 } // namespace printf_core
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/printf_core/float_dec_converter_limited.h b/libc/src/stdio/printf_core/float_dec_converter_limited.h
index 0f85d0a8d26b4..2fc2a180ae2b9 100644
--- a/libc/src/stdio/printf_core/float_dec_converter_limited.h
+++ b/libc/src/stdio/printf_core/float_dec_converter_limited.h
@@ -676,6 +676,18 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<write_mode> *writer,
   return convert_float_typed<T>(writer, to_conv, float_bits, ConversionType::G);
 }
 
+template <WriteMode write_mode>
+LIBC_PRINTF_MODULAR_DECL int
+convert_float_decimal(Writer<write_mode> *writer, const FormatSection &to_conv);
+template <WriteMode write_mode>
+LIBC_PRINTF_MODULAR_DECL int
+convert_float_dec_exp(Writer<write_mode> *writer, const FormatSection &to_conv);
+template <WriteMode write_mode>
+LIBC_PRINTF_MODULAR_DECL int
+convert_float_dec_auto(Writer<write_mode> *writer,
+                       const FormatSection &to_conv);
+
+#ifdef LIBC_PRINTF_DEFINE_MODULAR
 template <WriteMode write_mode>
 LIBC_INLINE int convert_float_decimal(Writer<write_mode> *writer,
                                       const FormatSection &to_conv) {
@@ -693,6 +705,7 @@ LIBC_INLINE int convert_float_dec_auto(Writer<write_mode> *writer,
                                        const FormatSection &to_conv) {
   return convert_float_outer(writer, to_conv, ConversionType::G);
 }
+#endif
 
 } // namespace printf_core
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/printf_core/float_hex_converter.h b/libc/src/stdio/printf_core/float_hex_converter.h
index 9b57f1d803e74..d2be294dc47ab 100644
--- a/libc/src/stdio/printf_core/float_hex_converter.h
+++ b/libc/src/stdio/printf_core/float_hex_converter.h
@@ -25,6 +25,11 @@
 namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
+template <WriteMode write_mode>
+LIBC_PRINTF_MODULAR_DECL int
+convert_float_hex_exp(Writer<write_mode> *writer, const FormatSection &to_conv);
+
+#ifdef LIBC_PRINTF_DEFINE_MODULAR
 template <WriteMode write_mode>
 LIBC_INLINE int convert_float_hex_exp(Writer<write_mode> *writer,
                                       const FormatSection &to_conv) {
@@ -253,6 +258,7 @@ LIBC_INLINE int convert_float_hex_exp(Writer<write_mode> *writer,
   }
   return WRITE_OK;
 }
+#endif
 
 } // namespace printf_core
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/printf_core/float_impl.cpp b/libc/src/stdio/printf_core/float_impl.cpp
new file mode 100644
index 0000000000000..2215ac101f47d
--- /dev/null
+++ b/libc/src/stdio/printf_core/float_impl.cpp
@@ -0,0 +1,56 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file instantiates the functionality needed for supporting floating
+/// point arguments in modular printf builds. Non-modular printf builds
+/// implicitly instantiate these functions.
+///
+//===----------------------------------------------------------------------===//
+
+#ifdef LIBC_COPT_PRINTF_MODULAR
+#include "src/__support/arg_list.h"
+
+#define LIBC_PRINTF_DEFINE_MODULAR
+#include "src/stdio/printf_core/float_dec_converter.h"
+#include "src/stdio/printf_core/float_hex_converter.h"
+#include "src/stdio/printf_core/parser.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace printf_core {
+template class Parser<internal::ArgList>;
+template class Parser<internal::DummyArgList<false>>;
+template class Parser<internal::DummyArgList<true>>;
+template class Parser<internal::StructArgList<false>>;
+template class Parser<internal::StructArgList<true>>;
+
+#define INSTANTIATE_CONVERT_FN(NAME)                                           \
+  template int NAME<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>(                   \
+      Writer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> * writer,                 \
+      const FormatSection &to_conv);                                           \
+  template int NAME<WriteMode::FLUSH_TO_STREAM>(                               \
+      Writer<WriteMode::FLUSH_TO_STREAM> * writer,                             \
+      const FormatSection &to_conv);                                           \
+  template int NAME<WriteMode::RESIZE_AND_FILL_BUFF>(                          \
+      Writer<WriteMode::RESIZE_AND_FILL_BUFF> * writer,                        \
+      const FormatSection &to_conv);                                           \
+  template int NAME<WriteMode::RUNTIME_DISPATCH>(                              \
+      Writer<WriteMode::RUNTIME_DISPATCH> * writer,                            \
+      const FormatSection &to_conv)
+
+INSTANTIATE_CONVERT_FN(convert_float_decimal);
+INSTANTIATE_CONVERT_FN(convert_float_dec_exp);
+INSTANTIATE_CONVERT_FN(convert_float_dec_auto);
+INSTANTIATE_CONVERT_FN(convert_float_hex_exp);
+
+} // namespace printf_core
+} // namespace LIBC_NAMESPACE_DECL
+
+// Bring this file into the link if __printf_float is referenced.
+extern "C" void __printf_float() {}
+#endif
diff --git a/libc/src/stdio/printf_core/parser.h b/libc/src/stdio/printf_core/parser.h
index a3b62991bcec9..37a05aed9a131 100644
--- a/libc/src/stdio/printf_core/parser.h
+++ b/libc/src/stdio/printf_core/parser.h
@@ -249,11 +249,7 @@ template <typename ArgProvider> class Parser {
       case ('A'):
       case ('g'):
       case ('G'):
-        if (lm != LengthModifier::L) {
-          WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, double, conv_index);
-        } else {
-          WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long double, conv_index);
-        }
+        write_float_arg_val(section, lm, conv_index);
         break;
 #endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
 #ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
@@ -312,6 +308,12 @@ template <typename ArgProvider> class Parser {
     return section;
   }
 
+  LIBC_PRINTF_MODULAR_DECL void write_float_arg_val(FormatSection &section,
+                                                    LengthModifier lm,
+                                                    size_t conv_index);
+  LIBC_PRINTF_MODULAR_DECL TypeDesc float_type_desc(LengthModifier lm);
+  LIBC_PRINTF_MODULAR_DECL bool advance_arg_if_float(TypeDesc cur_type_desc);
+
 private:
   // parse_flags parses the flags inside a format string. It assumes that
   // str[*local_pos] is inside a format specifier, and parses any flags it
@@ -487,10 +489,9 @@ template <typename ArgProvider> class Parser {
         args_cur.template next_var<uint64_t>();
 #ifndef LIBC_COPT_PRINTF_DISABLE_FLOAT
       // Floating point numbers are stored separately from the other arguments.
-      else if (cur_type_desc == type_desc_from_type<double>())
-        args_cur.template next_var<double>();
-      else if (cur_type_desc == type_desc_from_type<long double>())
-        args_cur.template next_var<long double>();
+      else if (&Parser::advance_arg_if_float &&
+               advance_arg_if_float(cur_type_desc))
+        ;
 #endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
 #ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
       // Floating point numbers may be stored separately from the other
@@ -652,10 +653,7 @@ template <typename ArgProvider> class Parser {
         case ('A'):
         case ('g'):
         case ('G'):
-          if (lm != LengthModifier::L)
-            conv_size = type_desc_from_type<double>();
-          else
-            conv_size = type_desc_from_type<long double>();
+          conv_size = float_type_desc(lm);
           break;
 #endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
 #ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
@@ -704,6 +702,40 @@ template <typename ArgProvider> class Parser {
 #endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
 };
 
+#ifdef LIBC_PRINTF_DEFINE_MODULAR
+template <typename ArgParser>
+LIBC_INLINE void
+Parser<ArgParser>::write_float_arg_val(FormatSection &section,
+                                       LengthModifier lm,
+                                       [[maybe_unused]] size_t conv_index) {
+  if (lm != LengthModifier::L) {
+    WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, double, conv_index);
+  } else {
+    WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long double, conv_index);
+  }
+}
+
+template <typename ArgParser>
+LIBC_INLINE TypeDesc Parser<ArgParser>::float_type_desc(LengthModifier lm) {
+  if (lm != LengthModifier::L)
+    return type_desc_from_type<double>();
+  else
+    return type_desc_from_type<long double>();
+}
+
+template <typename ArgParser>
+LIBC_INLINE bool
+Parser<ArgParser>::advance_arg_if_float(TypeDesc cur_type_desc) {
+  if (cur_type_desc == type_desc_from_type<double>())
+    args_cur.template next_var<double>();
+  else if (cur_type_desc == type_desc_from_type<long double>())
+    args_cur.template next_var<long double>();
+  else
+    return false;
+  return true;
+}
+#endif
+
 } // namespace printf_core
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/stdio/printf_core/printf_config.h b/libc/src/stdio/printf_core/printf_config.h
index 8a48abdd170ec..8f6ae8b41bc92 100644
--- a/libc/src/stdio/printf_core/printf_config.h
+++ b/libc/src/stdio/printf_core/printf_config.h
@@ -48,4 +48,11 @@
 
 // LIBC_COPT_PRINTF_NO_NULLPTR_CHECKS
 
+#ifdef LIBC_COPT_PRINTF_MODULAR
+#define LIBC_PRINTF_MODULAR_DECL [[gnu::weak]]
+#else
+#define LIBC_PRINTF_MODULAR_DECL LIBC_INLINE
+#define LIBC_PRINTF_DEFINE_MODULAR
+#endif
+
 #endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_PRINTF_CONFIG_H
diff --git a/libc/src/stdio/printf_core/printf_main.h b/libc/src/stdio/printf_core/printf_main.h
index 1c7a7237c097d..874c24464ed52 100644
--- a/libc/src/stdio/printf_core/printf_main.h
+++ b/libc/src/stdio/printf_core/printf_main.h
@@ -23,9 +23,9 @@ namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
 template <WriteMode write_mode>
-ErrorOr<size_t> printf_main(Writer<write_mode> *writer,
-                            const char *__restrict str,
-                            internal::ArgList &args) {
+ErrorOr<size_t> printf_main_modular(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();
@@ -42,6 +42,16 @@ ErrorOr<size_t> printf_main(Writer<write_mode> *writer,
   return writer->get_chars_written();
 }
 
+template <WriteMode write_mode>
+ErrorOr<size_t> printf_main(Writer<write_mode> *writer,
+                            const char *__restrict str,
+                            internal::ArgList &args) {
+#ifdef LIBC_COPT_PRINTF_MODULAR
+  LIBC_INLINE_ASM(".reloc ., BFD_RELOC_NONE, __printf_float");
+#endif
+  return printf_main_modular(writer, str, args);
+}
+
 } // namespace printf_core
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/stdio/printf_core/vasprintf_internal.h b/libc/src/stdio/printf_core/vasprintf_internal.h
index db6b95d49aaca..1dbf5787717f9 100644
--- a/libc/src/stdio/printf_core/vasprintf_internal.h
+++ b/libc/src/stdio/printf_core/vasprintf_internal.h
@@ -41,6 +41,7 @@ LIBC_INLINE int resize_overflow_hook(cpp::string_view new_str,
 
 constexpr size_t DEFAULT_BUFFER_SIZE = 200;
 
+template <bool use_modular = false>
 LIBC_INLINE ErrorOr<size_t> vasprintf_internal(char **ret,
                                                const char *__restrict format,
                                                internal::ArgList args) {
@@ -49,7 +50,9 @@ LIBC_INLINE ErrorOr<size_t> vasprintf_internal(char **ret,
                                  resize_overflow_hook);
   printf_core::Writer writer(wb);
 
-  auto ret_val = printf_core::printf_main(&writer, format, args);
+  auto ret_val = use_modular
+                     ? printf_core::printf_main_modular(&writer, format, args)
+                     : printf_core::printf_main(&writer, format, args);
   if (!ret_val.has_value()) {
     *ret = nullptr;
     return ret_val;
diff --git a/libc/src/stdio/printf_core/vfprintf_internal.h b/libc/src/stdio/printf_core/vfprintf_internal.h
index 321b0693ad339..6ce4ab3d62d4a 100644
--- a/libc/src/stdio/printf_core/vfprintf_internal.h
+++ b/libc/src/stdio/printf_core/vfprintf_internal.h
@@ -81,16 +81,17 @@ LIBC_INLINE int file_write_hook(cpp::string_view new_str, void *fp) {
   return WRITE_OK;
 }
 
-LIBC_INLINE ErrorOr<size_t> vfprintf_internal(::FILE *__restrict stream,
-                                              const char *__restrict format,
-                                              internal::ArgList &args) {
+LIBC_INLINE ErrorOr<size_t>
+vfprintf_internal_modular(::FILE *__restrict stream,
+                          const char *__restrict format,
+                          internal::ArgList &args) {
   constexpr size_t BUFF_SIZE = 1024;
   char buffer[BUFF_SIZE];
   printf_core::FlushingBuffer wb(buffer, BUFF_SIZE, &file_write_hook,
                                  reinterpret_cast<void *>(stream));
   Writer writer(wb);
   internal::flockfile(stream);
-  auto retval = printf_main(&writer, format, args);
+  auto retval = printf_main_modular(&writer, format, args);
   if (!retval.has_value()) {
     internal::funlockfile(stream);
     return retval;
@@ -102,6 +103,15 @@ LIBC_INLINE ErrorOr<size_t> vfprintf_internal(::FILE *__restrict stream,
   return retval;
 }
 
+LIBC_INLINE ErrorOr<size_t> vfprintf_internal(::FILE *__restrict stream,
+                                              const char *__restrict format,
+                                              internal::ArgList &args) {
+#ifdef LIBC_COPT_PRINTF_MODULAR
+  __asm__ __volatile__(".reloc ., BFD_RELOC_NONE, __printf_float");
+#endif
+  return vfprintf_internal_modular(stream, format, args);
+}
+
 } // namespace printf_core
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/stdio/snprintf.cpp b/libc/src/stdio/snprintf.cpp
index 8364e8d59b278..d8fb1b1ad3577 100644
--- a/libc/src/stdio/snprintf.cpp
+++ b/libc/src/stdio/snprintf.cpp
@@ -34,7 +34,12 @@ LLVM_LIBC_FUNCTION(int, snprintf,
   printf_core::DropOverflowBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
   printf_core::Writer writer(wb);
 
+#ifdef LIBC_COPT_PRINTF_MODULAR
+  LIBC_INLINE_ASM(".reloc ., BFD_RELOC_NONE, __printf_float");
+  auto ret_val = printf_core::printf_main_modular(&writer, format, args);
+#else
   auto ret_val = printf_core::printf_main(&writer, format, args);
+#endif
   if (!ret_val.has_value()) {
     libc_errno = printf_core::internal_error_to_errno(ret_val.error());
     return -1;
diff --git a/libc/src/stdio/snprintf.h b/libc/src/stdio/snprintf.h
index 92a6529704076..9d350d2712803 100644
--- a/libc/src/stdio/snprintf.h
+++ b/libc/src/stdio/snprintf.h
@@ -16,6 +16,8 @@ namespace LIBC_NAMESPACE_DECL {
 
 int snprintf(char *__restrict buffer, size_t buffsz,
              const char *__restrict format, ...);
+int __snprintf_modular(char *__restrict buffer, size_t buffsz,
+                       const char *__restrict format, ...);
 
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/stdio/snprintf_modular.cpp b/libc/src/stdio/snprintf_modular.cpp
new file mode 100644
index 0000000000000..2e7683d56de75
--- /dev/null
+++ b/libc/src/stdio/snprintf_modular.cpp
@@ -0,0 +1,54 @@
+//===-- Implementation of snprintf_modular ----------------------*- 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/snprintf.h"
+
+#include "src/__support/CPP/limits.h"
+#include "src/__support/arg_list.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/printf_core/core_structs.h"
+#include "src/stdio/printf_core/error_mapper.h"
+#include "src/stdio/printf_core/printf_main.h"
+#include "src/stdio/printf_core/writer.h"
+
+#include <stdarg.h>
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, __snprintf_modular,
+                   (char *__restrict buffer, size_t buffsz,
+                    const char *__restrict format, ...)) {
+  va_list vlist;
+  va_start(vlist, format);
+  internal::ArgList args(vlist); // This holder class allows for easier copying
+                                 // and pointer semantics, as well as handling
+                                 // destruction automatically.
+  va_end(vlist);
+  printf_core::DropOverflowBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
+  printf_core::Writer writer(wb);
+
+  auto ret_val = printf_core::printf_main_modular(&writer, format, args);
+  if (!ret_val.has_value()) {
+    libc_errno = printf_core::internal_error_to_errno(ret_val.error());
+    return -1;
+  }
+  if (buffsz > 0) // if the buffsz is 0 the buffer may be a null pointer.
+    wb.buff[wb.buff_cur] = '\0';
+
+  if (ret_val.value() > static_cast<size_t>(cpp::numeric_limits<int>::max())) {
+    libc_errno =
+        printf_core::internal_error_to_errno(-printf_core::OVERFLOW_ERROR);
+    return -1;
+  }
+
+  return static_cast<int>(ret_val.value());
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/sprintf.cpp b/libc/src/stdio/sprintf.cpp
index d340096bb6d2b..bbb3f64ea7b15 100644
--- a/libc/src/stdio/sprintf.cpp
+++ b/libc/src/stdio/sprintf.cpp
@@ -35,7 +35,12 @@ LLVM_LIBC_FUNCTION(int, sprintf,
                                      cpp::numeric_limits<size_t>::max());
   printf_core::Writer writer(wb);
 
+#ifdef LIBC_COPT_PRINTF_MODULAR
+  LIBC_INLINE_ASM(".reloc ., BFD_RELOC_NONE, __printf_float");
+  auto ret_val = printf_core::printf_main_modular(&writer, format, args);
+#else
   auto ret_val = printf_core::printf_main(&writer, format, args);
+#endif
   if (!ret_val.has_value()) {
     libc_errno = printf_core::internal_error_to_errno(ret_val.error());
     return -1;
diff --git a/libc/src/stdio/sprintf.h b/libc/src/stdio/sprintf.h
index ef65de399dc6d..4008858aaaa63 100644
--- a/libc/src/stdio/sprintf.h
+++ b/libc/src/stdio/sprintf.h
@@ -14,6 +14,8 @@
 namespace LIBC_NAMESPACE_DECL {
 
 int sprintf(char *__restrict buffer, const char *__restrict format, ...);
+int __sprintf_modular(char *__restrict buffer, const char *__restrict format,
+                      ...);
 
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/stdio/sprintf_modular.cpp b/libc/src/stdio/sprintf_modular.cpp
new file mode 100644
index 0000000000000..cfed3fe4a4f4d
--- /dev/null
+++ b/libc/src/stdio/sprintf_modular.cpp
@@ -0,0 +1,54 @@
+//===-- Implementation of sprintf_modular -----------------------*- 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/sprintf.h"
+
+#include "src/__support/CPP/limits.h"
+#include "src/__support/arg_list.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/printf_core/core_structs.h"
+#include "src/stdio/printf_core/error_mapper.h"
+#include "src/stdio/printf_core/printf_main.h"
+#include "src/stdio/printf_core/writer.h"
+
+#include <stdarg.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, __sprintf_modular,
+                   (char *__restrict buffer, const char *__restrict format,
+                    ...)) {
+  va_list vlist;
+  va_start(vlist, format);
+  internal::ArgList args(vlist); // This holder class allows for easier copying
+                                 // and pointer semantics, as well as handling
+                                 // destruction automatically.
+  va_end(vlist);
+
+  printf_core::DropOverflowBuffer wb(buffer,
+                                     cpp::numeric_limits<size_t>::max());
+  printf_core::Writer writer(wb);
+
+  auto ret_val = printf_core::printf_main_modular(&writer, format, args);
+  if (!ret_val.has_value()) {
+    libc_errno = printf_core::internal_error_to_errno(ret_val.error());
+    return -1;
+  }
+  wb.buff[wb.buff_cur] = '\0';
+
+  if (ret_val.value() > static_cast<size_t>(cpp::numeric_limits<int>::max())) {
+    libc_errno =
+        printf_core::internal_error_to_errno(-printf_core::OVERFLOW_ERROR);
+    return -1;
+  }
+
+  return static_cast<int>(ret_val.value());
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/vasprintf.cpp b/libc/src/stdio/vasprintf.cpp
index bd77cd8864312..64d959c6edcae 100644
--- a/libc/src/stdio/vasprintf.cpp
+++ b/libc/src/stdio/vasprintf.cpp
@@ -22,7 +22,12 @@ LLVM_LIBC_FUNCTION(int, vasprintf,
   internal::ArgList args(vlist); // This holder class allows for easier copying
                                  // and pointer semantics, as well as handling
                                  // destruction automatically.
+#ifdef LIBC_COPT_PRINTF_MODULAR
+  LIBC_INLINE_ASM(".reloc ., BFD_RELOC_NONE, __printf_float");
+  auto ret_val = printf_core::vasprintf_internal<true>(ret, format, args);
+#else
   auto ret_val = printf_core::vasprintf_internal(ret, format, args);
+#endif
   if (!ret_val.has_value()) {
     libc_errno = printf_core::internal_error_to_errno(ret_val.error());
     return -1;
diff --git a/libc/src/stdio/vasprintf.h b/libc/src/stdio/vasprintf.h
index 7a98568edbc07..9f6ad87deac81 100644
--- a/libc/src/stdio/vasprintf.h
+++ b/libc/src/stdio/vasprintf.h
@@ -16,6 +16,8 @@ namespace LIBC_NAMESPACE_DECL {
 
 int vasprintf(char **__restrict s, const char *__restrict format,
               va_list vlist);
+int __vasprintf_modular(char **__restrict s, const char *__restrict format,
+                        va_list vlist);
 
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/stdio/vasprintf_modular.cpp b/libc/src/stdio/vasprintf_modular.cpp
new file mode 100644
index 0000000000000..98620bb8a440c
--- /dev/null
+++ b/libc/src/stdio/vasprintf_modular.cpp
@@ -0,0 +1,38 @@
+//===-- Implementation of vasprintf_modular ---------------------*- 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/__support/CPP/limits.h"
+#include "src/__support/arg_list.h"
+#include "src/__support/libc_errno.h"
+#include "src/stdio/printf_core/core_structs.h"
+#include "src/stdio/printf_core/error_mapper.h"
+#include "src/stdio/printf_core/vasprintf_internal.h"
+#include "src/stdio/vasprintf.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, __vasprintf_modular,
+                   (char **__restrict ret, const char *__restrict format,
+                    va_list vlist)) {
+  internal::ArgList args(vlist); // This holder class allows for easier copying
+                                 // and pointer semantics, as well as handling
+                                 // destruction automatically.
+  auto ret_val = printf_core::vasprintf_internal<true>(ret, format, args);
+  if (!ret_val.has_value()) {
+    libc_errno = printf_core::internal_error_to_errno(ret_val.error());
+    return -1;
+  }
+  if (ret_val.value() > static_cast<size_t>(cpp::numeric_limits<int>::max())) {
+    libc_errno =
+        printf_core::internal_error_to_errno(-printf_core::OVERFLOW_ERROR);
+    return -1;
+  }
+  return static_cast<int>(ret_val.value());
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/vprintf.h b/libc/src/stdio/vprintf.h
index 63f0f3ae62d6e..70cf03f69a767 100644
--- a/libc/src/stdio/vprintf.h
+++ b/libc/src/stdio/vprintf.h
@@ -16,6 +16,7 @@
 namespace LIBC_NAMESPACE_DECL {
 
 int vprintf(const char *__restrict format, va_list vlist);
+int __vprintf_modular(const char *__restrict format, va_list vlist);
 
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/stdio/vsnprintf.cpp b/libc/src/stdio/vsnprintf.cpp
index b65343dfefc75..bea980d6b20ec 100644
--- a/libc/src/stdio/vsnprintf.cpp
+++ b/libc/src/stdio/vsnprintf.cpp
@@ -31,7 +31,12 @@ LLVM_LIBC_FUNCTION(int, vsnprintf,
   printf_core::DropOverflowBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
   printf_core::Writer writer(wb);
 
+#ifdef LIBC_COPT_PRINTF_MODULAR
+  LIBC_INLINE_ASM(".reloc ., BFD_RELOC_NONE, __printf_float");
+  auto ret_val = printf_core::printf_main_modular(&writer, format, args);
+#else
   auto ret_val = printf_core::printf_main(&writer, format, args);
+#endif
   if (!ret_val.has_value()) {
     libc_errno = printf_core::internal_error_to_errno(ret_val.error());
     return -1;
diff --git a/libc/src/stdio/vsnprintf.h b/libc/src/stdio/vsnprintf.h
index 27ae763f7746a..0b8af0ac6ff36 100644
--- a/libc/src/stdio/vsnprintf.h
+++ b/libc/src/stdio/vsnprintf.h
@@ -17,6 +17,8 @@ namespace LIBC_NAMESPACE_DECL {
 
 int vsnprintf(char *__restrict buffer, size_t buffsz,
               const char *__restrict format, va_list vlist);
+int __vsnprintf_modular(char *__restrict buffer, size_t buffsz,
+                        const char *__restrict format, va_list vlist);
 
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/stdio/vsnprintf_modular.cpp b/libc/src/stdio/vsnprintf_modular.cpp
new file mode 100644
index 0000000000000..7225311601281
--- /dev/null
+++ b/libc/src/stdio/vsnprintf_modular.cpp
@@ -0,0 +1,51 @@
+//===-- Implementation of vsnprintf_modular ---------------------*- 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/vsnprintf.h"
+
+#include "src/__support/CPP/limits.h"
+#include "src/__support/arg_list.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/printf_core/core_structs.h"
+#include "src/stdio/printf_core/error_mapper.h"
+#include "src/stdio/printf_core/printf_main.h"
+#include "src/stdio/printf_core/writer.h"
+
+#include <stdarg.h>
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, __vsnprintf_modular,
+                   (char *__restrict buffer, size_t buffsz,
+                    const char *__restrict format, va_list vlist)) {
+  internal::ArgList args(vlist); // This holder class allows for easier copying
+                                 // and pointer semantics, as well as handling
+                                 // destruction automatically.
+  printf_core::DropOverflowBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
+  printf_core::Writer writer(wb);
+
+  auto ret_val = printf_core::printf_main_modular(&writer, format, args);
+  if (!ret_val.has_value()) {
+    libc_errno = printf_core::internal_error_to_errno(ret_val.error());
+    return -1;
+  }
+  if (buffsz > 0) // if the buffsz is 0 the buffer may be a null pointer.
+    wb.buff[wb.buff_cur] = '\0';
+
+  if (ret_val.value() > static_cast<size_t>(cpp::numeric_limits<int>::max())) {
+    libc_errno =
+        printf_core::internal_error_to_errno(-printf_core::OVERFLOW_ERROR);
+    return -1;
+  }
+
+  return static_cast<int>(ret_val.value());
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/vsprintf.cpp b/libc/src/stdio/vsprintf.cpp
index 8affb88d2b807..3dae05fbbaadd 100644
--- a/libc/src/stdio/vsprintf.cpp
+++ b/libc/src/stdio/vsprintf.cpp
@@ -32,7 +32,12 @@ LLVM_LIBC_FUNCTION(int, vsprintf,
                                      cpp::numeric_limits<size_t>::max());
   printf_core::Writer writer(wb);
 
+#ifdef LIBC_COPT_PRINTF_MODULAR
+  LIBC_INLINE_ASM(".reloc ., BFD_RELOC_NONE, __printf_float");
+  auto ret_val = printf_core::printf_main_modular(&writer, format, args);
+#else
   auto ret_val = printf_core::printf_main(&writer, format, args);
+#endif
   if (!ret_val.has_value()) {
     libc_errno = printf_core::internal_error_to_errno(ret_val.error());
     return -1;
diff --git a/libc/src/stdio/vsprintf.h b/libc/src/stdio/vsprintf.h
index abb89ba76eae5..54eb6120098a4 100644
--- a/libc/src/stdio/vsprintf.h
+++ b/libc/src/stdio/vsprintf.h
@@ -16,6 +16,8 @@ namespace LIBC_NAMESPACE_DECL {
 
 int vsprintf(char *__restrict buffer, const char *__restrict format,
              va_list vlist);
+int __vsprintf_modular(char *__restrict buffer, const char *__restrict format,
+                       va_list vlist);
 
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/stdio/vsprintf_modular.cpp b/libc/src/stdio/vsprintf_modular.cpp
new file mode 100644
index 0000000000000..844d1e3cdd550
--- /dev/null
+++ b/libc/src/stdio/vsprintf_modular.cpp
@@ -0,0 +1,50 @@
+//===-- Implementation of vsprintf_modular ----------------------*- 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/vsprintf.h"
+
+#include "src/__support/CPP/limits.h"
+#include "src/__support/arg_list.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/printf_core/core_structs.h"
+#include "src/stdio/printf_core/error_mapper.h"
+#include "src/stdio/printf_core/printf_main.h"
+#include "src/stdio/printf_core/writer.h"
+
+#include <stdarg.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, __vsprintf_modular,
+                   (char *__restrict buffer, const char *__restrict format,
+                    va_list vlist)) {
+  internal::ArgList args(vlist); // This holder class allows for easier copying
+                                 // and pointer semantics, as well as handling
+                                 // destruction automatically.
+
+  printf_core::DropOverflowBuffer wb(buffer,
+                                     cpp::numeric_limits<size_t>::max());
+  printf_core::Writer writer(wb);
+
+  auto ret_val = printf_core::printf_main_modular(&writer, format, args);
+  if (!ret_val.has_value()) {
+    libc_errno = printf_core::internal_error_to_errno(ret_val.error());
+    return -1;
+  }
+  wb.buff[wb.buff_cur] = '\0';
+
+  if (ret_val.value() > static_cast<size_t>(cpp::numeric_limits<int>::max())) {
+    libc_errno =
+        printf_core::internal_error_to_errno(-printf_core::OVERFLOW_ERROR);
+    return -1;
+  }
+  return static_cast<int>(ret_val.value());
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/utils/hdrgen/hdrgen/header.py b/libc/utils/hdrgen/hdrgen/header.py
index b69d22494a7ac..1af741844bd01 100644
--- a/libc/utils/hdrgen/hdrgen/header.py
+++ b/libc/utils/hdrgen/hdrgen/header.py
@@ -177,7 +177,7 @@ def includes(self):
                 for typ in self.all_types()
             }
             | {
-                PurePosixPath("llvm-libc-macros") / f"{attr}.h"
+                PurePosixPath("llvm-libc-macros") / f"{attr.split('(')[0]}.h"
                 for attr in self.all_attributes() - COMMON_ATTRIBUTES
             }
         )
diff --git a/libc/utils/hdrgen/tests/expected_output/test_header.h b/libc/utils/hdrgen/tests/expected_output/test_header.h
index 49112a353f7b6..7704176bbc3d7 100644
--- a/libc/utils/hdrgen/tests/expected_output/test_header.h
+++ b/libc/utils/hdrgen/tests/expected_output/test_header.h
@@ -13,6 +13,7 @@
 #include "llvm-libc-macros/float16-macros.h"
 
 #include "llvm-libc-macros/CONST_FUNC_A.h"
+#include "llvm-libc-macros/MACRO_ATTR.h"
 #include "llvm-libc-macros/test_more-macros.h"
 #include "llvm-libc-macros/test_small-macros.h"
 #include "llvm-libc-types/float128.h"
@@ -32,7 +33,7 @@ enum {
 
 __BEGIN_C_DECLS
 
-CONST_FUNC_A void func_a(void) __NOEXCEPT;
+CONST_FUNC_A MACRO_ATTR(A) void func_a(void) __NOEXCEPT;
 
 #ifdef LIBC_TYPES_HAS_FLOAT128
 float128 func_b(void) __NOEXCEPT;
diff --git a/libc/utils/hdrgen/tests/expected_output/test_small.json b/libc/utils/hdrgen/tests/expected_output/test_small.json
index 8502df23b9a41..e62fce1043ee5 100644
--- a/libc/utils/hdrgen/tests/expected_output/test_small.json
+++ b/libc/utils/hdrgen/tests/expected_output/test_small.json
@@ -5,6 +5,7 @@
     "includes": [
       "__llvm-libc-common.h",
       "llvm-libc-macros/CONST_FUNC_A.h",
+      "llvm-libc-macros/MACRO_ATTR.h",
       "llvm-libc-macros/test_more-macros.h",
       "llvm-libc-macros/test_small-macros.h",
       "llvm-libc-types/float128.h",
diff --git a/libc/utils/hdrgen/tests/input/merge1.yaml b/libc/utils/hdrgen/tests/input/merge1.yaml
index 950abd1770320..9abcf7f266710 100644
--- a/libc/utils/hdrgen/tests/input/merge1.yaml
+++ b/libc/utils/hdrgen/tests/input/merge1.yaml
@@ -17,3 +17,4 @@ functions:
       - stdc
     attributes:
       - CONST_FUNC_A
+      - MACRO_ATTR(A)

>From 0ef729ccfbc435c3e7cda8c146513fb4dfd1f2e7 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Wed, 28 Jan 2026 14:47:51 -0800
Subject: [PATCH 2/8] Attr Docs typos

---
 clang/include/clang/Basic/AttrDocs.td | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index b3dcd4410de95..429ce266d5c79 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9726,9 +9726,9 @@ not all aspects of the implementation are needed for a given call, the compiler
 may redirect the call to the identifier given as the first argument to the
 attribute (the modular implementation function).
 
-The second argument is a implementation name, and the remaining arguments are
+The second argument is an implementation name, and the remaining arguments are
 aspects of the format string for the compiler to report. The implementation
-name is an unevaluated identifier be in the C namespace.
+name is an unevaluated identifier in the C namespace.
 
 The compiler reports that a call requires an aspect by issuing a relocation for
 the symbol ``<impl_name>_<aspect>`` at the point of the call. This arranges for

>From 4f9c3657e09c6f9594d21689b639beea3a46474d Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Wed, 28 Jan 2026 15:03:26 -0800
Subject: [PATCH 3/8] Use constexpr for switching on printf core version

---
 libc/src/stdio/baremetal/vfprintf_internal.h    | 9 ++++++---
 libc/src/stdio/printf_core/vasprintf_internal.h | 9 ++++++---
 2 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/libc/src/stdio/baremetal/vfprintf_internal.h b/libc/src/stdio/baremetal/vfprintf_internal.h
index 50eaef4a4a3e2..c4cd8b8ad8a83 100644
--- a/libc/src/stdio/baremetal/vfprintf_internal.h
+++ b/libc/src/stdio/baremetal/vfprintf_internal.h
@@ -49,9 +49,12 @@ LIBC_INLINE int vfprintf_internal(::FILE *__restrict stream,
                                  stream);
   printf_core::Writer writer(wb);
 
-  auto retval = use_modular
-                    ? printf_core::printf_main_modular(&writer, format, args)
-                    : printf_core::printf_main(&writer, format, args);
+  auto retval = [&] {
+    if constexpr (use_modular)
+      return printf_core::printf_main_modular(&writer, format, args);
+    else
+      return printf_core::printf_main(&writer, format, args);
+  }();
   if (!retval.has_value()) {
     libc_errno = printf_core::internal_error_to_errno(retval.error());
     return -1;
diff --git a/libc/src/stdio/printf_core/vasprintf_internal.h b/libc/src/stdio/printf_core/vasprintf_internal.h
index 1dbf5787717f9..fe5428e6118dc 100644
--- a/libc/src/stdio/printf_core/vasprintf_internal.h
+++ b/libc/src/stdio/printf_core/vasprintf_internal.h
@@ -50,9 +50,12 @@ LIBC_INLINE ErrorOr<size_t> vasprintf_internal(char **ret,
                                  resize_overflow_hook);
   printf_core::Writer writer(wb);
 
-  auto ret_val = use_modular
-                     ? printf_core::printf_main_modular(&writer, format, args)
-                     : printf_core::printf_main(&writer, format, args);
+  auto ret_val = [&] {
+    if constexpr (use_modular)
+      return printf_core::printf_main_modular(&writer, format, args);
+    else
+      return printf_core::printf_main(&writer, format, args);
+  }();
   if (!ret_val.has_value()) {
     *ret = nullptr;
     return ret_val;

>From 5c011f49987223afccba9d441031ccd6f74a99eb Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Thu, 29 Jan 2026 14:52:47 -0800
Subject: [PATCH 4/8] Undo changes to core vfprintf_internal; now altering
 baremetal only

---
 libc/src/stdio/printf_core/vfprintf_internal.h | 18 ++++--------------
 1 file changed, 4 insertions(+), 14 deletions(-)

diff --git a/libc/src/stdio/printf_core/vfprintf_internal.h b/libc/src/stdio/printf_core/vfprintf_internal.h
index 6ce4ab3d62d4a..321b0693ad339 100644
--- a/libc/src/stdio/printf_core/vfprintf_internal.h
+++ b/libc/src/stdio/printf_core/vfprintf_internal.h
@@ -81,17 +81,16 @@ LIBC_INLINE int file_write_hook(cpp::string_view new_str, void *fp) {
   return WRITE_OK;
 }
 
-LIBC_INLINE ErrorOr<size_t>
-vfprintf_internal_modular(::FILE *__restrict stream,
-                          const char *__restrict format,
-                          internal::ArgList &args) {
+LIBC_INLINE ErrorOr<size_t> vfprintf_internal(::FILE *__restrict stream,
+                                              const char *__restrict format,
+                                              internal::ArgList &args) {
   constexpr size_t BUFF_SIZE = 1024;
   char buffer[BUFF_SIZE];
   printf_core::FlushingBuffer wb(buffer, BUFF_SIZE, &file_write_hook,
                                  reinterpret_cast<void *>(stream));
   Writer writer(wb);
   internal::flockfile(stream);
-  auto retval = printf_main_modular(&writer, format, args);
+  auto retval = printf_main(&writer, format, args);
   if (!retval.has_value()) {
     internal::funlockfile(stream);
     return retval;
@@ -103,15 +102,6 @@ vfprintf_internal_modular(::FILE *__restrict stream,
   return retval;
 }
 
-LIBC_INLINE ErrorOr<size_t> vfprintf_internal(::FILE *__restrict stream,
-                                              const char *__restrict format,
-                                              internal::ArgList &args) {
-#ifdef LIBC_COPT_PRINTF_MODULAR
-  __asm__ __volatile__(".reloc ., BFD_RELOC_NONE, __printf_float");
-#endif
-  return vfprintf_internal_modular(stream, format, args);
-}
-
 } // namespace printf_core
 } // namespace LIBC_NAMESPACE_DECL
 

>From 442680a78c5ce9e622432b302643ce44dfb182f4 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Thu, 29 Jan 2026 16:16:48 -0800
Subject: [PATCH 5/8] Clarify logic about definitions; change MODULAR->MODULE

---
 libc/src/stdio/printf_core/float_dec_converter.h         | 8 ++++----
 libc/src/stdio/printf_core/float_dec_converter_limited.h | 8 ++++----
 libc/src/stdio/printf_core/float_hex_converter.h         | 4 ++--
 libc/src/stdio/printf_core/float_impl.cpp                | 3 ++-
 libc/src/stdio/printf_core/parser.h                      | 8 ++++----
 libc/src/stdio/printf_core/printf_config.h               | 5 ++---
 6 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/libc/src/stdio/printf_core/float_dec_converter.h b/libc/src/stdio/printf_core/float_dec_converter.h
index 1b0a02ed426c1..0f63f666a8ccb 100644
--- a/libc/src/stdio/printf_core/float_dec_converter.h
+++ b/libc/src/stdio/printf_core/float_dec_converter.h
@@ -1123,17 +1123,17 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<write_mode> *writer,
 }
 
 template <WriteMode write_mode>
-LIBC_PRINTF_MODULAR_DECL int
+LIBC_PRINTF_MODULE_DECL int
 convert_float_decimal(Writer<write_mode> *writer, const FormatSection &to_conv);
 template <WriteMode write_mode>
-LIBC_PRINTF_MODULAR_DECL int
+LIBC_PRINTF_MODULE_DECL int
 convert_float_dec_exp(Writer<write_mode> *writer, const FormatSection &to_conv);
 template <WriteMode write_mode>
-LIBC_PRINTF_MODULAR_DECL int
+LIBC_PRINTF_MODULE_DECL int
 convert_float_dec_auto(Writer<write_mode> *writer,
                        const FormatSection &to_conv);
 
-#ifdef LIBC_PRINTF_DEFINE_MODULAR
+#if !defined(LIBC_COPT_PRINTF_MODULAR) || defined(LIBC_PRINTF_DEFINE_MODULES)
 // TODO: unify the float converters to remove the duplicated checks for inf/nan.
 
 template <WriteMode write_mode>
diff --git a/libc/src/stdio/printf_core/float_dec_converter_limited.h b/libc/src/stdio/printf_core/float_dec_converter_limited.h
index 2fc2a180ae2b9..491b74ebd758a 100644
--- a/libc/src/stdio/printf_core/float_dec_converter_limited.h
+++ b/libc/src/stdio/printf_core/float_dec_converter_limited.h
@@ -677,17 +677,17 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<write_mode> *writer,
 }
 
 template <WriteMode write_mode>
-LIBC_PRINTF_MODULAR_DECL int
+LIBC_PRINTF_MODULE_DECL int
 convert_float_decimal(Writer<write_mode> *writer, const FormatSection &to_conv);
 template <WriteMode write_mode>
-LIBC_PRINTF_MODULAR_DECL int
+LIBC_PRINTF_MODULE_DECL int
 convert_float_dec_exp(Writer<write_mode> *writer, const FormatSection &to_conv);
 template <WriteMode write_mode>
-LIBC_PRINTF_MODULAR_DECL int
+LIBC_PRINTF_MODULE_DECL int
 convert_float_dec_auto(Writer<write_mode> *writer,
                        const FormatSection &to_conv);
 
-#ifdef LIBC_PRINTF_DEFINE_MODULAR
+#if !defined(LIBC_COPT_PRINTF_MODULAR) || defined(LIBC_PRINTF_DEFINE_MODULES)
 template <WriteMode write_mode>
 LIBC_INLINE int convert_float_decimal(Writer<write_mode> *writer,
                                       const FormatSection &to_conv) {
diff --git a/libc/src/stdio/printf_core/float_hex_converter.h b/libc/src/stdio/printf_core/float_hex_converter.h
index d2be294dc47ab..15d21b1ef9b9e 100644
--- a/libc/src/stdio/printf_core/float_hex_converter.h
+++ b/libc/src/stdio/printf_core/float_hex_converter.h
@@ -26,10 +26,10 @@ namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
 template <WriteMode write_mode>
-LIBC_PRINTF_MODULAR_DECL int
+LIBC_PRINTF_MODULE_DECL int
 convert_float_hex_exp(Writer<write_mode> *writer, const FormatSection &to_conv);
 
-#ifdef LIBC_PRINTF_DEFINE_MODULAR
+#if !defined(LIBC_COPT_PRINTF_MODULAR) || defined(LIBC_PRINTF_DEFINE_MODULES)
 template <WriteMode write_mode>
 LIBC_INLINE int convert_float_hex_exp(Writer<write_mode> *writer,
                                       const FormatSection &to_conv) {
diff --git a/libc/src/stdio/printf_core/float_impl.cpp b/libc/src/stdio/printf_core/float_impl.cpp
index 2215ac101f47d..47827cb0c3084 100644
--- a/libc/src/stdio/printf_core/float_impl.cpp
+++ b/libc/src/stdio/printf_core/float_impl.cpp
@@ -16,7 +16,8 @@
 #ifdef LIBC_COPT_PRINTF_MODULAR
 #include "src/__support/arg_list.h"
 
-#define LIBC_PRINTF_DEFINE_MODULAR
+
+#define LIBC_PRINTF_DEFINE_MODULES
 #include "src/stdio/printf_core/float_dec_converter.h"
 #include "src/stdio/printf_core/float_hex_converter.h"
 #include "src/stdio/printf_core/parser.h"
diff --git a/libc/src/stdio/printf_core/parser.h b/libc/src/stdio/printf_core/parser.h
index 37a05aed9a131..90cb4fd03a147 100644
--- a/libc/src/stdio/printf_core/parser.h
+++ b/libc/src/stdio/printf_core/parser.h
@@ -308,11 +308,11 @@ template <typename ArgProvider> class Parser {
     return section;
   }
 
-  LIBC_PRINTF_MODULAR_DECL void write_float_arg_val(FormatSection &section,
+  LIBC_PRINTF_MODULE_DECL void write_float_arg_val(FormatSection &section,
                                                     LengthModifier lm,
                                                     size_t conv_index);
-  LIBC_PRINTF_MODULAR_DECL TypeDesc float_type_desc(LengthModifier lm);
-  LIBC_PRINTF_MODULAR_DECL bool advance_arg_if_float(TypeDesc cur_type_desc);
+  LIBC_PRINTF_MODULE_DECL TypeDesc float_type_desc(LengthModifier lm);
+  LIBC_PRINTF_MODULE_DECL bool advance_arg_if_float(TypeDesc cur_type_desc);
 
 private:
   // parse_flags parses the flags inside a format string. It assumes that
@@ -702,7 +702,7 @@ template <typename ArgProvider> class Parser {
 #endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
 };
 
-#ifdef LIBC_PRINTF_DEFINE_MODULAR
+#if !defined(LIBC_COPT_PRINTF_MODULAR) || defined(LIBC_PRINTF_DEFINE_MODULES)
 template <typename ArgParser>
 LIBC_INLINE void
 Parser<ArgParser>::write_float_arg_val(FormatSection &section,
diff --git a/libc/src/stdio/printf_core/printf_config.h b/libc/src/stdio/printf_core/printf_config.h
index 8f6ae8b41bc92..bb4be8bdd6b1c 100644
--- a/libc/src/stdio/printf_core/printf_config.h
+++ b/libc/src/stdio/printf_core/printf_config.h
@@ -49,10 +49,9 @@
 // LIBC_COPT_PRINTF_NO_NULLPTR_CHECKS
 
 #ifdef LIBC_COPT_PRINTF_MODULAR
-#define LIBC_PRINTF_MODULAR_DECL [[gnu::weak]]
+#define LIBC_PRINTF_MODULE_DECL [[gnu::weak]]
 #else
-#define LIBC_PRINTF_MODULAR_DECL LIBC_INLINE
-#define LIBC_PRINTF_DEFINE_MODULAR
+#define LIBC_PRINTF_MODULE_DECL LIBC_INLINE
 #endif
 
 #endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_PRINTF_CONFIG_H

>From 428416e014901eb3d6698ce5f651323e15ad321d Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Thu, 29 Jan 2026 16:07:19 -0800
Subject: [PATCH 6/8] clang-format

---
 libc/src/stdio/baremetal/printf_modular.cpp              | 3 ++-
 libc/src/stdio/printf_core/float_dec_converter.h         | 8 ++++----
 libc/src/stdio/printf_core/float_dec_converter_limited.h | 8 ++++----
 libc/src/stdio/printf_core/float_hex_converter.h         | 4 ++--
 libc/src/stdio/printf_core/float_impl.cpp                | 1 -
 libc/src/stdio/printf_core/parser.h                      | 4 ++--
 6 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/libc/src/stdio/baremetal/printf_modular.cpp b/libc/src/stdio/baremetal/printf_modular.cpp
index f03060a9c3ff5..cb4b9ddcbcdbb 100644
--- a/libc/src/stdio/baremetal/printf_modular.cpp
+++ b/libc/src/stdio/baremetal/printf_modular.cpp
@@ -18,7 +18,8 @@
 
 namespace LIBC_NAMESPACE_DECL {
 
-LLVM_LIBC_FUNCTION(int, __printf_modular, (const char *__restrict format, ...)) {
+LLVM_LIBC_FUNCTION(int, __printf_modular,
+                   (const char *__restrict format, ...)) {
   va_list vlist;
   va_start(vlist, format);
   internal::ArgList args(vlist); // This holder class allows for easier copying
diff --git a/libc/src/stdio/printf_core/float_dec_converter.h b/libc/src/stdio/printf_core/float_dec_converter.h
index 0f63f666a8ccb..6221f465a09c4 100644
--- a/libc/src/stdio/printf_core/float_dec_converter.h
+++ b/libc/src/stdio/printf_core/float_dec_converter.h
@@ -1123,11 +1123,11 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<write_mode> *writer,
 }
 
 template <WriteMode write_mode>
-LIBC_PRINTF_MODULE_DECL int
-convert_float_decimal(Writer<write_mode> *writer, const FormatSection &to_conv);
+LIBC_PRINTF_MODULE_DECL int convert_float_decimal(Writer<write_mode> *writer,
+                                                  const FormatSection &to_conv);
 template <WriteMode write_mode>
-LIBC_PRINTF_MODULE_DECL int
-convert_float_dec_exp(Writer<write_mode> *writer, const FormatSection &to_conv);
+LIBC_PRINTF_MODULE_DECL int convert_float_dec_exp(Writer<write_mode> *writer,
+                                                  const FormatSection &to_conv);
 template <WriteMode write_mode>
 LIBC_PRINTF_MODULE_DECL int
 convert_float_dec_auto(Writer<write_mode> *writer,
diff --git a/libc/src/stdio/printf_core/float_dec_converter_limited.h b/libc/src/stdio/printf_core/float_dec_converter_limited.h
index 491b74ebd758a..183a09397166a 100644
--- a/libc/src/stdio/printf_core/float_dec_converter_limited.h
+++ b/libc/src/stdio/printf_core/float_dec_converter_limited.h
@@ -677,11 +677,11 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<write_mode> *writer,
 }
 
 template <WriteMode write_mode>
-LIBC_PRINTF_MODULE_DECL int
-convert_float_decimal(Writer<write_mode> *writer, const FormatSection &to_conv);
+LIBC_PRINTF_MODULE_DECL int convert_float_decimal(Writer<write_mode> *writer,
+                                                  const FormatSection &to_conv);
 template <WriteMode write_mode>
-LIBC_PRINTF_MODULE_DECL int
-convert_float_dec_exp(Writer<write_mode> *writer, const FormatSection &to_conv);
+LIBC_PRINTF_MODULE_DECL int convert_float_dec_exp(Writer<write_mode> *writer,
+                                                  const FormatSection &to_conv);
 template <WriteMode write_mode>
 LIBC_PRINTF_MODULE_DECL int
 convert_float_dec_auto(Writer<write_mode> *writer,
diff --git a/libc/src/stdio/printf_core/float_hex_converter.h b/libc/src/stdio/printf_core/float_hex_converter.h
index 15d21b1ef9b9e..9ae9f0e76477c 100644
--- a/libc/src/stdio/printf_core/float_hex_converter.h
+++ b/libc/src/stdio/printf_core/float_hex_converter.h
@@ -26,8 +26,8 @@ namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
 template <WriteMode write_mode>
-LIBC_PRINTF_MODULE_DECL int
-convert_float_hex_exp(Writer<write_mode> *writer, const FormatSection &to_conv);
+LIBC_PRINTF_MODULE_DECL int convert_float_hex_exp(Writer<write_mode> *writer,
+                                                  const FormatSection &to_conv);
 
 #if !defined(LIBC_COPT_PRINTF_MODULAR) || defined(LIBC_PRINTF_DEFINE_MODULES)
 template <WriteMode write_mode>
diff --git a/libc/src/stdio/printf_core/float_impl.cpp b/libc/src/stdio/printf_core/float_impl.cpp
index 47827cb0c3084..1efea9d52dedb 100644
--- a/libc/src/stdio/printf_core/float_impl.cpp
+++ b/libc/src/stdio/printf_core/float_impl.cpp
@@ -16,7 +16,6 @@
 #ifdef LIBC_COPT_PRINTF_MODULAR
 #include "src/__support/arg_list.h"
 
-
 #define LIBC_PRINTF_DEFINE_MODULES
 #include "src/stdio/printf_core/float_dec_converter.h"
 #include "src/stdio/printf_core/float_hex_converter.h"
diff --git a/libc/src/stdio/printf_core/parser.h b/libc/src/stdio/printf_core/parser.h
index 90cb4fd03a147..2d9d70775628a 100644
--- a/libc/src/stdio/printf_core/parser.h
+++ b/libc/src/stdio/printf_core/parser.h
@@ -309,8 +309,8 @@ template <typename ArgProvider> class Parser {
   }
 
   LIBC_PRINTF_MODULE_DECL void write_float_arg_val(FormatSection &section,
-                                                    LengthModifier lm,
-                                                    size_t conv_index);
+                                                   LengthModifier lm,
+                                                   size_t conv_index);
   LIBC_PRINTF_MODULE_DECL TypeDesc float_type_desc(LengthModifier lm);
   LIBC_PRINTF_MODULE_DECL bool advance_arg_if_float(TypeDesc cur_type_desc);
 

>From 5ccc53e76753225e48e297bf87a8a0242308fe30 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Thu, 29 Jan 2026 16:18:44 -0800
Subject: [PATCH 7/8] Move float_impl.cpp responsibilities to header files

---
 libc/src/__support/arg_list_types.def         | 13 +++++++
 .../stdio/printf_core/float_dec_converter.h   | 22 ++++++++++++
 .../printf_core/float_dec_converter_limited.h | 22 ++++++++++++
 .../stdio/printf_core/float_hex_converter.h   | 10 ++++++
 libc/src/stdio/printf_core/float_impl.cpp     | 35 ++-----------------
 libc/src/stdio/printf_core/parser.h           | 22 ++++++++++++
 libc/src/stdio/printf_core/write_modes.def    | 12 +++++++
 libc/src/stdio/printf_core/writer.h           |  7 ++--
 8 files changed, 106 insertions(+), 37 deletions(-)
 create mode 100644 libc/src/__support/arg_list_types.def
 create mode 100644 libc/src/stdio/printf_core/write_modes.def

diff --git a/libc/src/__support/arg_list_types.def b/libc/src/__support/arg_list_types.def
new file mode 100644
index 0000000000000..33693bbe65ae6
--- /dev/null
+++ b/libc/src/__support/arg_list_types.def
@@ -0,0 +1,13 @@
+//===-- argument list type list ----------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+HANDLE_ARG_LIST_TYPE(ArgList)
+HANDLE_ARG_LIST_TYPE(DummyArgList<false>)
+HANDLE_ARG_LIST_TYPE(DummyArgList<true>)
+HANDLE_ARG_LIST_TYPE(StructArgList<false>)
+HANDLE_ARG_LIST_TYPE(StructArgList<true>)
diff --git a/libc/src/stdio/printf_core/float_dec_converter.h b/libc/src/stdio/printf_core/float_dec_converter.h
index 6221f465a09c4..583570b1e9236 100644
--- a/libc/src/stdio/printf_core/float_dec_converter.h
+++ b/libc/src/stdio/printf_core/float_dec_converter.h
@@ -1201,6 +1201,28 @@ LIBC_INLINE int convert_float_dec_auto(Writer<write_mode> *writer,
 
   return convert_inf_nan(writer, to_conv);
 }
+
+#ifdef LIBC_PRINTF_DEFINE_MODULES
+
+#define HANDLE_WRITE_MODE(MODE)                                                \
+  template int convert_float_decimal<WriteMode::MODE>(                         \
+      Writer<WriteMode::MODE> * writer, const FormatSection &to_conv);
+#include "src/stdio/printf_core/write_modes.def"
+#undef HANDLE_WRITE_MODE
+
+#define HANDLE_WRITE_MODE(MODE)                                                \
+  template int convert_float_dec_exp<WriteMode::MODE>(                         \
+      Writer<WriteMode::MODE> * writer, const FormatSection &to_conv);
+#include "src/stdio/printf_core/write_modes.def"
+#undef HANDLE_WRITE_MODE
+
+#define HANDLE_WRITE_MODE(MODE)                                                \
+  template int convert_float_dec_auto<WriteMode::MODE>(                        \
+      Writer<WriteMode::MODE> * writer, const FormatSection &to_conv);
+#include "src/stdio/printf_core/write_modes.def"
+#undef HANDLE_WRITE_MODE
+
+#endif
 #endif
 
 } // namespace printf_core
diff --git a/libc/src/stdio/printf_core/float_dec_converter_limited.h b/libc/src/stdio/printf_core/float_dec_converter_limited.h
index 183a09397166a..e63b5a884bb54 100644
--- a/libc/src/stdio/printf_core/float_dec_converter_limited.h
+++ b/libc/src/stdio/printf_core/float_dec_converter_limited.h
@@ -705,6 +705,28 @@ LIBC_INLINE int convert_float_dec_auto(Writer<write_mode> *writer,
                                        const FormatSection &to_conv) {
   return convert_float_outer(writer, to_conv, ConversionType::G);
 }
+
+#ifdef LIBC_PRINTF_DEFINE_MODULES
+
+#define HANDLE_WRITE_MODE(MODE)                                                \
+  template int convert_float_decimal<WriteMode::MODE>(                         \
+      Writer<WriteMode::MODE> * writer, const FormatSection &to_conv);
+#include "src/stdio/printf_core/write_modes.def"
+#undef HANDLE_WRITE_MODE
+
+#define HANDLE_WRITE_MODE(MODE)                                                \
+  template int convert_float_dec_exp<WriteMode::MODE>(                         \
+      Writer<WriteMode::MODE> * writer, const FormatSection &to_conv);
+#include "src/stdio/printf_core/write_modes.def"
+#undef HANDLE_WRITE_MODE
+
+#define HANDLE_WRITE_MODE(MODE)                                                \
+  template int convert_float_dec_auto<WriteMode::MODE>(                        \
+      Writer<WriteMode::MODE> * writer, const FormatSection &to_conv);
+#include "src/stdio/printf_core/write_modes.def"
+#undef HANDLE_WRITE_MODE
+
+#endif
 #endif
 
 } // namespace printf_core
diff --git a/libc/src/stdio/printf_core/float_hex_converter.h b/libc/src/stdio/printf_core/float_hex_converter.h
index 9ae9f0e76477c..1cd4bdeefd6c8 100644
--- a/libc/src/stdio/printf_core/float_hex_converter.h
+++ b/libc/src/stdio/printf_core/float_hex_converter.h
@@ -258,6 +258,16 @@ LIBC_INLINE int convert_float_hex_exp(Writer<write_mode> *writer,
   }
   return WRITE_OK;
 }
+
+#ifdef LIBC_PRINTF_DEFINE_MODULES
+
+#define HANDLE_WRITE_MODE(MODE)                                                \
+  template int convert_float_hex_exp<WriteMode::MODE>(                         \
+      Writer<WriteMode::MODE> * writer, const FormatSection &to_conv);
+#include "src/stdio/printf_core/write_modes.def"
+#undef HANDLE_WRITE_MODE
+
+#endif
 #endif
 
 } // namespace printf_core
diff --git a/libc/src/stdio/printf_core/float_impl.cpp b/libc/src/stdio/printf_core/float_impl.cpp
index 1efea9d52dedb..5b17d7c6ae894 100644
--- a/libc/src/stdio/printf_core/float_impl.cpp
+++ b/libc/src/stdio/printf_core/float_impl.cpp
@@ -14,43 +14,12 @@
 //===----------------------------------------------------------------------===//
 
 #ifdef LIBC_COPT_PRINTF_MODULAR
-#include "src/__support/arg_list.h"
 
 #define LIBC_PRINTF_DEFINE_MODULES
-#include "src/stdio/printf_core/float_dec_converter.h"
-#include "src/stdio/printf_core/float_hex_converter.h"
+#include "src/stdio/printf_core/converter.h"
 #include "src/stdio/printf_core/parser.h"
 
-namespace LIBC_NAMESPACE_DECL {
-namespace printf_core {
-template class Parser<internal::ArgList>;
-template class Parser<internal::DummyArgList<false>>;
-template class Parser<internal::DummyArgList<true>>;
-template class Parser<internal::StructArgList<false>>;
-template class Parser<internal::StructArgList<true>>;
-
-#define INSTANTIATE_CONVERT_FN(NAME)                                           \
-  template int NAME<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>(                   \
-      Writer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> * writer,                 \
-      const FormatSection &to_conv);                                           \
-  template int NAME<WriteMode::FLUSH_TO_STREAM>(                               \
-      Writer<WriteMode::FLUSH_TO_STREAM> * writer,                             \
-      const FormatSection &to_conv);                                           \
-  template int NAME<WriteMode::RESIZE_AND_FILL_BUFF>(                          \
-      Writer<WriteMode::RESIZE_AND_FILL_BUFF> * writer,                        \
-      const FormatSection &to_conv);                                           \
-  template int NAME<WriteMode::RUNTIME_DISPATCH>(                              \
-      Writer<WriteMode::RUNTIME_DISPATCH> * writer,                            \
-      const FormatSection &to_conv)
-
-INSTANTIATE_CONVERT_FN(convert_float_decimal);
-INSTANTIATE_CONVERT_FN(convert_float_dec_exp);
-INSTANTIATE_CONVERT_FN(convert_float_dec_auto);
-INSTANTIATE_CONVERT_FN(convert_float_hex_exp);
-
-} // namespace printf_core
-} // namespace LIBC_NAMESPACE_DECL
-
 // Bring this file into the link if __printf_float is referenced.
 extern "C" void __printf_float() {}
+
 #endif
diff --git a/libc/src/stdio/printf_core/parser.h b/libc/src/stdio/printf_core/parser.h
index 2d9d70775628a..b461dfff02c42 100644
--- a/libc/src/stdio/printf_core/parser.h
+++ b/libc/src/stdio/printf_core/parser.h
@@ -734,6 +734,28 @@ Parser<ArgParser>::advance_arg_if_float(TypeDesc cur_type_desc) {
     return false;
   return true;
 }
+
+#ifdef LIBC_PRINTF_DEFINE_MODULES
+
+#define HANDLE_ARG_LIST_TYPE(TYPE)                                             \
+  template void Parser<internal::TYPE>::write_float_arg_val(                   \
+      FormatSection &section, LengthModifier lm, size_t conv_index);
+#include "src/__support/arg_list_types.def"
+#undef HANDLE_ARG_LIST_TYPE
+
+#define HANDLE_ARG_LIST_TYPE(TYPE)                                             \
+  template TypeDesc Parser<internal::TYPE>::float_type_desc(LengthModifier lm);
+#include "src/__support/arg_list_types.def"
+#undef HANDLE_ARG_LIST_TYPE
+
+#define HANDLE_ARG_LIST_TYPE(TYPE)                                             \
+  template bool Parser<internal::TYPE>::advance_arg_if_float(                  \
+      TypeDesc cur_type_desc);
+#include "src/__support/arg_list_types.def"
+#undef HANDLE_ARG_LIST_TYPE
+
+#endif
+
 #endif
 
 } // namespace printf_core
diff --git a/libc/src/stdio/printf_core/write_modes.def b/libc/src/stdio/printf_core/write_modes.def
new file mode 100644
index 0000000000000..fdce4d0241f99
--- /dev/null
+++ b/libc/src/stdio/printf_core/write_modes.def
@@ -0,0 +1,12 @@
+//===-- printf write mode definitions ----------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+HANDLE_WRITE_MODE(FILL_BUFF_AND_DROP_OVERFLOW)
+HANDLE_WRITE_MODE(FLUSH_TO_STREAM)
+HANDLE_WRITE_MODE(RESIZE_AND_FILL_BUFF)
+HANDLE_WRITE_MODE(RUNTIME_DISPATCH)
diff --git a/libc/src/stdio/printf_core/writer.h b/libc/src/stdio/printf_core/writer.h
index cb45b105597d1..b82792e4d5e02 100644
--- a/libc/src/stdio/printf_core/writer.h
+++ b/libc/src/stdio/printf_core/writer.h
@@ -21,12 +21,11 @@
 namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
+#define HANDLE_WRITE_MODE(MODE) MODE,
 enum class WriteMode {
-  FILL_BUFF_AND_DROP_OVERFLOW,
-  FLUSH_TO_STREAM,
-  RESIZE_AND_FILL_BUFF,
-  RUNTIME_DISPATCH,
+#include "src/stdio/printf_core/write_modes.def"
 };
+#undef HANDLE_WRITE_MODE
 
 // Helper to omit the template argument if we are using runtime dispatch and
 // avoid multiple copies of the converter functions.

>From c7dd4c6c24002fca057bf75d45a656c5748419a5 Mon Sep 17 00:00:00 2001
From: Daniel Thornburgh <dthorn at google.com>
Date: Thu, 5 Feb 2026 13:48:24 -0800
Subject: [PATCH 8/8] LIBC_PRINTF_MODULE macro to declare *and* define modules

---
 .../stdio/printf_core/float_dec_converter.h   | 157 +++---
 .../printf_core/float_dec_converter_limited.h |  55 +-
 .../stdio/printf_core/float_hex_converter.h   | 469 +++++++++---------
 libc/src/stdio/printf_core/parser.h           |  70 ++-
 libc/src/stdio/printf_core/printf_config.h    |  16 +-
 5 files changed, 376 insertions(+), 391 deletions(-)

diff --git a/libc/src/stdio/printf_core/float_dec_converter.h b/libc/src/stdio/printf_core/float_dec_converter.h
index 583570b1e9236..99cc38adab512 100644
--- a/libc/src/stdio/printf_core/float_dec_converter.h
+++ b/libc/src/stdio/printf_core/float_dec_converter.h
@@ -1122,88 +1122,87 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<write_mode> *writer,
   }
 }
 
-template <WriteMode write_mode>
-LIBC_PRINTF_MODULE_DECL int convert_float_decimal(Writer<write_mode> *writer,
-                                                  const FormatSection &to_conv);
-template <WriteMode write_mode>
-LIBC_PRINTF_MODULE_DECL int convert_float_dec_exp(Writer<write_mode> *writer,
-                                                  const FormatSection &to_conv);
-template <WriteMode write_mode>
-LIBC_PRINTF_MODULE_DECL int
-convert_float_dec_auto(Writer<write_mode> *writer,
-                       const FormatSection &to_conv);
-
-#if !defined(LIBC_COPT_PRINTF_MODULAR) || defined(LIBC_PRINTF_DEFINE_MODULES)
 // TODO: unify the float converters to remove the duplicated checks for inf/nan.
 
-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;
-    fputil::FPBits<long double> float_bits(float_raw);
-    if (!float_bits.is_inf_or_nan()) {
-      return convert_float_decimal_typed<long double>(writer, to_conv,
-                                                      float_bits);
-    }
-  } else {
-    fputil::FPBits<double>::StorageType float_raw =
-        static_cast<fputil::FPBits<double>::StorageType>(to_conv.conv_val_raw);
-    fputil::FPBits<double> float_bits(float_raw);
-    if (!float_bits.is_inf_or_nan()) {
-      return convert_float_decimal_typed<double>(writer, to_conv, float_bits);
-    }
-  }
-
-  return convert_inf_nan(writer, to_conv);
-}
-
-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;
-    fputil::FPBits<long double> float_bits(float_raw);
-    if (!float_bits.is_inf_or_nan()) {
-      return convert_float_dec_exp_typed<long double>(writer, to_conv,
-                                                      float_bits);
-    }
-  } else {
-    fputil::FPBits<double>::StorageType float_raw =
-        static_cast<fputil::FPBits<double>::StorageType>(to_conv.conv_val_raw);
-    fputil::FPBits<double> float_bits(float_raw);
-    if (!float_bits.is_inf_or_nan()) {
-      return convert_float_dec_exp_typed<double>(writer, to_conv, float_bits);
-    }
-  }
-
-  return convert_inf_nan(writer, to_conv);
-}
-
-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;
-    fputil::FPBits<long double> float_bits(float_raw);
-    if (!float_bits.is_inf_or_nan()) {
-      return convert_float_dec_auto_typed<long double>(writer, to_conv,
-                                                       float_bits);
-    }
-  } else {
-    fputil::FPBits<double>::StorageType float_raw =
-        static_cast<fputil::FPBits<double>::StorageType>(to_conv.conv_val_raw);
-    fputil::FPBits<double> float_bits(float_raw);
-    if (!float_bits.is_inf_or_nan()) {
-      return convert_float_dec_auto_typed<double>(writer, to_conv, float_bits);
-    }
-  }
-
-  return convert_inf_nan(writer, to_conv);
-}
+LIBC_PRINTF_MODULE((template <WriteMode write_mode>
+                    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;
+                       fputil::FPBits<long double> float_bits(float_raw);
+                       if (!float_bits.is_inf_or_nan()) {
+                         return convert_float_decimal_typed<long double>(
+                             writer, to_conv, float_bits);
+                       }
+                     } else {
+                       fputil::FPBits<double>::StorageType float_raw =
+                           static_cast<fputil::FPBits<double>::StorageType>(
+                               to_conv.conv_val_raw);
+                       fputil::FPBits<double> float_bits(float_raw);
+                       if (!float_bits.is_inf_or_nan()) {
+                         return convert_float_decimal_typed<double>(
+                             writer, to_conv, float_bits);
+                       }
+                     }
+
+                     return convert_inf_nan(writer, to_conv);
+                   })
+
+LIBC_PRINTF_MODULE((template <WriteMode write_mode>
+                    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;
+                       fputil::FPBits<long double> float_bits(float_raw);
+                       if (!float_bits.is_inf_or_nan()) {
+                         return convert_float_dec_exp_typed<long double>(
+                             writer, to_conv, float_bits);
+                       }
+                     } else {
+                       fputil::FPBits<double>::StorageType float_raw =
+                           static_cast<fputil::FPBits<double>::StorageType>(
+                               to_conv.conv_val_raw);
+                       fputil::FPBits<double> float_bits(float_raw);
+                       if (!float_bits.is_inf_or_nan()) {
+                         return convert_float_dec_exp_typed<double>(
+                             writer, to_conv, float_bits);
+                       }
+                     }
+
+                     return convert_inf_nan(writer, to_conv);
+                   })
+
+LIBC_PRINTF_MODULE((template <WriteMode write_mode>
+                    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;
+                       fputil::FPBits<long double> float_bits(float_raw);
+                       if (!float_bits.is_inf_or_nan()) {
+                         return convert_float_dec_auto_typed<long double>(
+                             writer, to_conv, float_bits);
+                       }
+                     } else {
+                       fputil::FPBits<double>::StorageType float_raw =
+                           static_cast<fputil::FPBits<double>::StorageType>(
+                               to_conv.conv_val_raw);
+                       fputil::FPBits<double> float_bits(float_raw);
+                       if (!float_bits.is_inf_or_nan()) {
+                         return convert_float_dec_auto_typed<double>(
+                             writer, to_conv, float_bits);
+                       }
+                     }
+
+                     return convert_inf_nan(writer, to_conv);
+                   })
 
 #ifdef LIBC_PRINTF_DEFINE_MODULES
-
 #define HANDLE_WRITE_MODE(MODE)                                                \
   template int convert_float_decimal<WriteMode::MODE>(                         \
       Writer<WriteMode::MODE> * writer, const FormatSection &to_conv);
@@ -1221,8 +1220,6 @@ LIBC_INLINE int convert_float_dec_auto(Writer<write_mode> *writer,
       Writer<WriteMode::MODE> * writer, const FormatSection &to_conv);
 #include "src/stdio/printf_core/write_modes.def"
 #undef HANDLE_WRITE_MODE
-
-#endif
 #endif
 
 } // namespace printf_core
diff --git a/libc/src/stdio/printf_core/float_dec_converter_limited.h b/libc/src/stdio/printf_core/float_dec_converter_limited.h
index e63b5a884bb54..6d393a7f95ce4 100644
--- a/libc/src/stdio/printf_core/float_dec_converter_limited.h
+++ b/libc/src/stdio/printf_core/float_dec_converter_limited.h
@@ -676,38 +676,31 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<write_mode> *writer,
   return convert_float_typed<T>(writer, to_conv, float_bits, ConversionType::G);
 }
 
-template <WriteMode write_mode>
-LIBC_PRINTF_MODULE_DECL int convert_float_decimal(Writer<write_mode> *writer,
-                                                  const FormatSection &to_conv);
-template <WriteMode write_mode>
-LIBC_PRINTF_MODULE_DECL int convert_float_dec_exp(Writer<write_mode> *writer,
-                                                  const FormatSection &to_conv);
-template <WriteMode write_mode>
-LIBC_PRINTF_MODULE_DECL int
-convert_float_dec_auto(Writer<write_mode> *writer,
-                       const FormatSection &to_conv);
-
-#if !defined(LIBC_COPT_PRINTF_MODULAR) || defined(LIBC_PRINTF_DEFINE_MODULES)
-template <WriteMode write_mode>
-LIBC_INLINE int convert_float_decimal(Writer<write_mode> *writer,
-                                      const FormatSection &to_conv) {
-  return convert_float_outer(writer, to_conv, ConversionType::F);
-}
-
-template <WriteMode write_mode>
-LIBC_INLINE int convert_float_dec_exp(Writer<write_mode> *writer,
-                                      const FormatSection &to_conv) {
-  return convert_float_outer(writer, to_conv, ConversionType::E);
-}
-
-template <WriteMode write_mode>
-LIBC_INLINE int convert_float_dec_auto(Writer<write_mode> *writer,
-                                       const FormatSection &to_conv) {
-  return convert_float_outer(writer, to_conv, ConversionType::G);
-}
+LIBC_PRINTF_MODULE((template <WriteMode write_mode>
+                    int convert_float_decimal(Writer<write_mode> *writer,
+                                              const FormatSection &to_conv)),
+                   {
+                     return convert_float_outer(writer, to_conv,
+                                                ConversionType::F);
+                   })
+
+LIBC_PRINTF_MODULE((template <WriteMode write_mode>
+                    int convert_float_dec_exp(Writer<write_mode> *writer,
+                                              const FormatSection &to_conv)),
+                   {
+                     return convert_float_outer(writer, to_conv,
+                                                ConversionType::E);
+                   })
+
+LIBC_PRINTF_MODULE((template <WriteMode write_mode>
+                    int convert_float_dec_auto(Writer<write_mode> *writer,
+                                               const FormatSection &to_conv)),
+                   {
+                     return convert_float_outer(writer, to_conv,
+                                                ConversionType::G);
+                   })
 
 #ifdef LIBC_PRINTF_DEFINE_MODULES
-
 #define HANDLE_WRITE_MODE(MODE)                                                \
   template int convert_float_decimal<WriteMode::MODE>(                         \
       Writer<WriteMode::MODE> * writer, const FormatSection &to_conv);
@@ -725,8 +718,6 @@ LIBC_INLINE int convert_float_dec_auto(Writer<write_mode> *writer,
       Writer<WriteMode::MODE> * writer, const FormatSection &to_conv);
 #include "src/stdio/printf_core/write_modes.def"
 #undef HANDLE_WRITE_MODE
-
-#endif
 #endif
 
 } // namespace printf_core
diff --git a/libc/src/stdio/printf_core/float_hex_converter.h b/libc/src/stdio/printf_core/float_hex_converter.h
index 1cd4bdeefd6c8..de42c4d72676b 100644
--- a/libc/src/stdio/printf_core/float_hex_converter.h
+++ b/libc/src/stdio/printf_core/float_hex_converter.h
@@ -25,249 +25,246 @@
 namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
 
-template <WriteMode write_mode>
-LIBC_PRINTF_MODULE_DECL int convert_float_hex_exp(Writer<write_mode> *writer,
-                                                  const FormatSection &to_conv);
-
-#if !defined(LIBC_COPT_PRINTF_MODULAR) || defined(LIBC_PRINTF_DEFINE_MODULES)
-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;
-
-  bool is_negative;
-  int exponent;
-  StorageType mantissa;
-  bool is_inf_or_nan;
-  uint32_t fraction_bits;
-  if (to_conv.length_modifier == LengthModifier::L) {
-    fraction_bits = LDBits::FRACTION_LEN;
-    LDBits::StorageType float_raw = to_conv.conv_val_raw;
-    LDBits float_bits(float_raw);
-    is_negative = float_bits.is_neg();
-    exponent = float_bits.get_explicit_exponent();
-    mantissa = float_bits.get_explicit_mantissa();
-    is_inf_or_nan = float_bits.is_inf_or_nan();
-  } else {
-    using LBits = fputil::FPBits<double>;
-    fraction_bits = LBits::FRACTION_LEN;
-    LBits::StorageType float_raw =
-        static_cast<LBits::StorageType>(to_conv.conv_val_raw);
-    LBits float_bits(float_raw);
-    is_negative = float_bits.is_neg();
-    exponent = float_bits.get_explicit_exponent();
-    mantissa = float_bits.get_explicit_mantissa();
-    is_inf_or_nan = float_bits.is_inf_or_nan();
-  }
-
-  if (is_inf_or_nan)
-    return convert_inf_nan(writer, to_conv);
-
-  char sign_char = 0;
-
-  if (is_negative)
-    sign_char = '-';
-  else if ((to_conv.flags & FormatFlags::FORCE_SIGN) == FormatFlags::FORCE_SIGN)
-    sign_char = '+'; // FORCE_SIGN has precedence over SPACE_PREFIX
-  else if ((to_conv.flags & FormatFlags::SPACE_PREFIX) ==
-           FormatFlags::SPACE_PREFIX)
-    sign_char = ' ';
-
-  constexpr size_t BITS_IN_HEX_DIGIT = 4;
-
-  // This is to handle situations where the mantissa isn't an even number of hex
-  // digits. This is primarily relevant for x86 80 bit long doubles, which have
-  // 63 bit mantissas. In the case where the mantissa is 0, however, the
-  // exponent should stay as 0.
-  if (fraction_bits % BITS_IN_HEX_DIGIT != 0 && mantissa > 0) {
-    exponent -= fraction_bits % BITS_IN_HEX_DIGIT;
-  }
-
-  // This is the max number of digits it can take to represent the mantissa.
-  // Since the number is in bits, we divide by 4, and then add one to account
-  // for the extra implicit bit. We use the larger of the two possible values
-  // since the size must be constant.
-  constexpr size_t MANT_BUFF_LEN =
-      (LDBits::FRACTION_LEN / BITS_IN_HEX_DIGIT) + 1;
-  char mant_buffer[MANT_BUFF_LEN];
-
-  size_t mant_len = (fraction_bits / BITS_IN_HEX_DIGIT) + 1;
-
-  // Precision only tracks the number of digits after the hexadecimal point, so
-  // we have to add one to account for the digit before the hexadecimal point.
-  if (to_conv.precision + 1 < static_cast<int>(mant_len) &&
-      to_conv.precision + 1 > 0) {
-    const size_t intended_digits = to_conv.precision + 1;
-    const size_t shift_amount =
-        (mant_len - intended_digits) * BITS_IN_HEX_DIGIT;
-
-    const StorageType truncated_bits =
-        mantissa & ((StorageType(1) << shift_amount) - 1);
-    const StorageType halfway_const = StorageType(1) << (shift_amount - 1);
-
-    mantissa >>= shift_amount;
-
-    switch (fputil::quick_get_round()) {
-    case FE_TONEAREST:
-      // Round to nearest, if it's exactly halfway then round to even.
-      if (truncated_bits > halfway_const)
-        ++mantissa;
-      else if (truncated_bits == halfway_const)
-        mantissa = mantissa + (mantissa & 1);
-      break;
-    case FE_DOWNWARD:
-      if (truncated_bits > 0 && is_negative)
-        ++mantissa;
-      break;
-    case FE_UPWARD:
-      if (truncated_bits > 0 && !is_negative)
-        ++mantissa;
-      break;
-    case FE_TOWARDZERO:
-      break;
-    }
-
-    // If the rounding caused an overflow, shift the mantissa and adjust the
-    // exponent to match.
-    if (mantissa >= (StorageType(1) << (intended_digits * BITS_IN_HEX_DIGIT))) {
-      mantissa >>= BITS_IN_HEX_DIGIT;
-      exponent += BITS_IN_HEX_DIGIT;
-    }
-
-    mant_len = intended_digits;
-  }
-
-  size_t mant_cur = mant_len;
-  size_t first_non_zero = 1;
-  for (; mant_cur > 0; --mant_cur, mantissa >>= 4) {
-    char mant_mod_16 = static_cast<char>(mantissa % 16);
-    char new_digit = internal::int_to_b36_char(mant_mod_16);
-    if (internal::isupper(to_conv.conv_name))
-      new_digit = internal::toupper(new_digit);
-    mant_buffer[mant_cur - 1] = new_digit;
-    if (new_digit != '0' && first_non_zero < mant_cur)
-      first_non_zero = mant_cur;
-  }
-
-  size_t mant_digits = first_non_zero;
-  if (to_conv.precision >= 0)
-    mant_digits = mant_len;
-
-  // This approximates the number of digits it will take to represent the
-  // exponent. The calculation is ceil((bits * 5) / 16). Floor also works, but
-  // only on exact multiples of 16. We add 1 for the sign.
-  // Relevant sizes:
-  // 15 -> 5
-  // 11 -> 4
-  // 8  -> 3
-  constexpr size_t EXP_LEN = (((LDBits::EXP_LEN * 5) + 15) / 16) + 1;
-  char exp_buffer[EXP_LEN];
-
-  bool exp_is_negative = false;
-  if (exponent < 0) {
-    exp_is_negative = true;
-    exponent = -exponent;
-  }
-
-  size_t exp_cur = EXP_LEN;
-  for (; exponent > 0; --exp_cur, exponent /= 10) {
-    exp_buffer[exp_cur - 1] = internal::int_to_b36_char(exponent % 10);
-  }
-  if (exp_cur == EXP_LEN) { // if nothing else was written, write a 0.
-    exp_buffer[EXP_LEN - 1] = '0';
-    exp_cur = EXP_LEN - 1;
-  }
-
-  exp_buffer[exp_cur - 1] = exp_is_negative ? '-' : '+';
-  --exp_cur;
-
-  // these are signed to prevent underflow due to negative values. The eventual
-  // values will always be non-negative.
-  size_t trailing_zeroes = 0;
-  int padding;
-
-  // prefix is "0x", and always appears.
-  constexpr size_t PREFIX_LEN = 2;
-  char prefix[PREFIX_LEN];
-  prefix[0] = '0';
-  prefix[1] = internal::islower(to_conv.conv_name) ? 'x' : 'X';
-  const cpp::string_view prefix_str(prefix, PREFIX_LEN);
-
-  // If the precision is greater than the actual result, pad with 0s
-  if (to_conv.precision > static_cast<int>(mant_digits - 1))
-    trailing_zeroes = to_conv.precision - (mant_digits - 1);
-
-  bool has_hexadecimal_point =
-      (mant_digits > 1) || ((to_conv.flags & FormatFlags::ALTERNATE_FORM) ==
-                            FormatFlags::ALTERNATE_FORM);
-  constexpr cpp::string_view HEXADECIMAL_POINT(".");
-
-  // This is for the letter 'p' before the exponent.
-  const char exp_separator = internal::islower(to_conv.conv_name) ? 'p' : 'P';
-  constexpr int EXP_SEPARATOR_LEN = 1;
-
-  padding = static_cast<int>(to_conv.min_width - (sign_char > 0 ? 1 : 0) -
-                             PREFIX_LEN - mant_digits - trailing_zeroes -
-                             static_cast<int>(has_hexadecimal_point) -
-                             EXP_SEPARATOR_LEN - (EXP_LEN - exp_cur));
-  if (padding < 0)
-    padding = 0;
-
-  if ((to_conv.flags & FormatFlags::LEFT_JUSTIFIED) ==
-      FormatFlags::LEFT_JUSTIFIED) {
-    // The pattern is (sign), 0x, digit, (.), (other digits), (zeroes), p,
-    // exponent, (spaces)
-    if (sign_char > 0)
-      RET_IF_RESULT_NEGATIVE(writer->write(sign_char));
-    RET_IF_RESULT_NEGATIVE(writer->write(prefix_str));
-    RET_IF_RESULT_NEGATIVE(writer->write(mant_buffer[0]));
-    if (has_hexadecimal_point)
-      RET_IF_RESULT_NEGATIVE(writer->write(HEXADECIMAL_POINT));
-    if (mant_digits > 1)
-      RET_IF_RESULT_NEGATIVE(writer->write({mant_buffer + 1, mant_digits - 1}));
-    if (trailing_zeroes > 0)
-      RET_IF_RESULT_NEGATIVE(writer->write('0', trailing_zeroes));
-    RET_IF_RESULT_NEGATIVE(writer->write(exp_separator));
-    RET_IF_RESULT_NEGATIVE(
-        writer->write({exp_buffer + exp_cur, EXP_LEN - exp_cur}));
-    if (padding > 0)
-      RET_IF_RESULT_NEGATIVE(writer->write(' ', padding));
-  } else {
-    // The pattern is (spaces), (sign), 0x, (zeroes), digit, (.), (other
-    // digits), (zeroes), p, exponent
-    if ((padding > 0) && ((to_conv.flags & FormatFlags::LEADING_ZEROES) !=
-                          FormatFlags::LEADING_ZEROES))
-      RET_IF_RESULT_NEGATIVE(writer->write(' ', padding));
-    if (sign_char > 0)
-      RET_IF_RESULT_NEGATIVE(writer->write(sign_char));
-    RET_IF_RESULT_NEGATIVE(writer->write(prefix_str));
-    if ((padding > 0) && ((to_conv.flags & FormatFlags::LEADING_ZEROES) ==
-                          FormatFlags::LEADING_ZEROES))
-      RET_IF_RESULT_NEGATIVE(writer->write('0', padding));
-    RET_IF_RESULT_NEGATIVE(writer->write(mant_buffer[0]));
-    if (has_hexadecimal_point)
-      RET_IF_RESULT_NEGATIVE(writer->write(HEXADECIMAL_POINT));
-    if (mant_digits > 1)
-      RET_IF_RESULT_NEGATIVE(writer->write({mant_buffer + 1, mant_digits - 1}));
-    if (trailing_zeroes > 0)
-      RET_IF_RESULT_NEGATIVE(writer->write('0', trailing_zeroes));
-    RET_IF_RESULT_NEGATIVE(writer->write(exp_separator));
-    RET_IF_RESULT_NEGATIVE(
-        writer->write({exp_buffer + exp_cur, EXP_LEN - exp_cur}));
-  }
-  return WRITE_OK;
-}
+LIBC_PRINTF_MODULE(
+    (template <WriteMode write_mode>
+     int convert_float_hex_exp(Writer<write_mode> *writer,
+                               const FormatSection &to_conv)),
+    {
+      using LDBits = fputil::FPBits<long double>;
+      using StorageType = LDBits::StorageType;
+
+      bool is_negative;
+      int exponent;
+      StorageType mantissa;
+      bool is_inf_or_nan;
+      uint32_t fraction_bits;
+      if (to_conv.length_modifier == LengthModifier::L) {
+        fraction_bits = LDBits::FRACTION_LEN;
+        LDBits::StorageType float_raw = to_conv.conv_val_raw;
+        LDBits float_bits(float_raw);
+        is_negative = float_bits.is_neg();
+        exponent = float_bits.get_explicit_exponent();
+        mantissa = float_bits.get_explicit_mantissa();
+        is_inf_or_nan = float_bits.is_inf_or_nan();
+      } else {
+        using LBits = fputil::FPBits<double>;
+        fraction_bits = LBits::FRACTION_LEN;
+        LBits::StorageType float_raw =
+            static_cast<LBits::StorageType>(to_conv.conv_val_raw);
+        LBits float_bits(float_raw);
+        is_negative = float_bits.is_neg();
+        exponent = float_bits.get_explicit_exponent();
+        mantissa = float_bits.get_explicit_mantissa();
+        is_inf_or_nan = float_bits.is_inf_or_nan();
+      }
+
+      if (is_inf_or_nan)
+        return convert_inf_nan(writer, to_conv);
+
+      char sign_char = 0;
+
+      if (is_negative)
+        sign_char = '-';
+      else if ((to_conv.flags & FormatFlags::FORCE_SIGN) ==
+               FormatFlags::FORCE_SIGN)
+        sign_char = '+'; // FORCE_SIGN has precedence over SPACE_PREFIX
+      else if ((to_conv.flags & FormatFlags::SPACE_PREFIX) ==
+               FormatFlags::SPACE_PREFIX)
+        sign_char = ' ';
+
+      constexpr size_t BITS_IN_HEX_DIGIT = 4;
+
+      // This is to handle situations where the mantissa isn't an even number of
+      // hex digits. This is primarily relevant for x86 80 bit long doubles,
+      // which have 63 bit mantissas. In the case where the mantissa is 0,
+      // however, the exponent should stay as 0.
+      if (fraction_bits % BITS_IN_HEX_DIGIT != 0 && mantissa > 0) {
+        exponent -= fraction_bits % BITS_IN_HEX_DIGIT;
+      }
+
+      // This is the max number of digits it can take to represent the mantissa.
+      // Since the number is in bits, we divide by 4, and then add one to
+      // account for the extra implicit bit. We use the larger of the two
+      // possible values since the size must be constant.
+      constexpr size_t MANT_BUFF_LEN =
+          (LDBits::FRACTION_LEN / BITS_IN_HEX_DIGIT) + 1;
+      char mant_buffer[MANT_BUFF_LEN];
+
+      size_t mant_len = (fraction_bits / BITS_IN_HEX_DIGIT) + 1;
+
+      // Precision only tracks the number of digits after the hexadecimal point,
+      // so we have to add one to account for the digit before the hexadecimal
+      // point.
+      if (to_conv.precision + 1 < static_cast<int>(mant_len) &&
+          to_conv.precision + 1 > 0) {
+        const size_t intended_digits = to_conv.precision + 1;
+        const size_t shift_amount =
+            (mant_len - intended_digits) * BITS_IN_HEX_DIGIT;
+
+        const StorageType truncated_bits =
+            mantissa & ((StorageType(1) << shift_amount) - 1);
+        const StorageType halfway_const = StorageType(1) << (shift_amount - 1);
+
+        mantissa >>= shift_amount;
+
+        switch (fputil::quick_get_round()) {
+        case FE_TONEAREST:
+          // Round to nearest, if it's exactly halfway then round to even.
+          if (truncated_bits > halfway_const)
+            ++mantissa;
+          else if (truncated_bits == halfway_const)
+            mantissa = mantissa + (mantissa & 1);
+          break;
+        case FE_DOWNWARD:
+          if (truncated_bits > 0 && is_negative)
+            ++mantissa;
+          break;
+        case FE_UPWARD:
+          if (truncated_bits > 0 && !is_negative)
+            ++mantissa;
+          break;
+        case FE_TOWARDZERO:
+          break;
+        }
+
+        // If the rounding caused an overflow, shift the mantissa and adjust the
+        // exponent to match.
+        if (mantissa >=
+            (StorageType(1) << (intended_digits * BITS_IN_HEX_DIGIT))) {
+          mantissa >>= BITS_IN_HEX_DIGIT;
+          exponent += BITS_IN_HEX_DIGIT;
+        }
+
+        mant_len = intended_digits;
+      }
+
+      size_t mant_cur = mant_len;
+      size_t first_non_zero = 1;
+      for (; mant_cur > 0; --mant_cur, mantissa >>= 4) {
+        char mant_mod_16 = static_cast<char>(mantissa % 16);
+        char new_digit = internal::int_to_b36_char(mant_mod_16);
+        if (internal::isupper(to_conv.conv_name))
+          new_digit = internal::toupper(new_digit);
+        mant_buffer[mant_cur - 1] = new_digit;
+        if (new_digit != '0' && first_non_zero < mant_cur)
+          first_non_zero = mant_cur;
+      }
+
+      size_t mant_digits = first_non_zero;
+      if (to_conv.precision >= 0)
+        mant_digits = mant_len;
+
+      // This approximates the number of digits it will take to represent the
+      // exponent. The calculation is ceil((bits * 5) / 16). Floor also works,
+      // but only on exact multiples of 16. We add 1 for the sign. Relevant
+      // sizes: 15 -> 5 11 -> 4 8  -> 3
+      constexpr size_t EXP_LEN = (((LDBits::EXP_LEN * 5) + 15) / 16) + 1;
+      char exp_buffer[EXP_LEN];
+
+      bool exp_is_negative = false;
+      if (exponent < 0) {
+        exp_is_negative = true;
+        exponent = -exponent;
+      }
+
+      size_t exp_cur = EXP_LEN;
+      for (; exponent > 0; --exp_cur, exponent /= 10) {
+        exp_buffer[exp_cur - 1] = internal::int_to_b36_char(exponent % 10);
+      }
+      if (exp_cur == EXP_LEN) { // if nothing else was written, write a 0.
+        exp_buffer[EXP_LEN - 1] = '0';
+        exp_cur = EXP_LEN - 1;
+      }
+
+      exp_buffer[exp_cur - 1] = exp_is_negative ? '-' : '+';
+      --exp_cur;
+
+      // these are signed to prevent underflow due to negative values. The
+      // eventual values will always be non-negative.
+      size_t trailing_zeroes = 0;
+      int padding;
+
+      // prefix is "0x", and always appears.
+      constexpr size_t PREFIX_LEN = 2;
+      char prefix[PREFIX_LEN];
+      prefix[0] = '0';
+      prefix[1] = internal::islower(to_conv.conv_name) ? 'x' : 'X';
+      const cpp::string_view prefix_str(prefix, PREFIX_LEN);
+
+      // If the precision is greater than the actual result, pad with 0s
+      if (to_conv.precision > static_cast<int>(mant_digits - 1))
+        trailing_zeroes = to_conv.precision - (mant_digits - 1);
+
+      bool has_hexadecimal_point =
+          (mant_digits > 1) || ((to_conv.flags & FormatFlags::ALTERNATE_FORM) ==
+                                FormatFlags::ALTERNATE_FORM);
+      constexpr cpp::string_view HEXADECIMAL_POINT(".");
+
+      // This is for the letter 'p' before the exponent.
+      const char exp_separator =
+          internal::islower(to_conv.conv_name) ? 'p' : 'P';
+      constexpr int EXP_SEPARATOR_LEN = 1;
+
+      padding = static_cast<int>(to_conv.min_width - (sign_char > 0 ? 1 : 0) -
+                                 PREFIX_LEN - mant_digits - trailing_zeroes -
+                                 static_cast<int>(has_hexadecimal_point) -
+                                 EXP_SEPARATOR_LEN - (EXP_LEN - exp_cur));
+      if (padding < 0)
+        padding = 0;
+
+      if ((to_conv.flags & FormatFlags::LEFT_JUSTIFIED) ==
+          FormatFlags::LEFT_JUSTIFIED) {
+        // The pattern is (sign), 0x, digit, (.), (other digits), (zeroes), p,
+        // exponent, (spaces)
+        if (sign_char > 0)
+          RET_IF_RESULT_NEGATIVE(writer->write(sign_char));
+        RET_IF_RESULT_NEGATIVE(writer->write(prefix_str));
+        RET_IF_RESULT_NEGATIVE(writer->write(mant_buffer[0]));
+        if (has_hexadecimal_point)
+          RET_IF_RESULT_NEGATIVE(writer->write(HEXADECIMAL_POINT));
+        if (mant_digits > 1)
+          RET_IF_RESULT_NEGATIVE(
+              writer->write({mant_buffer + 1, mant_digits - 1}));
+        if (trailing_zeroes > 0)
+          RET_IF_RESULT_NEGATIVE(writer->write('0', trailing_zeroes));
+        RET_IF_RESULT_NEGATIVE(writer->write(exp_separator));
+        RET_IF_RESULT_NEGATIVE(
+            writer->write({exp_buffer + exp_cur, EXP_LEN - exp_cur}));
+        if (padding > 0)
+          RET_IF_RESULT_NEGATIVE(writer->write(' ', padding));
+      } else {
+        // The pattern is (spaces), (sign), 0x, (zeroes), digit, (.), (other
+        // digits), (zeroes), p, exponent
+        if ((padding > 0) && ((to_conv.flags & FormatFlags::LEADING_ZEROES) !=
+                              FormatFlags::LEADING_ZEROES))
+          RET_IF_RESULT_NEGATIVE(writer->write(' ', padding));
+        if (sign_char > 0)
+          RET_IF_RESULT_NEGATIVE(writer->write(sign_char));
+        RET_IF_RESULT_NEGATIVE(writer->write(prefix_str));
+        if ((padding > 0) && ((to_conv.flags & FormatFlags::LEADING_ZEROES) ==
+                              FormatFlags::LEADING_ZEROES))
+          RET_IF_RESULT_NEGATIVE(writer->write('0', padding));
+        RET_IF_RESULT_NEGATIVE(writer->write(mant_buffer[0]));
+        if (has_hexadecimal_point)
+          RET_IF_RESULT_NEGATIVE(writer->write(HEXADECIMAL_POINT));
+        if (mant_digits > 1)
+          RET_IF_RESULT_NEGATIVE(
+              writer->write({mant_buffer + 1, mant_digits - 1}));
+        if (trailing_zeroes > 0)
+          RET_IF_RESULT_NEGATIVE(writer->write('0', trailing_zeroes));
+        RET_IF_RESULT_NEGATIVE(writer->write(exp_separator));
+        RET_IF_RESULT_NEGATIVE(
+            writer->write({exp_buffer + exp_cur, EXP_LEN - exp_cur}));
+      }
+      return WRITE_OK;
+    })
 
 #ifdef LIBC_PRINTF_DEFINE_MODULES
-
 #define HANDLE_WRITE_MODE(MODE)                                                \
   template int convert_float_hex_exp<WriteMode::MODE>(                         \
       Writer<WriteMode::MODE> * writer, const FormatSection &to_conv);
 #include "src/stdio/printf_core/write_modes.def"
 #undef HANDLE_WRITE_MODE
-
-#endif
 #endif
 
 } // namespace printf_core
diff --git a/libc/src/stdio/printf_core/parser.h b/libc/src/stdio/printf_core/parser.h
index b461dfff02c42..4667992c4a8b6 100644
--- a/libc/src/stdio/printf_core/parser.h
+++ b/libc/src/stdio/printf_core/parser.h
@@ -14,6 +14,7 @@
 #include "src/__support/CPP/limits.h"
 #include "src/__support/CPP/optional.h"
 #include "src/__support/CPP/type_traits.h"
+#include "src/__support/arg_list.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/str_to_integer.h"
 #include "src/stdio/printf_core/core_structs.h"
@@ -308,11 +309,33 @@ template <typename ArgProvider> class Parser {
     return section;
   }
 
-  LIBC_PRINTF_MODULE_DECL void write_float_arg_val(FormatSection &section,
-                                                   LengthModifier lm,
-                                                   size_t conv_index);
-  LIBC_PRINTF_MODULE_DECL TypeDesc float_type_desc(LengthModifier lm);
-  LIBC_PRINTF_MODULE_DECL bool advance_arg_if_float(TypeDesc cur_type_desc);
+  LIBC_PRINTF_MODULE(
+      (void write_float_arg_val(FormatSection &section, LengthModifier lm,
+                                [[maybe_unused]] size_t conv_index)),
+      {
+        if (lm != LengthModifier::L) {
+          WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, double, conv_index);
+        } else {
+          WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long double, conv_index);
+        }
+      })
+
+  LIBC_PRINTF_MODULE((TypeDesc float_type_desc(LengthModifier lm)), {
+    if (lm != LengthModifier::L)
+      return type_desc_from_type<double>();
+    else
+      return type_desc_from_type<long double>();
+  })
+
+  LIBC_PRINTF_MODULE((bool advance_arg_if_float(TypeDesc cur_type_desc)), {
+    if (cur_type_desc == type_desc_from_type<double>())
+      args_cur.template next_var<double>();
+    else if (cur_type_desc == type_desc_from_type<long double>())
+      args_cur.template next_var<long double>();
+    else
+      return false;
+    return true;
+  })
 
 private:
   // parse_flags parses the flags inside a format string. It assumes that
@@ -702,41 +725,7 @@ template <typename ArgProvider> class Parser {
 #endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
 };
 
-#if !defined(LIBC_COPT_PRINTF_MODULAR) || defined(LIBC_PRINTF_DEFINE_MODULES)
-template <typename ArgParser>
-LIBC_INLINE void
-Parser<ArgParser>::write_float_arg_val(FormatSection &section,
-                                       LengthModifier lm,
-                                       [[maybe_unused]] size_t conv_index) {
-  if (lm != LengthModifier::L) {
-    WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, double, conv_index);
-  } else {
-    WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long double, conv_index);
-  }
-}
-
-template <typename ArgParser>
-LIBC_INLINE TypeDesc Parser<ArgParser>::float_type_desc(LengthModifier lm) {
-  if (lm != LengthModifier::L)
-    return type_desc_from_type<double>();
-  else
-    return type_desc_from_type<long double>();
-}
-
-template <typename ArgParser>
-LIBC_INLINE bool
-Parser<ArgParser>::advance_arg_if_float(TypeDesc cur_type_desc) {
-  if (cur_type_desc == type_desc_from_type<double>())
-    args_cur.template next_var<double>();
-  else if (cur_type_desc == type_desc_from_type<long double>())
-    args_cur.template next_var<long double>();
-  else
-    return false;
-  return true;
-}
-
 #ifdef LIBC_PRINTF_DEFINE_MODULES
-
 #define HANDLE_ARG_LIST_TYPE(TYPE)                                             \
   template void Parser<internal::TYPE>::write_float_arg_val(                   \
       FormatSection &section, LengthModifier lm, size_t conv_index);
@@ -753,9 +742,6 @@ Parser<ArgParser>::advance_arg_if_float(TypeDesc cur_type_desc) {
       TypeDesc cur_type_desc);
 #include "src/__support/arg_list_types.def"
 #undef HANDLE_ARG_LIST_TYPE
-
-#endif
-
 #endif
 
 } // namespace printf_core
diff --git a/libc/src/stdio/printf_core/printf_config.h b/libc/src/stdio/printf_core/printf_config.h
index bb4be8bdd6b1c..5be382a34e619 100644
--- a/libc/src/stdio/printf_core/printf_config.h
+++ b/libc/src/stdio/printf_core/printf_config.h
@@ -49,9 +49,23 @@
 // LIBC_COPT_PRINTF_NO_NULLPTR_CHECKS
 
 #ifdef LIBC_COPT_PRINTF_MODULAR
-#define LIBC_PRINTF_MODULE_DECL [[gnu::weak]]
+#define LIBC_PRINTF_MODULE_DECL __attribute__((weak))
 #else
 #define LIBC_PRINTF_MODULE_DECL LIBC_INLINE
 #endif
 
+// LIBC_PRINTF_MODULE: Defines/declares a printf module.
+//
+// Usage: LIBC_PRINTF_MODULE((<signature>), { <body> })
+//
+// Note that the signature is parenthesized, but the body is not.
+
+#define LIBC_PRINTF_MODULE_UNWRAP(...) __VA_ARGS__
+#if !defined(LIBC_COPT_PRINTF_MODULAR) || defined(LIBC_PRINTF_DEFINE_MODULES)
+#define LIBC_PRINTF_MODULE(SIG, ...) LIBC_PRINTF_MODULE_UNWRAP SIG __VA_ARGS__
+#else
+#define LIBC_PRINTF_MODULE(SIG, ...)                                           \
+  LIBC_PRINTF_MODULE_UNWRAP SIG LIBC_PRINTF_MODULE_DECL;
+#endif
+
 #endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_PRINTF_CONFIG_H



More information about the cfe-commits mailing list