diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index 8bf6c402b0395..4d94f10196fd7 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -1247,6 +1247,7 @@ if(LLVM_LIBC_FULL_BUILD) # wchar.h entrypoints libc.src.wchar.mbrtowc + libc.src.wchar.wcrtomb ) endif() diff --git a/libc/include/wchar.yaml b/libc/include/wchar.yaml index c036636e12c32..64eb381710668 100644 --- a/libc/include/wchar.yaml +++ b/libc/include/wchar.yaml @@ -159,6 +159,14 @@ functions: - type: wchar_t *__restrict - type: const wchar_t *__restrict - type: size_t + - name: wcrtomb + standards: + - stdc + return_type: size_t + arguments: + - type: char *__restrict + - type: wchar_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 479c1dff2c6e0..6aade4ccc84a6 100644 --- a/libc/src/__support/wchar/CMakeLists.txt +++ b/libc/src/__support/wchar/CMakeLists.txt @@ -20,6 +20,22 @@ add_object_library( .mbstate ) +add_object_library( + wcrtomb + HDRS + wcrtomb.h + SRCS + wcrtomb.cpp + DEPENDS + libc.hdr.types.char32_t + libc.hdr.types.size_t + libc.hdr.types.wchar_t + libc.src.__support.error_or + libc.src.__support.common + .character_converter + .mbstate +) + add_object_library( mbrtowc HDRS diff --git a/libc/src/__support/wchar/wcrtomb.cpp b/libc/src/__support/wchar/wcrtomb.cpp new file mode 100644 index 0000000000000..8ca3d17ad6ce1 --- /dev/null +++ b/libc/src/__support/wchar/wcrtomb.cpp @@ -0,0 +1,49 @@ +//===-- Implementation of wcrtomb -----------------------------------------===// +// +// 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/wchar/wcrtomb.h" +#include "src/__support/error_or.h" +#include "src/__support/wchar/character_converter.h" +#include "src/__support/wchar/mbstate.h" + +#include "hdr/types/char32_t.h" +#include "hdr/types/size_t.h" +#include "hdr/types/wchar_t.h" +#include "src/__support/common.h" +#include "src/__support/libc_assert.h" + +namespace LIBC_NAMESPACE_DECL { +namespace internal { + +ErrorOr wcrtomb(char *__restrict s, wchar_t wc, + mbstate *__restrict ps) { + static_assert(sizeof(wchar_t) == 4); + + CharacterConverter cr(ps); + + if (s == nullptr) + return Error(-1); + + int status = cr.push(static_cast(wc)); + 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()); + + *s = utf8.value(); + s++; + count++; + } + return count; +} + +} // namespace internal +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/__support/wchar/wcrtomb.h b/libc/src/__support/wchar/wcrtomb.h new file mode 100644 index 0000000000000..bcd39a92a3b76 --- /dev/null +++ b/libc/src/__support/wchar/wcrtomb.h @@ -0,0 +1,26 @@ +//===-- Implementation header for wcrtomb ---------------------------------===// +// +// 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_WCRTOMB_H +#define LLVM_LIBC_SRC__SUPPORT_WCHAR_WCRTOMB_H + +#include "hdr/types/size_t.h" +#include "hdr/types/wchar_t.h" +#include "src/__support/error_or.h" +#include "src/__support/macros/config.h" +#include "src/__support/wchar/mbstate.h" + +namespace LIBC_NAMESPACE_DECL { +namespace internal { + +ErrorOr wcrtomb(char *__restrict s, wchar_t wc, mbstate *__restrict ps); + +} // namespace internal +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC__SUPPORT_WCHAR_WCRTOMB_H diff --git a/libc/src/wchar/CMakeLists.txt b/libc/src/wchar/CMakeLists.txt index 163c29847e6a2..ec33caccb16d5 100644 --- a/libc/src/wchar/CMakeLists.txt +++ b/libc/src/wchar/CMakeLists.txt @@ -34,6 +34,20 @@ add_entrypoint_object( libc.src.__support.wctype_utils ) +add_entrypoint_object( + wcrtomb + SRCS + wcrtomb.cpp + HDRS + wcrtomb.h + DEPENDS + libc.hdr.types.wchar_t + libc.hdr.types.mbstate_t + libc.src.__support.libc_errno + libc.src.__support.wchar.wcrtomb + libc.src.__support.wchar.mbstate +) + add_entrypoint_object( mbrtowc SRCS diff --git a/libc/src/wchar/wcrtomb.cpp b/libc/src/wchar/wcrtomb.cpp new file mode 100644 index 0000000000000..6d604a00599ee --- /dev/null +++ b/libc/src/wchar/wcrtomb.cpp @@ -0,0 +1,45 @@ +//===-- Implementation of wcrtomb -----------------------------------------===// +// +// 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/wcrtomb.h" + +#include "hdr/types/mbstate_t.h" +#include "hdr/types/wchar_t.h" +#include "src/__support/common.h" +#include "src/__support/libc_errno.h" +#include "src/__support/macros/config.h" +#include "src/__support/wchar/mbstate.h" +#include "src/__support/wchar/wcrtomb.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(size_t, wcrtomb, + (char *__restrict s, wchar_t wc, mbstate_t *__restrict ps)) { + static internal::mbstate internal_mbstate; + + // when s is nullptr, this is equivalent to wcrtomb(buf, L'\0', ps) + char buf[sizeof(wchar_t) / sizeof(char)]; + if (s == nullptr) { + s = buf; + wc = L'\0'; + } + + auto result = internal::wcrtomb( + s, wc, + ps == nullptr ? &internal_mbstate + : reinterpret_cast(ps)); + + if (!result.has_value()) { + libc_errno = EILSEQ; + return -1; + } + + return result.value(); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/wchar/wcrtomb.h b/libc/src/wchar/wcrtomb.h new file mode 100644 index 0000000000000..06c42f158122c --- /dev/null +++ b/libc/src/wchar/wcrtomb.h @@ -0,0 +1,23 @@ +//===-- Implementation header for wcrtomb -----------------------*- 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_WCRTOMB_H +#define LLVM_LIBC_SRC_WCHAR_WCRTOMB_H + +#include "hdr/types/mbstate_t.h" +#include "hdr/types/size_t.h" +#include "hdr/types/wchar_t.h" +#include "src/__support/macros/config.h" + +namespace LIBC_NAMESPACE_DECL { + +size_t wcrtomb(char *__restrict s, wchar_t wc, mbstate_t *__restrict ps); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_WCHAR_WCRTOMB_H diff --git a/libc/test/src/wchar/CMakeLists.txt b/libc/test/src/wchar/CMakeLists.txt index d4cae1f6228bd..184e482c895b1 100644 --- a/libc/test/src/wchar/CMakeLists.txt +++ b/libc/test/src/wchar/CMakeLists.txt @@ -47,6 +47,20 @@ add_libc_test( libc.src.wchar.wctob ) +add_libc_test( + wcrtomb_test + SUITE + libc_wchar_unittests + SRCS + wcrtomb_test.cpp + DEPENDS + libc.src.wchar.wcrtomb + libc.src.string.memset + libc.hdr.types.wchar_t + libc.hdr.types.mbstate_t + libc.src.__support.libc_errno +) + add_libc_test( wmemset_test SUITE diff --git a/libc/test/src/wchar/wcrtomb_test.cpp b/libc/test/src/wchar/wcrtomb_test.cpp new file mode 100644 index 0000000000000..c06b39ae0143f --- /dev/null +++ b/libc/test/src/wchar/wcrtomb_test.cpp @@ -0,0 +1,93 @@ +//===-- Unittests for wcrtomb --------------------------------------------===// +// +// 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/types/mbstate_t.h" +#include "hdr/types/wchar_t.h" +#include "src/__support/libc_errno.h" +#include "src/string/memset.h" +#include "src/wchar/wcrtomb.h" +#include "test/UnitTest/Test.h" + +TEST(LlvmLibcWCRToMBTest, OneByte) { + mbstate_t state; + LIBC_NAMESPACE::memset(&state, 0, sizeof(mbstate_t)); + wchar_t wc = L'U'; + char mb[4]; + size_t cnt = LIBC_NAMESPACE::wcrtomb(mb, wc, &state); + ASSERT_EQ(cnt, static_cast(1)); + ASSERT_EQ(mb[0], 'U'); +} + +TEST(LlvmLibcWCRToMBTest, TwoByte) { + mbstate_t state; + LIBC_NAMESPACE::memset(&state, 0, sizeof(mbstate_t)); + // testing utf32: 0xff -> utf8: 0xc3 0xbf + wchar_t wc = 0xff; + char mb[4]; + size_t cnt = LIBC_NAMESPACE::wcrtomb(mb, wc, &state); + ASSERT_EQ(cnt, static_cast(2)); + ASSERT_EQ(mb[0], static_cast(0xc3)); + ASSERT_EQ(mb[1], static_cast(0xbf)); +} + +TEST(LlvmLibcWCRToMBTest, ThreeByte) { + mbstate_t state; + LIBC_NAMESPACE::memset(&state, 0, sizeof(mbstate_t)); + // testing utf32: 0xac15 -> utf8: 0xea 0xb0 0x95 + wchar_t wc = 0xac15; + char mb[4]; + size_t cnt = LIBC_NAMESPACE::wcrtomb(mb, wc, &state); + ASSERT_EQ(cnt, static_cast(3)); + ASSERT_EQ(mb[0], static_cast(0xea)); + ASSERT_EQ(mb[1], static_cast(0xb0)); + ASSERT_EQ(mb[2], static_cast(0x95)); +} + +TEST(LlvmLibcWCRToMBTest, FourByte) { + mbstate_t state; + LIBC_NAMESPACE::memset(&state, 0, sizeof(mbstate_t)); + // testing utf32: 0x1f921 -> utf8: 0xf0 0x9f 0xa4 0xa1 + wchar_t wc = 0x1f921; + char mb[4]; + size_t cnt = LIBC_NAMESPACE::wcrtomb(mb, wc, &state); + ASSERT_EQ(cnt, static_cast(4)); + ASSERT_EQ(mb[0], static_cast(0xf0)); + ASSERT_EQ(mb[1], static_cast(0x9f)); + ASSERT_EQ(mb[2], static_cast(0xa4)); + ASSERT_EQ(mb[3], static_cast(0xa1)); +} + +TEST(LlvmLibcWCRToMBTest, NullString) { + mbstate_t state; + LIBC_NAMESPACE::memset(&state, 0, sizeof(mbstate_t)); + wchar_t wc = L'A'; + char mb[4]; + + // should be equivalent to the call wcrtomb(buf, L'\0', state) + size_t cnt1 = LIBC_NAMESPACE::wcrtomb(nullptr, wc, &state); + size_t cnt2 = LIBC_NAMESPACE::wcrtomb(mb, L'\0', &state); + + ASSERT_EQ(cnt1, cnt2); +} + +TEST(LlvmLibcWCRToMBTest, NullState) { + wchar_t wc = L'A'; + char mb[4]; + size_t cnt = LIBC_NAMESPACE::wcrtomb(mb, wc, nullptr); + ASSERT_EQ(cnt, static_cast(1)); +} + +TEST(LlvmLibcWCRToMBTest, InvalidWchar) { + mbstate_t state; + LIBC_NAMESPACE::memset(&state, 0, sizeof(mbstate_t)); + wchar_t wc = 0x12ffff; + char mb[4]; + size_t cnt = LIBC_NAMESPACE::wcrtomb(mb, wc, &state); + ASSERT_EQ(cnt, static_cast(-1)); + ASSERT_EQ(static_cast(libc_errno), EILSEQ); +}