[libc-commits] [libc] [libc] Added c32rtomb implementation (PR #236061)
Arya C S via libc-commits
libc-commits at lists.llvm.org
Wed Oct 28 06:57:59 PDT 2026
https://github.com/AryaCS111 updated https://github.com/llvm/llvm-project/pull/236061
>From 5887e09f6440ba2e9e1e348de67a0c74c60b1e43 Mon Sep 17 00:00:00 2001
From: "arya.cs" <arya.cs at blackfigtech.com>
Date: Tue, 27 Oct 2026 16:00:49 +0530
Subject: [PATCH] [libc] Added c32rtomb implementation
---
libc/config/linux/riscv/entrypoints.txt | 1 +
libc/config/linux/x86_64/entrypoints.txt | 1 +
libc/include/CMakeLists.txt | 1 +
libc/include/wchar.yaml | 9 ++
libc/src/__support/wchar/CMakeLists.txt | 16 ++
libc/src/__support/wchar/c32rtomb.h | 54 +++++++
.../src/__support/wchar/character_converter.h | 5 +
libc/src/wchar/CMakeLists.txt | 14 ++
libc/src/wchar/c32rtomb.cpp | 46 ++++++
libc/src/wchar/c32rtomb.h | 23 +++
libc/test/src/wchar/CMakeLists.txt | 16 ++
libc/test/src/wchar/c32rtomb_test.cpp | 145 ++++++++++++++++++
12 files changed, 331 insertions(+)
create mode 100644 libc/src/__support/wchar/c32rtomb.h
create mode 100644 libc/src/wchar/c32rtomb.cpp
create mode 100644 libc/src/wchar/c32rtomb.h
create mode 100644 libc/test/src/wchar/c32rtomb_test.cpp
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 8c31aaad07015f..8e2c7a2c4f3bf8 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1686,6 +1686,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.wchar.mbsrtowcs
libc.src.wchar.mbsnrtowcs
libc.src.wchar.wcrtomb
+ libc.src.wchar.c32rtomb
libc.src.wchar.wcsrtombs
libc.src.wchar.wcsnrtombs
libc.src.wchar.fgetwc
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 02aef4a1297186..8ca0c52fedebb7 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1712,6 +1712,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.wchar.mbsrtowcs
libc.src.wchar.mbsnrtowcs
libc.src.wchar.wcrtomb
+ libc.src.wchar.c32rtomb
libc.src.wchar.wcsrtombs
libc.src.wchar.wcsnrtombs
libc.src.wchar.fgetwc
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index 0130ba1d72e0e1..84eb34fc09e1b9 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -1279,6 +1279,7 @@ add_header_macro(
.llvm-libc-types.wchar_t
.llvm-libc-types.FILE
.llvm-libc-types.struct_tm
+ .llvm-libc-types.char32_t
)
add_header_macro(
diff --git a/libc/include/wchar.yaml b/libc/include/wchar.yaml
index 2b321f6502ff16..ca047963978641 100644
--- a/libc/include/wchar.yaml
+++ b/libc/include/wchar.yaml
@@ -17,6 +17,7 @@ types:
- type_name: wint_t
- type_name: wchar_t
- type_name: mbstate_t
+ - type_name: char32_t
enums: []
objects: []
functions:
@@ -272,6 +273,14 @@ functions:
- type: char *__restrict
- type: wchar_t
- type: mbstate_t *__restrict
+ - name: c32rtomb
+ standards:
+ - stdc
+ return_type: size_t
+ arguments:
+ - type: char *__restrict
+ - type: char32_t
+ - type: mbstate_t *__restrict
- name: wcscpy
standards:
- stdc
diff --git a/libc/src/__support/wchar/CMakeLists.txt b/libc/src/__support/wchar/CMakeLists.txt
index c4e7f0dbaa1786..46d8fc87ad7986 100644
--- a/libc/src/__support/wchar/CMakeLists.txt
+++ b/libc/src/__support/wchar/CMakeLists.txt
@@ -40,6 +40,22 @@ add_header_library(
.mbstate
)
+add_header_library(
+ c32rtomb
+ HDRS
+ c32rtomb.h
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.types.char32_t
+ libc.hdr.types.size_t
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.__support.libc_assert
+ libc.src.__support.macros.null_check
+ .character_converter
+ .mbstate
+)
+
add_header_library(
wcrtomb
HDRS
diff --git a/libc/src/__support/wchar/c32rtomb.h b/libc/src/__support/wchar/c32rtomb.h
new file mode 100644
index 00000000000000..9e8d34e13cd77c
--- /dev/null
+++ b/libc/src/__support/wchar/c32rtomb.h
@@ -0,0 +1,54 @@
+//===-- Implementation header for c32rtomb --------------------------------===//
+//
+// 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_SRC__SUPPORT_WCHAR_C32RTOMB_H
+#define LLVM_LIBC_SRC__SUPPORT_WCHAR_C32RTOMB_H
+
+#include "hdr/errno_macros.h"
+#include "hdr/types/char32_t.h"
+#include "hdr/types/size_t.h"
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/libc_assert.h"
+#include "src/__support/macros/null_check.h"
+#include "src/__support/wchar/character_converter.h"
+#include "src/__support/wchar/mbstate.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace internal {
+
+LIBC_INLINE ErrorOr<size_t> c32rtomb(char *__restrict dest_ptr, char32_t c32,
+ mbstate *__restrict ps) {
+ LIBC_CRASH_ON_NULLPTR(dest_ptr);
+ LIBC_CRASH_ON_NULLPTR(ps);
+
+ CharacterConverter cr(ps);
+
+ if (!cr.isValidState())
+ return Error(EINVAL);
+
+ int status = cr.push(c32);
+ if (status != 0)
+ return Error(status);
+
+ size_t count = 0;
+ while (!cr.isEmpty()) {
+ auto utf8 = cr.pop_utf8(); // can never fail as long as the push succeeded
+ LIBC_ASSERT(utf8.has_value());
+
+ *dest_ptr = utf8.value();
+ dest_ptr++;
+ count++;
+ }
+ return count;
+}
+
+} // namespace internal
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC__SUPPORT_WCHAR_C32RTOMB_H
diff --git a/libc/src/__support/wchar/character_converter.h b/libc/src/__support/wchar/character_converter.h
index f754a602d2cce0..b518ea6eb24671 100644
--- a/libc/src/__support/wchar/character_converter.h
+++ b/libc/src/__support/wchar/character_converter.h
@@ -132,6 +132,11 @@ LIBC_INLINE int CharacterConverter::push(char32_t utf32) {
if (!isEmpty())
return -1;
+ if (utf32 >= 0xd800 && utf32 <= 0xdfff) {
+ clear();
+ return EILSEQ;
+ }
+
state->partial = utf32;
// determine number of utf-8 bytes needed to represent this utf32 value
diff --git a/libc/src/wchar/CMakeLists.txt b/libc/src/wchar/CMakeLists.txt
index 938b7ad2e4c737..563997666b3ef3 100644
--- a/libc/src/wchar/CMakeLists.txt
+++ b/libc/src/wchar/CMakeLists.txt
@@ -154,6 +154,20 @@ add_entrypoint_object(
libc.src.__support.wchar.mbstate
)
+add_entrypoint_object(
+ c32rtomb
+ SRCS
+ c32rtomb.cpp
+ HDRS
+ c32rtomb.h
+ DEPENDS
+ libc.hdr.types.char32_t
+ libc.hdr.types.mbstate_t
+ libc.src.__support.libc_errno
+ libc.src.__support.wchar.c32rtomb
+ libc.src.__support.wchar.mbstate
+)
+
add_entrypoint_object(
mbsinit
SRCS
diff --git a/libc/src/wchar/c32rtomb.cpp b/libc/src/wchar/c32rtomb.cpp
new file mode 100644
index 00000000000000..b20554b3723dc3
--- /dev/null
+++ b/libc/src/wchar/c32rtomb.cpp
@@ -0,0 +1,46 @@
+//===-- Implementation of c32rtomb ----------------------------------------===//
+//
+// 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/wchar/c32rtomb.h"
+
+#include "hdr/types/char32_t.h"
+#include "hdr/types/mbstate_t.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/wchar/c32rtomb.h"
+#include "src/__support/wchar/mbstate.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(size_t, c32rtomb,
+ (char *__restrict s, char32_t c32,
+ mbstate_t *__restrict ps)) {
+ static internal::mbstate internal_mbstate;
+
+ // when s is nullptr, this is equivalent to c32rtomb(buf, U'\0', ps)
+ char buf[sizeof(char32_t) / sizeof(char)];
+ if (s == nullptr) {
+ s = buf;
+ c32 = 0;
+ }
+
+ auto result = internal::c32rtomb(
+ s, c32,
+ ps == nullptr ? &internal_mbstate
+ : reinterpret_cast<internal::mbstate *>(ps));
+
+ if (!result.has_value()) {
+ libc_errno = result.error();
+ return -1;
+ }
+
+ return result.value();
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/c32rtomb.h b/libc/src/wchar/c32rtomb.h
new file mode 100644
index 00000000000000..42e289e4545d78
--- /dev/null
+++ b/libc/src/wchar/c32rtomb.h
@@ -0,0 +1,23 @@
+//===-- Implementation header for c32rtomb ----------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_WCHAR_C32RTOMB_H
+#define LLVM_LIBC_SRC_WCHAR_C32RTOMB_H
+
+#include "hdr/types/char32_t.h"
+#include "hdr/types/mbstate_t.h"
+#include "hdr/types/size_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+size_t c32rtomb(char *__restrict s, char32_t c32, mbstate_t *__restrict ps);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_WCHAR_C32RTOMB_H
diff --git a/libc/test/src/wchar/CMakeLists.txt b/libc/test/src/wchar/CMakeLists.txt
index 558b2739930aba..09fffc7b81f2fb 100644
--- a/libc/test/src/wchar/CMakeLists.txt
+++ b/libc/test/src/wchar/CMakeLists.txt
@@ -151,6 +151,22 @@ add_libc_test(
libc.test.UnitTest.ErrnoCheckingTest
)
+add_libc_test(
+ c32rtomb_test
+ SUITE
+ libc-wchar-tests
+ SRCS
+ c32rtomb_test.cpp
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.src.wchar.c32rtomb
+ libc.src.string.memset
+ libc.hdr.types.char32_t
+ libc.hdr.types.mbstate_t
+ libc.src.__support.wchar.mbstate
+ libc.test.UnitTest.ErrnoCheckingTest
+)
+
add_libc_test(
wcsrtombs_test
SUITE
diff --git a/libc/test/src/wchar/c32rtomb_test.cpp b/libc/test/src/wchar/c32rtomb_test.cpp
new file mode 100644
index 00000000000000..5f58614ab0d193
--- /dev/null
+++ b/libc/test/src/wchar/c32rtomb_test.cpp
@@ -0,0 +1,145 @@
+//===-- Unittests for c32rtomb --------------------------------------------===//
+//
+// 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 "hdr/errno_macros.h"
+#include "hdr/types/char32_t.h"
+#include "hdr/types/mbstate_t.h"
+#include "src/__support/wchar/mbstate.h"
+#include "src/string/memset.h"
+#include "src/wchar/c32rtomb.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcC32RToMBTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcC32RToMBTest, OneByte) {
+ mbstate_t state;
+ LIBC_NAMESPACE::memset(&state, 0, sizeof(mbstate_t));
+ char32_t c32 = U'U';
+ char mb[4];
+ size_t cnt = LIBC_NAMESPACE::c32rtomb(mb, c32, &state);
+ ASSERT_EQ(cnt, static_cast<size_t>(1));
+ ASSERT_EQ(mb[0], 'U');
+ ASSERT_ERRNO_SUCCESS();
+}
+
+TEST_F(LlvmLibcC32RToMBTest, TwoByte) {
+ mbstate_t state;
+ LIBC_NAMESPACE::memset(&state, 0, sizeof(mbstate_t));
+ // testing utf32: 0xff -> utf8: 0xc3 0xbf
+ char32_t c32 = 0xff;
+ char mb[4];
+ size_t cnt = LIBC_NAMESPACE::c32rtomb(mb, c32, &state);
+ ASSERT_EQ(cnt, static_cast<size_t>(2));
+ ASSERT_EQ(mb[0], static_cast<char>(0xc3));
+ ASSERT_EQ(mb[1], static_cast<char>(0xbf));
+ ASSERT_ERRNO_SUCCESS();
+}
+
+TEST_F(LlvmLibcC32RToMBTest, ThreeByte) {
+ mbstate_t state;
+ LIBC_NAMESPACE::memset(&state, 0, sizeof(mbstate_t));
+ // testing utf32: 0xac15 -> utf8: 0xea 0xb0 0x95
+ char32_t c32 = 0xac15;
+ char mb[4];
+ size_t cnt = LIBC_NAMESPACE::c32rtomb(mb, c32, &state);
+ ASSERT_EQ(cnt, static_cast<size_t>(3));
+ ASSERT_EQ(mb[0], static_cast<char>(0xea));
+ ASSERT_EQ(mb[1], static_cast<char>(0xb0));
+ ASSERT_EQ(mb[2], static_cast<char>(0x95));
+ ASSERT_ERRNO_SUCCESS();
+}
+
+TEST_F(LlvmLibcC32RToMBTest, FourByte) {
+ mbstate_t state;
+ LIBC_NAMESPACE::memset(&state, 0, sizeof(mbstate_t));
+ // testing utf32: 0x1f921 -> utf8: 0xf0 0x9f 0xa4 0xa1
+ char32_t c32 = 0x1f921;
+ char mb[4];
+ size_t cnt = LIBC_NAMESPACE::c32rtomb(mb, c32, &state);
+ ASSERT_EQ(cnt, static_cast<size_t>(4));
+ ASSERT_EQ(mb[0], static_cast<char>(0xf0));
+ ASSERT_EQ(mb[1], static_cast<char>(0x9f));
+ ASSERT_EQ(mb[2], static_cast<char>(0xa4));
+ ASSERT_EQ(mb[3], static_cast<char>(0xa1));
+ ASSERT_ERRNO_SUCCESS();
+}
+
+TEST_F(LlvmLibcC32RToMBTest, NullString) {
+ mbstate_t state;
+ LIBC_NAMESPACE::memset(&state, 0, sizeof(mbstate_t));
+ char32_t c32 = U'A';
+ char mb[4];
+
+ // should be equivalent to the call c32rtomb(buf, U'\0', state)
+ size_t cnt1 = LIBC_NAMESPACE::c32rtomb(nullptr, c32, &state);
+ ASSERT_ERRNO_SUCCESS();
+ size_t cnt2 = LIBC_NAMESPACE::c32rtomb(mb, 0, &state);
+ ASSERT_ERRNO_SUCCESS();
+
+ ASSERT_EQ(cnt1, cnt2);
+ ASSERT_EQ(cnt1, static_cast<size_t>(1));
+ ASSERT_EQ(mb[0], '\0');
+}
+
+TEST_F(LlvmLibcC32RToMBTest, NullState) {
+ char32_t c32 = U'A';
+ char mb[4];
+ size_t cnt = LIBC_NAMESPACE::c32rtomb(mb, c32, nullptr);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(cnt, static_cast<size_t>(1));
+ ASSERT_EQ(mb[0], 'A');
+}
+
+TEST_F(LlvmLibcC32RToMBTest, NullWideChar) {
+ mbstate_t state;
+ LIBC_NAMESPACE::memset(&state, 0, sizeof(mbstate_t));
+ char mb[4];
+ size_t cnt = LIBC_NAMESPACE::c32rtomb(mb, 0, &state);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(cnt, static_cast<size_t>(1));
+ ASSERT_EQ(mb[0], '\0');
+}
+
+TEST_F(LlvmLibcC32RToMBTest, InvalidCharTooLarge) {
+ mbstate_t state;
+ LIBC_NAMESPACE::memset(&state, 0, sizeof(mbstate_t));
+ char32_t c32 = 0x12ffff;
+ char mb[4];
+ size_t cnt = LIBC_NAMESPACE::c32rtomb(mb, c32, &state);
+ ASSERT_EQ(cnt, static_cast<size_t>(-1));
+ ASSERT_ERRNO_EQ(EILSEQ);
+}
+
+TEST_F(LlvmLibcC32RToMBTest, InvalidSurrogate) {
+ mbstate_t state;
+ LIBC_NAMESPACE::memset(&state, 0, sizeof(mbstate_t));
+ char mb[4];
+
+ // Leading surrogate
+ size_t cnt1 = LIBC_NAMESPACE::c32rtomb(mb, 0xd800, &state);
+ ASSERT_EQ(cnt1, static_cast<size_t>(-1));
+ ASSERT_ERRNO_EQ(EILSEQ);
+
+ // Trailing surrogate
+ size_t cnt2 = LIBC_NAMESPACE::c32rtomb(mb, 0xdfff, &state);
+ ASSERT_EQ(cnt2, static_cast<size_t>(-1));
+ ASSERT_ERRNO_EQ(EILSEQ);
+}
+
+TEST_F(LlvmLibcC32RToMBTest, InvalidMBState) {
+ mbstate_t *state;
+ LIBC_NAMESPACE::internal::mbstate inv;
+ inv.total_bytes = 6;
+ state = reinterpret_cast<mbstate_t *>(&inv);
+ char32_t c32 = U'A';
+ char mb[4];
+ size_t cnt = LIBC_NAMESPACE::c32rtomb(mb, c32, state);
+ ASSERT_EQ(cnt, static_cast<size_t>(-1));
+ ASSERT_ERRNO_EQ(EINVAL);
+}
More information about the libc-commits
mailing list