[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