[libc-commits] [libc] [libc] Implement vasprintf (PR #98824)
Tsz Chan via libc-commits
libc-commits at lists.llvm.org
Sun Jul 14 10:41:55 PDT 2024
https://github.com/tszhin-swe updated https://github.com/llvm/llvm-project/pull/98824
>From 98059bab8557e839c6e5564ebb3d02e0ece87365 Mon Sep 17 00:00:00 2001
From: Izaak Schroeder <izaak.schroeder at gmail.com>
Date: Tue, 2 Jul 2024 23:53:14 -0700
Subject: [PATCH 1/2] [libc] Add `vasprintf` function stubs
---
libc/spec/stdc.td | 7 +++++++
libc/src/stdio/CMakeLists.txt | 31 +++++++++++++++++++++++++++++++
libc/src/stdio/vasprintf.cpp | 26 ++++++++++++++++++++++++++
libc/src/stdio/vasprintf.h | 21 +++++++++++++++++++++
4 files changed, 85 insertions(+)
create mode 100644 libc/src/stdio/vasprintf.cpp
create mode 100644 libc/src/stdio/vasprintf.h
diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td
index aa56152aee141..e5e0900cce8c6 100644
--- a/libc/spec/stdc.td
+++ b/libc/spec/stdc.td
@@ -967,6 +967,13 @@ def StdC : StandardSpec<"stdc"> {
RetValSpec<IntType>,
[ArgSpec<IntType>, ArgSpec<FILEPtr>]
>,
+ FunctionSpec<
+ "vasprintf",
+ RetValSpec<IntType>,
+ [ArgSpec<CharRestrictedPtrPtr>,
+ ArgSpec<ConstCharPtr>,
+ ArgSpec<VaListType>]
+ >,
],
[
ObjectSpec<
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index 2d528a903cc2f..0582c96d320b3 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -185,6 +185,37 @@ add_entrypoint_object(
libc.src.stdio.printf_core.writer
)
+add_entrypoint_object(
+ vfprintf
+ SRCS
+ vfprintf.cpp
+ HDRS
+ vfprintf.h
+ DEPENDS
+ libc.src.__support.arg_list
+ libc.src.stdio.printf_core.vfprintf_internal
+)
+
+add_entrypoint_object(
+ vasprintf
+ SRCS
+ vasprintf.cpp
+ HDRS
+ vasprintf.h
+ DEPENDS
+ libc.src.__support.arg_list
+)
+
+add_stdio_entrypoint_object(
+ fileno
+ SRCS
+ fileno.cpp
+ HDRS
+ fileno.h
+ DEPENDS
+ libc.src.stdio.fileno
+)
+
add_subdirectory(printf_core)
add_subdirectory(scanf_core)
diff --git a/libc/src/stdio/vasprintf.cpp b/libc/src/stdio/vasprintf.cpp
new file mode 100644
index 0000000000000..712a3f72ab62d
--- /dev/null
+++ b/libc/src/stdio/vasprintf.cpp
@@ -0,0 +1,26 @@
+//===-- Implementation of vasprintf -----------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/stdio/vasprintf.h"
+
+#include "src/__support/arg_list.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+
+namespace LIBC_NAMESPACE {
+
+LLVM_LIBC_FUNCTION(int, vasprintf,
+ (char **__restrict, const char *, va_list vlist)) {
+ internal::ArgList args(vlist); // This holder class allows for easier copying
+ // and pointer semantics, as well as handling
+ // destruction automatically.
+ return -1;
+}
+
+} // namespace LIBC_NAMESPACE
diff --git a/libc/src/stdio/vasprintf.h b/libc/src/stdio/vasprintf.h
new file mode 100644
index 0000000000000..c11675461e3fb
--- /dev/null
+++ b/libc/src/stdio/vasprintf.h
@@ -0,0 +1,21 @@
+//===-- Implementation header of vasprintf ----------------------*- 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_STDIO_VASPRINTF_H
+#define LLVM_LIBC_SRC_STDIO_VASPRINTF_H
+
+#include <stdarg.h>
+#include <stdio.h>
+
+namespace LIBC_NAMESPACE {
+
+int vasprintf(char **__restrict s, const char *format, va_list vlist);
+
+} // namespace LIBC_NAMESPACE
+
+#endif // LLVM_LIBC_SRC_STDIO_VASPRINTF_H
>From 19d5b6926fc59ea9a977e6d617de7396df5e150a Mon Sep 17 00:00:00 2001
From: Tsz Chan <keithcth2001 at gmail.com>
Date: Mon, 15 Jul 2024 01:41:38 +0800
Subject: [PATCH 2/2] [libc] Implement vasprintf
---
libc/config/darwin/arm/entrypoints.txt | 3 +
libc/src/stdio/CMakeLists.txt | 25 ++-----
libc/src/stdio/printf_core/writer.h | 82 ++++++++++++++---------
libc/src/stdio/vasprintf.cpp | 54 +++++++++++++++-
libc/test/src/stdio/CMakeLists.txt | 9 +++
libc/test/src/stdio/vasprintf_test.cpp | 90 ++++++++++++++++++++++++++
6 files changed, 210 insertions(+), 53 deletions(-)
create mode 100644 libc/test/src/stdio/vasprintf_test.cpp
diff --git a/libc/config/darwin/arm/entrypoints.txt b/libc/config/darwin/arm/entrypoints.txt
index 383118dc781e5..0c9f5d0b87423 100644
--- a/libc/config/darwin/arm/entrypoints.txt
+++ b/libc/config/darwin/arm/entrypoints.txt
@@ -94,6 +94,9 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.stdlib.calloc
libc.src.stdlib.realloc
libc.src.stdlib.free
+
+ libc.src.stdio.vsprintf
+ libc.src.stdio.vasprintf
)
set(TARGET_LIBM_ENTRYPOINTS
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index 0582c96d320b3..ea98e5757ca43 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -185,17 +185,6 @@ add_entrypoint_object(
libc.src.stdio.printf_core.writer
)
-add_entrypoint_object(
- vfprintf
- SRCS
- vfprintf.cpp
- HDRS
- vfprintf.h
- DEPENDS
- libc.src.__support.arg_list
- libc.src.stdio.printf_core.vfprintf_internal
-)
-
add_entrypoint_object(
vasprintf
SRCS
@@ -204,16 +193,10 @@ add_entrypoint_object(
vasprintf.h
DEPENDS
libc.src.__support.arg_list
-)
-
-add_stdio_entrypoint_object(
- fileno
- SRCS
- fileno.cpp
- HDRS
- fileno.h
- DEPENDS
- libc.src.stdio.fileno
+ libc.src.stdio.printf_core.printf_main
+ libc.src.stdio.printf_core.writer
+ libc.src.stdlib.malloc
+ libc.src.stdlib.realloc
)
add_subdirectory(printf_core)
diff --git a/libc/src/stdio/printf_core/writer.h b/libc/src/stdio/printf_core/writer.h
index 89421561f3b2e..7828d3a8f016d 100644
--- a/libc/src/stdio/printf_core/writer.h
+++ b/libc/src/stdio/printf_core/writer.h
@@ -23,8 +23,10 @@ namespace printf_core {
struct WriteBuffer {
using StreamWriter = int (*)(cpp::string_view, void *);
+ using ResizeOverflowWriter = int (*)(WriteBuffer *wb,
+ cpp::string_view new_str);
char *buff;
- const size_t buff_len;
+ size_t buff_len;
size_t buff_cur = 0;
// The stream writer will be called when the buffer is full. It will be passed
@@ -32,6 +34,8 @@ struct WriteBuffer {
StreamWriter stream_writer;
void *output_target;
+ ResizeOverflowWriter resize_writer = nullptr;
+
LIBC_INLINE WriteBuffer(char *Buff, size_t Buff_len, StreamWriter hook,
void *target)
: buff(Buff), buff_len(Buff_len), stream_writer(hook),
@@ -41,41 +45,61 @@ struct WriteBuffer {
: buff(Buff), buff_len(Buff_len), stream_writer(nullptr),
output_target(nullptr) {}
+ LIBC_INLINE WriteBuffer(char *Buff, size_t Buff_len,
+ ResizeOverflowWriter hook)
+ : buff(Buff), buff_len(Buff_len), stream_writer(nullptr),
+ output_target(nullptr), resize_writer(hook) {}
+
+ LIBC_INLINE int flush_to_stream(cpp::string_view new_str) {
+ if (buff_cur > 0) {
+ int retval = stream_writer({buff, buff_cur}, output_target);
+ if (retval < 0) {
+ return retval;
+ }
+ }
+ if (new_str.size() > 0) {
+ int retval = stream_writer(new_str, output_target);
+ if (retval < 0) {
+ return retval;
+ }
+ }
+ buff_cur = 0;
+ return WRITE_OK;
+ }
+
+ LIBC_INLINE int fill_remaining_to_buff(cpp::string_view new_str) {
+ if (buff_cur < buff_len) {
+ size_t bytes_to_write = buff_len - buff_cur;
+ if (bytes_to_write > new_str.size()) {
+ bytes_to_write = new_str.size();
+ }
+ inline_memcpy(buff + buff_cur, new_str.data(), bytes_to_write);
+ buff_cur += bytes_to_write;
+ }
+ return WRITE_OK;
+ }
+
// The overflow_write method is intended to be called to write the contents of
- // the buffer and new_str to the stream_writer if it exists, else it will
- // write as much of new_str to the buffer as it can. The current position in
- // the buffer will be reset iff stream_writer is called. Calling this with an
- // empty string will flush the buffer if relevant.
+ // the buffer and new_str to the stream_writer if it exists. If a resizing
+ // hook is provided, it will resize the buffer and write the contents. If
+ // neither a stream_writer nor a resizing hook is provided, it will fill the
+ // remaining space in the buffer with new_str and drop the overflow. Calling
+ // this with an empty string will flush the buffer if relevant.
+
LIBC_INLINE int overflow_write(cpp::string_view new_str) {
+ // If there is resizing hook for this buffer, resize and write
+ // contents, this can change buff, buff_len and move the curr ptr.
+ if (resize_writer != nullptr) {
+ return resize_writer(this, new_str);
+ }
// If there is a stream_writer, write the contents of the buffer, then
// new_str, then clear the buffer.
- if (stream_writer != nullptr) {
- if (buff_cur > 0) {
- int retval = stream_writer({buff, buff_cur}, output_target);
- if (retval < 0) {
- return retval;
- }
- }
- if (new_str.size() > 0) {
- int retval = stream_writer(new_str, output_target);
- if (retval < 0) {
- return retval;
- }
- }
- buff_cur = 0;
- return WRITE_OK;
+ else if (stream_writer != nullptr) {
+ return flush_to_stream(new_str);
} else {
// We can't flush to the stream, so fill the rest of the buffer, then drop
// the overflow.
- if (buff_cur < buff_len) {
- size_t bytes_to_write = buff_len - buff_cur;
- if (bytes_to_write > new_str.size()) {
- bytes_to_write = new_str.size();
- }
- inline_memcpy(buff + buff_cur, new_str.data(), bytes_to_write);
- buff_cur += bytes_to_write;
- }
- return WRITE_OK;
+ return fill_remaining_to_buff(new_str);
}
}
};
diff --git a/libc/src/stdio/vasprintf.cpp b/libc/src/stdio/vasprintf.cpp
index 712a3f72ab62d..f488d3c512fb3 100644
--- a/libc/src/stdio/vasprintf.cpp
+++ b/libc/src/stdio/vasprintf.cpp
@@ -8,19 +8,67 @@
#include "src/stdio/vasprintf.h"
+#include "src/__support/OSUtil/io.h"
#include "src/__support/arg_list.h"
-
+#include "src/stdio/printf.h"
+#include "src/stdio/printf_core/core_structs.h"
+#include "src/stdio/printf_core/printf_main.h"
+#include "src/stdio/printf_core/writer.h"
#include <stdarg.h>
#include <stdio.h>
+#include <stdlib.h> // malloc, realloc, free
namespace LIBC_NAMESPACE {
+namespace {
+
+LIBC_INLINE int resize_overflow_hook(printf_core::WriteBuffer *wb,
+ cpp::string_view new_str) {
+ size_t NewSize = new_str.size() + wb->buff_cur;
+ char *TmpBuf =
+ static_cast<char *>(realloc(wb->buff, NewSize + 1)); // +1 for null
+ if (TmpBuf == nullptr) {
+ return printf_core::FILE_WRITE_ERROR;
+ }
+ wb->buff = TmpBuf;
+ inline_memcpy(wb->buff + wb->buff_cur, new_str.data(), new_str.size());
+ wb->buff_cur = NewSize;
+ wb->buff_len = NewSize;
+ return printf_core::WRITE_OK;
+}
+
+} // namespace
+
LLVM_LIBC_FUNCTION(int, vasprintf,
- (char **__restrict, const char *, va_list vlist)) {
+ (char **__restrict ret, const char *format, va_list vlist)) {
internal::ArgList args(vlist); // This holder class allows for easier copying
// and pointer semantics, as well as handling
// destruction automatically.
- return -1;
+ const uint16_t defaultSize = 200;
+ auto InitBuff = static_cast<char *>(malloc(defaultSize));
+ if (InitBuff == nullptr)
+ return printf_core::FILE_WRITE_ERROR;
+ printf_core::WriteBuffer wb(InitBuff, defaultSize, resize_overflow_hook);
+ printf_core::Writer writer(&wb);
+ int ret_val = printf_core::printf_main(&writer, format, args);
+ if (LIBC_LIKELY(wb.buff == InitBuff)) {
+ *ret = static_cast<char *>(realloc(InitBuff, ret_val + 1)); // +1 for null
+ if (LIBC_UNLIKELY(ret == nullptr)) {
+ free(InitBuff);
+ return printf_core::FILE_WRITE_ERROR;
+ }
+ }
+ if (ret_val == printf_core::FILE_WRITE_ERROR) {
+ if (wb.buff != InitBuff) {
+ free(wb.buff); // InitBuff should have been freed during successful
+ // realloc.
+ } else {
+ free(InitBuff);
+ }
+ return printf_core::FILE_WRITE_ERROR;
+ }
+ (*ret)[ret_val] = '\0';
+ return ret_val;
}
} // namespace LIBC_NAMESPACE
diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index 5eb8c9577893b..98a5c072cb23b 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -236,6 +236,15 @@ add_libc_test(
libc.src.stdio.vprintf
)
+add_libc_unittest(
+ vasprintf_test
+ SUITE
+ libc_stdio_unittests
+ SRCS
+ vasprintf_test.cpp
+ DEPENDS
+ libc.src.stdio.vasprintf
+ )
if(LLVM_LIBC_FULL_BUILD)
# In fullbuild mode, fscanf's tests use the internal FILE for other functions.
diff --git a/libc/test/src/stdio/vasprintf_test.cpp b/libc/test/src/stdio/vasprintf_test.cpp
new file mode 100644
index 0000000000000..3c67e01c4df0c
--- /dev/null
+++ b/libc/test/src/stdio/vasprintf_test.cpp
@@ -0,0 +1,90 @@
+//===-- Unittests for vasprintf
+//--------------------------------------------===//
+//
+// 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/vasprintf.h"
+#include "test/UnitTest/Test.h"
+#include <string>
+
+int call_vasprintf(char **__restrict buffer, const char *__restrict format,
+ ...) {
+ va_list vlist;
+ va_start(vlist, format);
+ int ret = LIBC_NAMESPACE::vasprintf(buffer, format, vlist);
+ va_end(vlist);
+ return ret;
+}
+
+TEST(LlvmLibcVASPrintfTest, SimpleNoConv) {
+ char *buff = nullptr;
+ int written;
+ written = call_vasprintf(&buff, "A simple string with no conversions.");
+ EXPECT_EQ(written, 36);
+ ASSERT_STREQ(buff, "A simple string with no conversions.");
+ free(buff);
+}
+
+TEST(LlvmLibcVASPrintfTest, PercentConv) {
+ char *buff = nullptr;
+ int written;
+
+ written = call_vasprintf(&buff, "%%");
+ EXPECT_EQ(written, 1);
+ ASSERT_STREQ(buff, "%");
+
+ written = call_vasprintf(&buff, "abc %% def");
+ EXPECT_EQ(written, 9);
+ ASSERT_STREQ(buff, "abc % def");
+
+ written = call_vasprintf(&buff, "%%%%%%");
+ EXPECT_EQ(written, 3);
+ ASSERT_STREQ(buff, "%%%");
+ free(buff);
+}
+
+TEST(LlvmLibcVASPrintfTest, CharConv) {
+ char *buff = nullptr;
+ int written;
+
+ written = call_vasprintf(&buff, "%c", 'a');
+ EXPECT_EQ(written, 1);
+ ASSERT_STREQ(buff, "a");
+
+ written = call_vasprintf(&buff, "%3c %-3c", '1', '2');
+ EXPECT_EQ(written, 7);
+ ASSERT_STREQ(buff, " 1 2 ");
+
+ written = call_vasprintf(&buff, "%*c", 2, '3');
+ EXPECT_EQ(written, 2);
+ ASSERT_STREQ(buff, " 3");
+ free(buff);
+}
+
+TEST(LlvmLibcVASPrintfTest, LargeStringNoConv) {
+ char *buff = nullptr;
+ int written;
+ auto long_str = std::string(250, 'o');
+ written = call_vasprintf(&buff, long_str.c_str());
+ EXPECT_EQ(written, 250);
+ ASSERT_STREQ(buff, long_str.c_str());
+ free(buff);
+}
+
+TEST(LlvmLibcVASPrintfTest, LargeStringCharConv) {
+ char *buff = nullptr;
+ int written;
+ auto long_str = std::string(199, 'a');
+ written = call_vasprintf(&buff, "%sbc", long_str.c_str());
+ EXPECT_EQ(written, 201);
+ ASSERT_STREQ(
+ buff,
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabc");
+ free(buff);
+}
\ No newline at end of file
More information about the libc-commits
mailing list