[libc] add printf writer
authorMichael Jones <michaelrj@google.com>
Mon, 25 Apr 2022 22:46:03 +0000 (15:46 -0700)
committerMichael Jones <michaelrj@google.com>
Tue, 3 May 2022 17:15:04 +0000 (10:15 -0700)
The printf implmentation is made up of three main pieces, the parser,
the converter, and the writer. This patch adds the implementation for
the writer, as well as the function for writing to a string, along with
tests.

Reviewed By: sivachandra, lntue

Differential Revision: https://reviews.llvm.org/D124421

libc/src/stdio/printf_core/CMakeLists.txt
libc/src/stdio/printf_core/string_writer.h [new file with mode: 0644]
libc/src/stdio/printf_core/writer.cpp [new file with mode: 0644]
libc/src/stdio/printf_core/writer.h
libc/test/src/stdio/printf_core/CMakeLists.txt
libc/test/src/stdio/printf_core/string_writer_test.cpp [new file with mode: 0644]

index f930225..d167a01 100644 (file)
@@ -19,3 +19,21 @@ add_object_library(
     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 (file)
index 0000000..d8d6dbf
--- /dev/null
@@ -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 <stddef.h>
+
+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<StringWriter *>(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.cpp b/libc/src/stdio/printf_core/writer.cpp
new file mode 100644 (file)
index 0000000..8f5e516
--- /dev/null
@@ -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 <stddef.h>
+
+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
index 47aa05a..0ad37e4 100644 (file)
@@ -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 @@ class Writer final {
   // 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 @@ public:
   // 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
index 04544ff..23e3133 100644 (file)
@@ -10,3 +10,14 @@ add_libc_unittest(
 )
 
 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 (file)
index 0000000..6701a23
--- /dev/null
@@ -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<void *>(&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<void *>(&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<void *>(&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<void *>(&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<void *>(&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<void *>(&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<void *>(&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<void *>(&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<void *>(&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<void *>(&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<void *>(&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<void *>(&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);
+}