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,56 @@ +//===-- String Writer class 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 { + +class StringWriter { + char *__restrict cur_buffer; + size_t available_capacity; + +public: + // StringWriter is intended to take a copy of the cur_buffer pointer, as well + // as the maximum length of the string. This maximum length should not include + // the null terminator, since that's written separately. + StringWriter(char *__restrict buffer, size_t max_len = ~size_t(0)) + : cur_buffer(buffer), available_capacity(max_len) {} + + void write(const char *__restrict to_write, size_t len) { + if (len > available_capacity) + len = available_capacity; + + if (len > 0) { + inline_memcpy(cur_buffer, to_write, len); + cur_buffer += len; + available_capacity -= len; + } + } + // Terminate should only be called if the original max length passed to + // snprintf was greater than 0. It writes a null byte to the end of the + // cur_buffer, regardless of available_capacity. + void terminate() { *cur_buffer = '\0'; } +}; + +// write_to_string treats raw_pointer as a StringWriter and calls its write +// function. +void write_to_string(void *raw_pointer, const char *__restrict to_write, + size_t len) { + StringWriter *string_writer = reinterpret_cast(raw_pointer); + string_writer->write(to_write, 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,11 @@ // onto the end of output. WriteFunc raw_write; - size_t max_length; - size_t chars_written; + unsigned long long chars_written = 0; public: - Writer(void *output, WriteFunc raw_write, size_t max_length); + Writer(void *init_output, WriteFunc init_raw_write) + : output(init_output), raw_write(init_raw_write) {} // 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 +42,7 @@ // increments chars_written by length. void write_chars(char new_char, size_t length); - size_t get_chars_written(); + unsigned long long 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,37 @@ +//===-- 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) { + + raw_write(output, new_string, length); + + // chars_written tracks the number of chars that would have been written + // regardless of what the raw_write call does. + 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,206 @@ +//===-- 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]; + __llvm_libc::printf_core::StringWriter str_writer(str); + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_writer), + __llvm_libc::printf_core::write_to_string); +} + +TEST(LlvmLibcPrintfStringWriterTest, Write) { + char str[4] = {'D', 'E', 'F', 'G'}; + __llvm_libc::printf_core::StringWriter str_writer(str); + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_writer), + __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 tell it when to null terminate + // the string. Importantly, it can't tell the difference between an intended + // max length of 0 (write nothing) or 1 (write just a null byte), and so it + // relies on the caller to do that bounds check. + str_writer.terminate(); + + ASSERT_STREQ("abc", str); + ASSERT_EQ(writer.get_chars_written(), 3ull); +} + +TEST(LlvmLibcPrintfStringWriterTest, WriteMultipleTimes) { + char str[10]; + __llvm_libc::printf_core::StringWriter str_writer(str); + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_writer), + __llvm_libc::printf_core::write_to_string); + writer.write("abc", 3); + writer.write("DEF", 3); + writer.write("1234", 3); + + str_writer.terminate(); + + ASSERT_STREQ("abcDEF123", str); + ASSERT_EQ(writer.get_chars_written(), 9ull); +} + +TEST(LlvmLibcPrintfStringWriterTest, WriteChars) { + char str[4] = {'D', 'E', 'F', 'G'}; + __llvm_libc::printf_core::StringWriter str_writer(str); + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_writer), + __llvm_libc::printf_core::write_to_string); + writer.write_chars('a', 3); + + EXPECT_EQ(str[3], 'G'); + str_writer.terminate(); + + ASSERT_STREQ("aaa", str); + ASSERT_EQ(writer.get_chars_written(), 3ull); +} + +TEST(LlvmLibcPrintfStringWriterTest, WriteCharsMultipleTimes) { + char str[10]; + __llvm_libc::printf_core::StringWriter str_writer(str); + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_writer), + __llvm_libc::printf_core::write_to_string); + writer.write_chars('a', 3); + writer.write_chars('D', 3); + writer.write_chars('1', 3); + + str_writer.terminate(); + + ASSERT_STREQ("aaaDDD111", str); + ASSERT_EQ(writer.get_chars_written(), 9ull); +} + +TEST(LlvmLibcPrintfStringWriterTest, WriteManyChars) { + char str[100]; + __llvm_libc::printf_core::StringWriter str_writer(str); + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_writer), + __llvm_libc::printf_core::write_to_string); + writer.write_chars('Z', 99); + + str_writer.terminate(); + + ASSERT_STREQ("ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZZ" + "ZZZZZZZZZ", + str); + ASSERT_EQ(writer.get_chars_written(), 99ull); +} + +TEST(LlvmLibcPrintfStringWriterTest, MixedWrites) { + char str[13]; + __llvm_libc::printf_core::StringWriter str_writer(str); + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_writer), + __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_writer.terminate(); + + ASSERT_STREQ("aaaDEF111456", str); + ASSERT_EQ(writer.get_chars_written(), 12ull); +} + +TEST(LlvmLibcPrintfStringWriterTest, WriteWithMaxLength) { + char str[11]; + __llvm_libc::printf_core::StringWriter str_writer(str, 10); + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_writer), + __llvm_libc::printf_core::write_to_string); + writer.write("abcDEF123456", 12); + + str_writer.terminate(); + + ASSERT_STREQ("abcDEF1234", str); + ASSERT_EQ(writer.get_chars_written(), 12ull); +} + +TEST(LlvmLibcPrintfStringWriterTest, WriteCharsWithMaxLength) { + char str[11]; + __llvm_libc::printf_core::StringWriter str_writer(str, 10); + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_writer), + __llvm_libc::printf_core::write_to_string); + + writer.write_chars('1', 15); + + str_writer.terminate(); + + ASSERT_STREQ("1111111111", str); + ASSERT_EQ(writer.get_chars_written(), 15ull); +} + +TEST(LlvmLibcPrintfStringWriterTest, MixedWriteWithMaxLength) { + char str[11]; + __llvm_libc::printf_core::StringWriter str_writer(str, 10); + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_writer), + __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_writer.terminate(); + + ASSERT_STREQ("aaaDEF1114", str); + ASSERT_EQ(writer.get_chars_written(), 12ull); +} + +TEST(LlvmLibcPrintfStringWriterTest, StringWithMaxLengthOne) { + char str[1]; + __llvm_libc::printf_core::StringWriter str_writer(str, 0); + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_writer), + __llvm_libc::printf_core::write_to_string); + // This is because the max length should be at most 1 less than the size of + // the buffer it's writing to. + writer.write_chars('a', 3); + writer.write("DEF", 3); + writer.write_chars('1', 3); + writer.write("456", 3); + + str_writer.terminate(); + + ASSERT_STREQ("", str); + ASSERT_EQ(writer.get_chars_written(), 12ull); +} + +TEST(LlvmLibcPrintfStringWriterTest, NullStringWithZeroMaxLength) { + __llvm_libc::printf_core::StringWriter str_writer(nullptr, 0); + __llvm_libc::printf_core::Writer writer( + reinterpret_cast(&str_writer), + __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); + + ASSERT_EQ(writer.get_chars_written(), 12ull); +}