[libc-commits] [libc] [libc] Support %lc in printf (PR #169983)
Shubh Pachchigar via libc-commits
libc-commits at lists.llvm.org
Sat Dec 20 02:22:39 PST 2025
https://github.com/shubhe25p updated https://github.com/llvm/llvm-project/pull/169983
>From 306dc7d8a135cf5d4f45e7c68a45d63f12076f03 Mon Sep 17 00:00:00 2001
From: "shubh at DOE" <shubhp at mbm3a24.local>
Date: Sat, 20 Dec 2025 01:37:17 -0800
Subject: [PATCH 1/3] [libc] Support %lc in printf
Add support for %lc in printf by calling internal
wcrtomb function and relevant end-to-end snprintf
test. Additionally, printf parser can also
recognize length modifier. Added a flag to disable
wchar support on windows platform.
---
libc/config/config.json | 4 +++
libc/config/windows/config.json | 8 +++++
libc/docs/configure.rst | 1 +
libc/docs/dev/printf_behavior.rst | 7 ++++
libc/src/stdio/printf_core/CMakeLists.txt | 19 ++++++++++
libc/src/stdio/printf_core/char_converter.h | 32 +++++++++++++++--
libc/src/stdio/printf_core/parser.h | 24 ++++++++++---
libc/test/src/stdio/CMakeLists.txt | 11 ++++++
libc/test/src/stdio/sprintf_test.cpp | 39 +++++++++++++++++++++
9 files changed, 138 insertions(+), 7 deletions(-)
create mode 100644 libc/config/windows/config.json
diff --git a/libc/config/config.json b/libc/config/config.json
index 1c11b9a86d8a4..a5254c66ea472 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -52,6 +52,10 @@
"LIBC_CONF_PRINTF_RUNTIME_DISPATCH": {
"value": true,
"doc": "Use dynamic dispatch for the output mechanism to reduce code size."
+ },
+ "LIBC_CONF_PRINTF_DISABLE_WIDE": {
+ "value": false,
+ "doc": "Disable handling wide characters for printf and friends."
}
},
"scanf": {
diff --git a/libc/config/windows/config.json b/libc/config/windows/config.json
new file mode 100644
index 0000000000000..75bb5e9debdde
--- /dev/null
+++ b/libc/config/windows/config.json
@@ -0,0 +1,8 @@
+{
+ "printf": {
+ "LIBC_CONF_PRINTF_DISABLE_WIDE": {
+ "value": "true",
+ "doc": "Disable handling wide characters for printf and friends."
+ }
+ }
+}
\ No newline at end of file
diff --git a/libc/docs/configure.rst b/libc/docs/configure.rst
index 7c36222c7fe81..1d529ca182940 100644
--- a/libc/docs/configure.rst
+++ b/libc/docs/configure.rst
@@ -48,6 +48,7 @@ to learn about the defaults for your platform and target.
- ``LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_FLOAT320``: Use an alternative printf float implementation based on 320-bit floats
- ``LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE``: Use large table for better printf long double performance.
- ``LIBC_CONF_PRINTF_RUNTIME_DISPATCH``: Use dynamic dispatch for the output mechanism to reduce code size.
+ - ``LIBC_CONF_PRINTF_DISABLE_WIDE``: Disable handling of %lc and %ls on unsupported platforms.
* **"pthread" options**
- ``LIBC_CONF_RAW_MUTEX_DEFAULT_SPIN_COUNT``: Default number of spins before blocking if a mutex is in contention (default to 100).
- ``LIBC_CONF_RWLOCK_DEFAULT_SPIN_COUNT``: Default number of spins before blocking if a rwlock is in contention (default to 100).
diff --git a/libc/docs/dev/printf_behavior.rst b/libc/docs/dev/printf_behavior.rst
index 01ab128a1f238..ba0578aee3fd8 100644
--- a/libc/docs/dev/printf_behavior.rst
+++ b/libc/docs/dev/printf_behavior.rst
@@ -71,6 +71,13 @@ conversions (%r, %k); any fixed point number conversion will be treated as
invalid. This reduces code size. This has no effect if the current compiler does
not support fixed point numbers.
+LIBC_COPT_PRINTF_DISABLE_WIDE
+--------------------------------
+When set, this flag disables support for wide characters (%lc and %ls). Any
+conversions will be ignored. This reduces code size. This will be set by default
+on windows platforms as current printf implementation does not support UTF-16 wide
+characters.
+
LIBC_COPT_PRINTF_NO_NULLPTR_CHECKS
----------------------------------
When set, this flag disables the nullptr checks in %n and %s.
diff --git a/libc/src/stdio/printf_core/CMakeLists.txt b/libc/src/stdio/printf_core/CMakeLists.txt
index 624129b2b36e7..c5c6809d5f940 100644
--- a/libc/src/stdio/printf_core/CMakeLists.txt
+++ b/libc/src/stdio/printf_core/CMakeLists.txt
@@ -43,6 +43,19 @@ if(NOT TARGET ${target_error_mapper})
set(target_error_mapper libc.src.stdio.printf_core.generic.error_mapper)
endif()
+if(LIBC_CONF_PRINTF_DISABLE_WIDE)
+ set(wchar_deps "")
+ list(APPEND wchar_copts "-DLIBC_COPT_PRINTF_DISABLE_WIDE")
+else()
+ set(wchar_deps
+ libc.hdr.types.wchar_t
+ libc.hdr.types.wint_t
+ libc.hdr.wchar_macros
+ libc.src.__support.wchar.wcrtomb
+ libc.src.__support.wchar.mbstate
+ )
+endif()
+
add_header_library(
printf_config
HDRS
@@ -76,6 +89,9 @@ add_header_library(
libc.src.__support.CPP.string_view
libc.src.__support.CPP.type_traits
libc.src.__support.common
+ ${wchar_deps}
+ COMPILE_OPTIONS
+ ${wchar_copts}
)
add_header_library(
@@ -125,6 +141,9 @@ add_header_library(
libc.src.__support.uint128
libc.src.__support.StringUtil.error_to_string
libc.src.string.memory_utils.inline_memcpy
+ ${wchar_deps}
+ COMPILE_OPTIONS
+ ${wchar_copts}
)
add_header_library(
diff --git a/libc/src/stdio/printf_core/char_converter.h b/libc/src/stdio/printf_core/char_converter.h
index fd2eb2553887a..53417f3e050d3 100644
--- a/libc/src/stdio/printf_core/char_converter.h
+++ b/libc/src/stdio/printf_core/char_converter.h
@@ -1,4 +1,4 @@
-//===-- String Converter for printf -----------------------------*- C++ -*-===//
+//===-- Character Converter for printf --------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -9,6 +9,14 @@
#ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_CHAR_CONVERTER_H
#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_CHAR_CONVERTER_H
+#ifndef LIBC_COPT_PRINTF_DISABLE_WIDE
+#include "hdr/types/wchar_t.h"
+#include "hdr/types/wint_t.h"
+#include "hdr/wchar_macros.h"
+#include "src/__support/wchar/mbstate.h"
+#include "src/__support/wchar/wcrtomb.h"
+#endif // LIBC_COPT_PRINTF_DISABLE_WIDE
+
#include "src/__support/macros/config.h"
#include "src/stdio/printf_core/converter_utils.h"
#include "src/stdio/printf_core/core_structs.h"
@@ -33,7 +41,27 @@ LIBC_INLINE int convert_char(Writer<write_mode> *writer,
RET_IF_RESULT_NEGATIVE(writer->write(' ', padding_spaces));
}
- RET_IF_RESULT_NEGATIVE(writer->write(c));
+#ifndef LIBC_COPT_PRINTF_DISABLE_WIDE
+ if (to_conv.length_modifier == LengthModifier::l) {
+ wint_t wi = static_cast<wint_t>(to_conv.conv_val_raw);
+
+ if (wi == WEOF) {
+ return -1;
+ }
+
+ char mb_str[MB_LEN_MAX];
+ internal::mbstate mbstate;
+ wchar_t wc = static_cast<wchar_t>(wi);
+
+ auto ret = internal::wcrtomb(mb_str, wc, &mbstate);
+ if (!ret.has_value()) {
+ return -1;
+ }
+
+ RET_IF_RESULT_NEGATIVE(writer->write({mb_str, ret.value()}));
+ } else
+#endif // LIBC_COPT_PRINTF_DISABLE_WIDE
+ RET_IF_RESULT_NEGATIVE(writer->write(c));
// If the padding is on the right side, write the spaces last.
if (padding_spaces > 0 &&
diff --git a/libc/src/stdio/printf_core/parser.h b/libc/src/stdio/printf_core/parser.h
index cef9b1ae58fa0..e563308647fae 100644
--- a/libc/src/stdio/printf_core/parser.h
+++ b/libc/src/stdio/printf_core/parser.h
@@ -9,6 +9,10 @@
#ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_PARSER_H
#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_PARSER_H
+#ifndef LIBC_COPT_PRINTF_DISABLE_WIDE
+#include "hdr/types/wint_t.h"
+#endif // LIBC_COPT_PRINTF_DISABLE_WIDE
+
#include "include/llvm-libc-macros/stdfix-macros.h"
#include "src/__support/CPP/algorithm.h" // max
#include "src/__support/CPP/limits.h"
@@ -73,9 +77,9 @@ template <typename ArgProvider> class Parser {
ArgProvider args_cur;
#ifndef LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
- // args_start stores the start of the va_args, which is allows getting the
- // value of arguments that have already been passed. args_index is tracked so
- // that we know which argument args_cur is on.
+ // args_start stores the start of the va_args, which helps in getting the
+ // number of arguments that have already been passed. args_index is tracked
+ // so that we know which argument args_cur is on.
ArgProvider args_start;
size_t args_index = 1;
@@ -173,7 +177,12 @@ template <typename ArgProvider> class Parser {
section.has_conv = true;
break;
case ('c'):
- WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, int, conv_index);
+#ifndef LIBC_COPT_PRINTF_DISABLE_WIDE
+ if (section.length_modifier == LengthModifier::l) {
+ WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, wint_t, conv_index);
+ } else
+#endif // LIBC_COPT_PRINTF_DISABLE_WIDE
+ WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, int, conv_index);
break;
case ('d'):
case ('i'):
@@ -574,7 +583,12 @@ template <typename ArgProvider> class Parser {
conv_size = type_desc_from_type<void>();
break;
case ('c'):
- conv_size = type_desc_from_type<int>();
+#ifndef LIBC_COPT_PRINTF_DISABLE_WIDE
+ if (lm == LengthModifier::l) {
+ conv_size = type_desc_from_type<wint_t>();
+ } else
+#endif // LIBC_COPT_PRINTF_DISABLE_WIDE
+ conv_size = type_desc_from_type<int>();
break;
case ('d'):
case ('i'):
diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index a39428fb8d16c..3bfeefd0750d8 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -137,6 +137,16 @@ if(LIBC_CONF_PRINTF_DISABLE_STRERROR)
list(APPEND sprintf_test_copts "-DLIBC_COPT_PRINTF_DISABLE_STRERROR")
endif()
+if(LIBC_CONF_PRINTF_DISABLE_WIDE)
+ list(APPEND sprintf_test_copts "-DLIBC_COPT_PRINTF_DISABLE_WIDE")
+ set(wchar_deps "")
+else()
+ set(wchar_deps
+ libc.hdr.types.wchar_t
+ libc.hdr.wchar_macros
+ )
+endif()
+
add_fp_unittest(
sprintf_test
UNIT_TEST_ONLY
@@ -148,6 +158,7 @@ add_fp_unittest(
libc.src.stdio.sprintf
libc.src.__support.FPUtil.fp_bits
libc.include.inttypes
+ ${wchar_deps}
COMPILE_OPTIONS
${sprintf_test_copts}
)
diff --git a/libc/test/src/stdio/sprintf_test.cpp b/libc/test/src/stdio/sprintf_test.cpp
index 689a38a49f13c..f4969a51a336e 100644
--- a/libc/test/src/stdio/sprintf_test.cpp
+++ b/libc/test/src/stdio/sprintf_test.cpp
@@ -9,8 +9,14 @@
#include "src/__support/macros/config.h"
#include "src/stdio/sprintf.h"
+#ifndef LIBC_COPT_PRINTF_DISABLE_WIDE
+#include "hdr/types/wchar_t.h"
+#include "hdr/wchar_macros.h"
+#endif // LIBC_COPT_PRINTF_DISABLE_WIDE
+
#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/libc_errno.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
#include "test/UnitTest/RoundingModeUtils.h"
#include "test/UnitTest/Test.h"
#include <inttypes.h>
@@ -3487,3 +3493,36 @@ TEST(LlvmLibcSPrintfTest, IndexModeParsing) {
"why would u do this, this is such a pain. %");
}
#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
+
+#ifndef LIBC_COPT_PRINTF_DISABLE_WIDE
+TEST(LlvmLibcSprintfTest, WideCharConversion) {
+ char buff[16];
+ int written;
+
+ // Euro sign is a 3-byte UTF-8 character.
+ written = LIBC_NAMESPACE::sprintf(buff, "%lc", static_cast<wchar_t>(L'€'));
+ EXPECT_EQ(written, 3);
+ ASSERT_STREQ(buff, "€");
+
+ // Euro sign left justified.
+ written = LIBC_NAMESPACE::sprintf(buff, "%-4lc", static_cast<wchar_t>(L'€'));
+ EXPECT_EQ(written, 6);
+ ASSERT_STREQ(buff, "€ ");
+
+ // Euro sign right justified.
+ written = LIBC_NAMESPACE::sprintf(buff, "%4lc", static_cast<wchar_t>(L'€'));
+ EXPECT_EQ(written, 6);
+ ASSERT_STREQ(buff, " €");
+
+ // WEOF test.
+ written = LIBC_NAMESPACE::sprintf(buff, "%lc", static_cast<wchar_t>(WEOF));
+ EXPECT_EQ(written, -1);
+ ASSERT_ERRNO_FAILURE();
+
+ // Invalid wide character test
+ written =
+ LIBC_NAMESPACE::sprintf(buff, "%lc", static_cast<wchar_t>(0x12ffff));
+ EXPECT_EQ(written, -1);
+ ASSERT_ERRNO_FAILURE();
+}
+#endif // LIBC_COPT_PRINTF_DISABLE_WIDE
>From 0ffbbf5acc507de3b8c8b3d80cd5c717c03682c6 Mon Sep 17 00:00:00 2001
From: "shubh at DOE" <shubhp at mbm3a24.local>
Date: Sat, 20 Dec 2025 01:53:22 -0800
Subject: [PATCH 2/3] test1:
---
libc/src/stdio/printf_core/CMakeLists.txt | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/libc/src/stdio/printf_core/CMakeLists.txt b/libc/src/stdio/printf_core/CMakeLists.txt
index c5c6809d5f940..06d74937def17 100644
--- a/libc/src/stdio/printf_core/CMakeLists.txt
+++ b/libc/src/stdio/printf_core/CMakeLists.txt
@@ -45,7 +45,7 @@ endif()
if(LIBC_CONF_PRINTF_DISABLE_WIDE)
set(wchar_deps "")
- list(APPEND wchar_copts "-DLIBC_COPT_PRINTF_DISABLE_WIDE")
+ list(APPEND printf_config_copts "-DLIBC_COPT_PRINTF_DISABLE_WIDE")
else()
set(wchar_deps
libc.hdr.types.wchar_t
@@ -90,8 +90,6 @@ add_header_library(
libc.src.__support.CPP.type_traits
libc.src.__support.common
${wchar_deps}
- COMPILE_OPTIONS
- ${wchar_copts}
)
add_header_library(
@@ -142,8 +140,6 @@ add_header_library(
libc.src.__support.StringUtil.error_to_string
libc.src.string.memory_utils.inline_memcpy
${wchar_deps}
- COMPILE_OPTIONS
- ${wchar_copts}
)
add_header_library(
>From b143927f6501ea36f7877fb8c7f32471fe20faf7 Mon Sep 17 00:00:00 2001
From: "shubh at DOE" <shubhp at mbm3a24.local>
Date: Sat, 20 Dec 2025 02:22:26 -0800
Subject: [PATCH 3/3] fix build
---
libc/src/stdio/printf_core/CMakeLists.txt | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/libc/src/stdio/printf_core/CMakeLists.txt b/libc/src/stdio/printf_core/CMakeLists.txt
index 06d74937def17..71c2f5906bf86 100644
--- a/libc/src/stdio/printf_core/CMakeLists.txt
+++ b/libc/src/stdio/printf_core/CMakeLists.txt
@@ -45,7 +45,7 @@ endif()
if(LIBC_CONF_PRINTF_DISABLE_WIDE)
set(wchar_deps "")
- list(APPEND printf_config_copts "-DLIBC_COPT_PRINTF_DISABLE_WIDE")
+ list(APPEND wchar_copts "-DLIBC_COPT_PRINTF_DISABLE_WIDE")
else()
set(wchar_deps
libc.hdr.types.wchar_t
@@ -55,6 +55,9 @@ else()
libc.src.__support.wchar.mbstate
)
endif()
+if(wchar_copts)
+ list(PREPEND wchar_copts "COMPILE_OPTIONS")
+endif()
add_header_library(
printf_config
@@ -90,6 +93,7 @@ add_header_library(
libc.src.__support.CPP.type_traits
libc.src.__support.common
${wchar_deps}
+ ${wchar_copts}
)
add_header_library(
@@ -140,6 +144,7 @@ add_header_library(
libc.src.__support.StringUtil.error_to_string
libc.src.string.memory_utils.inline_memcpy
${wchar_deps}
+ ${wchar_copts}
)
add_header_library(
More information about the libc-commits
mailing list