diff --git a/libc/src/stdio/printf_core/CMakeLists.txt b/libc/src/stdio/printf_core/CMakeLists.txt --- a/libc/src/stdio/printf_core/CMakeLists.txt +++ b/libc/src/stdio/printf_core/CMakeLists.txt @@ -19,3 +19,21 @@ libc.src.__support.CPP.bit ) + +add_header_library( + string_writer + HDRS + string_writer.h + DEPENDS + libc.src.string.memory_utils.memcpy_implementation +) + +add_object_library( + writer + SRCS + writer.cpp + HDRS + writer.h + DEPENDS + libc.src.string.memory_utils.memset_implementation +) diff --git a/libc/src/stdio/printf_core/string_writer.h b/libc/src/stdio/printf_core/string_writer.h new file mode 100644 --- /dev/null +++ b/libc/src/stdio/printf_core/string_writer.h @@ -0,0 +1,37 @@ +//===-- String Writer function 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. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_STRING_WRITER_H +#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_STRING_WRITER_H + +#include "src/string/memory_utils/memcpy_implementations.h" +#include + +namespace __llvm_libc { +namespace printf_core { + +// write_to_string treats raw_pointer as a char** so that it can increment the +// inner pointer. This means that the variable raw_pointer points to should be a +// copy of the original string passed to the call to sprintf. Additionally, this +// function can't tell when the end of the string is, and as such cannot +// properly null terminate the string. The copy of the original string should, +// after the call to printf_main, point to the end of the string, and it should +// be set to '\0' by the higher level call. +void write_to_string(void *raw_pointer, const char *__restrict to_write, + size_t len) { + // the string is stored with a double pointer so that it can be incremeneted + // without needing an additional variable. + char **string_dp = reinterpret_cast(raw_pointer); + inline_memcpy(*string_dp, to_write, len); + *string_dp += len; +} + +} // namespace printf_core +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_STRING_WRITER_H diff --git a/libc/src/stdio/printf_core/writer.h b/libc/src/stdio/printf_core/writer.h --- a/libc/src/stdio/printf_core/writer.h +++ b/libc/src/stdio/printf_core/writer.h @@ -1,4 +1,4 @@ -//===-- String writer for printf --------------------------------*- C++ -*-===// +//===-- Writer definition 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. @@ -26,11 +26,17 @@ // onto the end of output. WriteFunc raw_write; + // The max_length for the writer needs to be 1 less than the size of the + // buffer it's writing to. This is because it doesn't handle the null + // terminator. size_t max_length; - size_t chars_written; + size_t chars_written = 0; public: - Writer(void *output, WriteFunc raw_write, size_t max_length); + Writer(void *init_output, WriteFunc init_raw_write, + size_t init_max_length = ~size_t(0)) + : output(init_output), raw_write(init_raw_write), + max_length(init_max_length) {} // write will copy length bytes from new_string into output using // raw_write, unless that would cause more bytes than max_length to be @@ -42,7 +48,7 @@ // increments chars_written by length. void write_chars(char new_char, size_t length); - size_t get_chars_written(); + size_t get_chars_written() { return chars_written; } }; } // namespace printf_core diff --git a/libc/src/stdio/printf_core/writer.cpp b/libc/src/stdio/printf_core/writer.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdio/printf_core/writer.cpp @@ -0,0 +1,42 @@ +//===-- Writer definition 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. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "writer.h" +#include "src/string/memory_utils/memset_implementations.h" +#include + +namespace __llvm_libc { +namespace printf_core { + +void Writer::write(const char *new_string, size_t length) { + // It's important that if max_length is 0, raw_write is never called. This is + // because for snprintf, it is valid to set the output string pointer to null + // if the max length is set to 0. + if (chars_written + length < max_length) + raw_write(output, new_string, length); + else if (chars_written < max_length) // chars_written + length >= max_length + raw_write(output, new_string, max_length - chars_written); + + // chars_written tracks the number of chars that would have been written + // regardless of max_length. + chars_written += length; +} + +void Writer::write_chars(char new_char, size_t length) { + constexpr size_t BUFF_SIZE = 8; + char buff[BUFF_SIZE]; + inline_memset(buff, new_char, BUFF_SIZE); + while (length > BUFF_SIZE) { + write(buff, BUFF_SIZE); + length -= BUFF_SIZE; + } + write(buff, length); +} + +} // namespace printf_core +} // namespace __llvm_libc diff --git a/libc/test/src/stdio/printf_core/CMakeLists.txt b/libc/test/src/stdio/printf_core/CMakeLists.txt --- a/libc/test/src/stdio/printf_core/CMakeLists.txt +++ b/libc/test/src/stdio/printf_core/CMakeLists.txt @@ -10,3 +10,14 @@ ) target_link_libraries(libc.test.src.stdio.printf_core.parser_test PRIVATE LibcPrintfHelpers) + +add_libc_unittest( + string_writer_test + SUITE + libc_stdio_unittests + SRCS + string_writer_test.cpp + DEPENDS + libc.src.stdio.printf_core.writer + libc.src.stdio.printf_core.string_writer +) diff --git a/libc/test/src/stdio/printf_core/string_writer_test.cpp b/libc/test/src/stdio/printf_core/string_writer_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/stdio/printf_core/string_writer_test.cpp @@ -0,0 +1,186 @@ +//===-- Unittests for the printf String Writer ----------------------------===// +// +// 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/printf_core/string_writer.h" +#include "src/stdio/printf_core/writer.h" + +#include "utils/UnitTest/Test.h" + +TEST(LlvmLibcPrintfStringWriterTest, Constructor) { + char str[10]; + char *str_ptr = str; + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_ptr), + __llvm_libc::printf_core::write_to_string); +} + +TEST(LlvmLibcPrintfStringWriterTest, Write) { + char str[4] = {'D', 'E', 'F', 'G'}; + char *str_ptr = str; // This is neccessary because the pointer passed to the + // writer will be modified. + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_ptr), + __llvm_libc::printf_core::write_to_string); + writer.write("abc", 3); + + EXPECT_EQ(str[3], 'G'); + // This null terminates the string. The writer has no indication when the + // string is done, so it relies on the user to null terminate the string using + // the pointer that was passed to it. + *str_ptr = '\0'; + + ASSERT_STREQ("abc", str); + ASSERT_EQ(writer.get_chars_written(), size_t(3)); +} + +TEST(LlvmLibcPrintfStringWriterTest, WriteMultipleTimes) { + char str[10]; + char *str_ptr = str; + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_ptr), + __llvm_libc::printf_core::write_to_string); + writer.write("abc", 3); + writer.write("DEF", 3); + writer.write("1234", 3); + + *str_ptr = '\0'; + + ASSERT_STREQ("abcDEF123", str); + ASSERT_EQ(writer.get_chars_written(), size_t(9)); +} + +TEST(LlvmLibcPrintfStringWriterTest, WriteChars) { + char str[4] = {'D', 'E', 'F', 'G'}; + char *str_ptr = str; + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_ptr), + __llvm_libc::printf_core::write_to_string); + writer.write_chars('a', 3); + + EXPECT_EQ(str[3], 'G'); + *str_ptr = '\0'; + + ASSERT_STREQ("aaa", str); + ASSERT_EQ(writer.get_chars_written(), size_t(3)); +} + +TEST(LlvmLibcPrintfStringWriterTest, WriteCharsMultipleTimes) { + char str[10]; + char *str_ptr = str; + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_ptr), + __llvm_libc::printf_core::write_to_string); + writer.write_chars('a', 3); + writer.write_chars('D', 3); + writer.write_chars('1', 3); + + *str_ptr = '\0'; + + ASSERT_STREQ("aaaDDD111", str); + ASSERT_EQ(writer.get_chars_written(), size_t(9)); +} + +TEST(LlvmLibcPrintfStringWriterTest, WriteManyChars) { + char str[100]; + char *str_ptr = str; + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_ptr), + __llvm_libc::printf_core::write_to_string); + writer.write_chars('Z', 99); + + *str_ptr = '\0'; + + ASSERT_STREQ("ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZ", + str); + ASSERT_EQ(writer.get_chars_written(), size_t(99)); +} + +TEST(LlvmLibcPrintfStringWriterTest, MixedWrites) { + char str[13]; + char *str_ptr = str; + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_ptr), + __llvm_libc::printf_core::write_to_string); + writer.write_chars('a', 3); + writer.write("DEF", 3); + writer.write_chars('1', 3); + writer.write("456", 3); + + *str_ptr = '\0'; + + ASSERT_STREQ("aaaDEF111456", str); + ASSERT_EQ(writer.get_chars_written(), size_t(12)); +} + +TEST(LlvmLibcPrintfStringWriterTest, WriteWithMaxLength) { + char str[11]; + char *str_ptr = str; + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_ptr), + __llvm_libc::printf_core::write_to_string, 10); + writer.write("abcDEF123456", 12); + + *str_ptr = '\0'; + + ASSERT_STREQ("abcDEF1234", str); + ASSERT_EQ(writer.get_chars_written(), size_t(12)); +} + +TEST(LlvmLibcPrintfStringWriterTest, WriteCharsWithMaxLength) { + char str[11]; + char *str_ptr = str; + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_ptr), + __llvm_libc::printf_core::write_to_string, 10); + + writer.write_chars('1', 15); + + *str_ptr = '\0'; + + ASSERT_STREQ("1111111111", str); + ASSERT_EQ(writer.get_chars_written(), size_t(15)); +} + +TEST(LlvmLibcPrintfStringWriterTest, MixedWriteWithMaxLength) { + char str[11]; + char *str_ptr = str; + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_ptr), + __llvm_libc::printf_core::write_to_string, 10); + writer.write_chars('a', 3); + writer.write("DEF", 3); + writer.write_chars('1', 3); + writer.write("456", 3); + + *str_ptr = '\0'; + + ASSERT_STREQ("aaaDEF1114", str); + ASSERT_EQ(writer.get_chars_written(), size_t(12)); +} + +TEST(LlvmLibcPrintfStringWriterTest, NullStringWithZeroMaxLength) { + char *str_ptr = nullptr; + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_ptr), + __llvm_libc::printf_core::write_to_string, 0); + writer.write_chars('a', 3); + writer.write("DEF", 3); + writer.write_chars('1', 3); + writer.write("456", 3); + + ASSERT_EQ(writer.get_chars_written(), size_t(12)); +}