Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: users/mysterymath/modular-printf/clang
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions libc/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
"LIBC_CONF_PRINTF_RUNTIME_DISPATCH": {
"value": true,
"doc": "Use dynamic dispatch for the output mechanism to reduce code size."
},
"LIBC_CONF_PRINTF_MODULAR": {
"value": true,
"doc": "Split printf implementation into modules that can be lazily linked in."
}
},
"scanf": {
Expand Down
1 change: 1 addition & 0 deletions libc/docs/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ to learn about the defaults for your platform and target.
- ``LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_DYADIC_FLOAT``: Use dyadic float for faster and smaller but less accurate printf doubles.
- ``LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_FLOAT320``: Use an alternative printf float implementation based on 320-bit floats
- ``LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE``: Use large table for better printf long double performance.
- ``LIBC_CONF_PRINTF_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).
Expand Down
7 changes: 6 additions & 1 deletion libc/src/stdio/generic/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -412,10 +412,15 @@ if(LLVM_LIBC_FULL_BUILD)
)
endif()

set(printf_srcs printf.cpp)
if (LIBC_CONF_PRINTF_MODULAR)
list(APPEND printf_srcs printf_modular.cpp)
endif()
Comment on lines +416 to +418
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to have __printf_float as a separate entrypoint so that it can be controlled per-platform with the same mechanism as other entrypoints?


add_generic_entrypoint_object(
printf
SRCS
printf.cpp
${printf_srcs}
HDRS
../printf.h
DEPENDS
Expand Down
40 changes: 40 additions & 0 deletions libc/src/stdio/generic/printf_modular.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//===-- Implementation of printf_modular-----------------------------------===//
//
// 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 "src/__support/File/file.h"
#include "src/__support/arg_list.h"
#include "src/__support/macros/config.h"
#include "src/stdio/printf_core/vfprintf_internal.h"

#include "hdr/types/FILE.h"
#include <stdarg.h>

#ifndef LIBC_COPT_STDIO_USE_SYSTEM_FILE
#define PRINTF_STDOUT LIBC_NAMESPACE::stdout
#else // LIBC_COPT_STDIO_USE_SYSTEM_FILE
#define PRINTF_STDOUT ::stdout
#endif // LIBC_COPT_STDIO_USE_SYSTEM_FILE

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);
int ret_val = printf_core::vfprintf_internal_modular(
reinterpret_cast<::FILE *>(PRINTF_STDOUT), format, args);
return ret_val;
}

} // namespace LIBC_NAMESPACE_DECL
1 change: 1 addition & 0 deletions libc/src/stdio/printf.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 6 additions & 1 deletion libc/src/stdio/printf_core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -112,10 +115,12 @@ add_header_library(
libc.src.__support.StringUtil.error_to_string
)

add_header_library(
add_object_library(
printf_main
HDRS
printf_main.h
SRCS
float_impl.cpp
DEPENDS
.parser
.converter
Expand Down
25 changes: 19 additions & 6 deletions libc/src/stdio/printf_core/float_dec_converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -1122,11 +1122,23 @@ 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a separate PR, but can we change all the writer argument to reference instead of pointer?

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>
LIBC_INLINE int convert_float_decimal(Writer<write_mode> *writer,
const FormatSection &to_conv) {
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);
Expand All @@ -1147,8 +1159,8 @@ LIBC_INLINE int convert_float_decimal(Writer<write_mode> *writer,
}

template <WriteMode write_mode>
LIBC_INLINE int convert_float_dec_exp(Writer<write_mode> *writer,
const FormatSection &to_conv) {
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);
Expand All @@ -1169,8 +1181,8 @@ LIBC_INLINE int convert_float_dec_exp(Writer<write_mode> *writer,
}

template <WriteMode write_mode>
LIBC_INLINE int convert_float_dec_auto(Writer<write_mode> *writer,
const FormatSection &to_conv) {
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);
Expand All @@ -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
Expand Down
24 changes: 18 additions & 6 deletions libc/src/stdio/printf_core/float_dec_converter_limited.h
Original file line number Diff line number Diff line change
Expand Up @@ -676,22 +676,34 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<write_mode> *writer,
}

template <WriteMode write_mode>
LIBC_INLINE int convert_float_decimal(Writer<write_mode> *writer,
const FormatSection &to_conv) {
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>
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) {
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) {
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
Expand Down
10 changes: 8 additions & 2 deletions libc/src/stdio/printf_core/float_hex_converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@ namespace LIBC_NAMESPACE_DECL {
namespace printf_core {

template <WriteMode write_mode>
LIBC_INLINE int convert_float_hex_exp(Writer<write_mode> *writer,
const FormatSection &to_conv) {
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>
int convert_float_hex_exp(Writer<write_mode> *writer,
const FormatSection &to_conv) {
using LDBits = fputil::FPBits<long double>;
using StorageType = LDBits::StorageType;

Expand Down Expand Up @@ -254,6 +259,7 @@ LIBC_INLINE int convert_float_hex_exp(Writer<write_mode> *writer,
}
return WRITE_OK;
}
#endif

} // namespace printf_core
} // namespace LIBC_NAMESPACE_DECL
Expand Down
41 changes: 41 additions & 0 deletions libc/src/stdio/printf_core/float_impl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#ifdef LIBC_COPT_PRINTF_MODULAR
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file needs the copyright header.

#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"
Comment on lines +4 to +7
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given this, do you need to modify converter.h or converter_atlas.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() {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One risk with this strategy: what happens if this file is compiled with -ffunction-sections? Then __printf_float might be in a different code section from the template instantiations, and a linker might garbage-collect the functions you actually wanted, treating the weak references to them as not enough reason to keep them.

If I wanted to be sure of this technique, I'd either enforce via compile options that everything in this file is in the same code section, or else add further BFD_RELOC_NONE links from __printf_float to the payload functions, to make sure there's an unbroken chain of non-weak references from the import of __printf_float to the functions that actually do the work.

Copy link
Contributor Author

@mysterymath mysterymath Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One risk with this strategy: what happens if this file is compiled with -ffunction-sections? Then __printf_float might be in a different code section from the template instantiations, and a linker might garbage-collect the functions you actually wanted, treating the weak references to them as not enough reason to keep them.

To my knowledge, this isn't possible. For LLD at least, the weakness of the reference isn't examined when determining whether or not a reference preserves a section. Weakness only controls whether or not an undefined or duplicate reference fails the link and whether an undefined reference extracts archive members. (There's some other differences with shared libraries unimportant to this case.)

I'd be surprised if other linkers differ; wasn't this usage the whole point of allowing undefined weak references?

#endif
56 changes: 43 additions & 13 deletions libc/src/stdio/printf_core/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,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
Expand Down Expand Up @@ -299,6 +295,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
Expand Down Expand Up @@ -474,10 +476,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
Expand Down Expand Up @@ -630,10 +631,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
Expand Down Expand Up @@ -682,6 +680,38 @@ template <typename ArgProvider> class Parser {
#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
};

#ifdef LIBC_PRINTF_DEFINE_MODULAR
template <typename ArgParser>
void Parser<ArgParser>::write_float_arg_val(FormatSection &section,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that these are non-inline functions in a header, do they need to be in an anonymous namespace to avoid ODR issues? Here and elsewhere.

LengthModifier lm,
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>
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>
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

Expand Down
7 changes: 7 additions & 0 deletions libc/src/stdio/printf_core/printf_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading
Loading