70 changes: 60 additions & 10 deletions libc/src/stdio/printf_core/vfprintf_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,74 @@
#include "src/__support/File/file.h"
#include "src/__support/arg_list.h"
#include "src/__support/macros/attributes.h" // For LIBC_INLINE
#include "src/stdio/printf_core/file_writer.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 <stdio.h>

namespace __llvm_libc {

namespace internal {
#ifndef LIBC_COPT_PRINTF_USE_SYSTEM_FILE
LIBC_INLINE int ferror_unlocked(FILE *f) {
return reinterpret_cast<__llvm_libc::File *>(f)->error_unlocked();
}

LIBC_INLINE void flockfile(FILE *f) {
reinterpret_cast<__llvm_libc::File *>(f)->lock();
}

LIBC_INLINE void funlockfile(FILE *f) {
reinterpret_cast<__llvm_libc::File *>(f)->unlock();
}

LIBC_INLINE int fwrite_unlocked(const void *ptr, size_t size, size_t nmemb,
FILE *f) {
return reinterpret_cast<__llvm_libc::File *>(f)->write_unlocked(ptr,
size * nmemb);
}
#else // defined(LIBC_COPT_PRINTF_USE_SYSTEM_FILE)
LIBC_INLINE int ferror_unlocked(::FILE *f) { return ::ferror_unlocked(f); }

LIBC_INLINE void flockfile(::FILE *f) { ::flockfile(f); }

LIBC_INLINE void funlockfile(::FILE *f) { ::funlockfile(f); }

LIBC_INLINE int fwrite_unlocked(const void *ptr, size_t size, size_t nmemb,
::FILE *f) {
return ::fwrite_unlocked(ptr, size, nmemb, f);
}
#endif // LIBC_COPT_PRINTF_USE_SYSTEM_FILE
} // namespace internal

namespace printf_core {

template <typename file_t>
LIBC_INLINE int vfprintf_internal(file_t *__restrict stream,
const char *__restrict format,
internal::ArgList &args) {
FileWriter<file_t> file_writer(stream);
Writer writer(reinterpret_cast<void *>(&file_writer),
FileWriter<file_t>::write_str, FileWriter<file_t>::write_chars,
FileWriter<file_t>::write_char);
return printf_main(&writer, format, args);
int file_write_hook(cpp::string_view new_str, void *fp) {
::FILE *target_file = reinterpret_cast<::FILE *>(fp);
// Write new_str to the target file. The logic preventing a zero-length write
// is in the writer, so we don't check here.
size_t written = internal::fwrite_unlocked(new_str.data(), sizeof(char),
new_str.size(), target_file);
if (written != new_str.size() || internal::ferror_unlocked(target_file))
return FILE_WRITE_ERROR;
return WRITE_OK;
}

int vfprintf_internal(::FILE *__restrict stream, const char *__restrict format,
internal::ArgList &args) {
constexpr size_t BUFF_SIZE = 1024;
char buffer[BUFF_SIZE];
printf_core::WriteBuffer wb(buffer, BUFF_SIZE, &file_write_hook,
reinterpret_cast<void *>(stream));
Writer writer(&wb);
internal::flockfile(stream);
int retval = printf_main(&writer, format, args);
int flushval = wb.overflow_write("");
if (flushval != WRITE_OK)
retval = flushval;
internal::funlockfile(stream);
return retval;
}

} // namespace printf_core
Expand Down
39 changes: 27 additions & 12 deletions libc/src/stdio/printf_core/writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,39 @@

#include "writer.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/macros/optimization.h"
#include "src/stdio/printf_core/core_structs.h"
#include "src/string/memory_utils/inline_memcpy.h"
#include "src/string/memory_utils/inline_memset.h"
#include <stddef.h>

namespace __llvm_libc {
namespace printf_core {

int Writer::write(cpp::string_view new_string) {
chars_written += new_string.size();
return str_write(output, new_string);
}

int Writer::write(char new_char, size_t length) {
chars_written += length;
return chars_write(output, new_char, length);
}
int Writer::pad(char new_char, size_t length) {
// First, fill as much of the buffer as possible with the padding char.
size_t written = 0;
const size_t buff_space = wb->buff_len - wb->buff_cur;
// ASSERT: length > buff_space
if (buff_space > 0) {
inline_memset(wb->buff + wb->buff_cur, new_char, buff_space);
wb->buff_cur += buff_space;
written = buff_space;
}

int Writer::write(char new_char) {
chars_written += 1;
return char_write(output, new_char);
// Next, overflow write the rest of length using the mini_buff.
constexpr size_t MINI_BUFF_SIZE = 64;
char mini_buff[MINI_BUFF_SIZE];
inline_memset(mini_buff, new_char, MINI_BUFF_SIZE);
cpp::string_view mb_string_view(mini_buff, MINI_BUFF_SIZE);
while (written + MINI_BUFF_SIZE < length) {
int result = wb->overflow_write(mb_string_view);
if (result != WRITE_OK)
return result;
written += MINI_BUFF_SIZE;
}
cpp::string_view mb_substr = mb_string_view.substr(0, length - written);
return wb->overflow_write(mb_substr);
}

} // namespace printf_core
Expand Down
146 changes: 111 additions & 35 deletions libc/src/stdio/printf_core/writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,51 +10,127 @@
#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_WRITER_H

#include "src/__support/CPP/string_view.h"
#include "src/__support/macros/optimization.h"
#include "src/stdio/printf_core/core_structs.h"
#include "src/string/memory_utils/inline_memcpy.h"
#include "src/string/memory_utils/inline_memset.h"

#include <stddef.h>

namespace __llvm_libc {
namespace printf_core {

using WriteStrFunc = int (*)(void *, cpp::string_view);
using WriteCharsFunc = int (*)(void *, char, size_t);
using WriteCharFunc = int (*)(void *, char);
struct WriteBuffer {
using StreamWriter = int (*)(cpp::string_view, void *);
char *buff;
const size_t buff_len;
size_t buff_cur = 0;

class Writer final {
// output is a pointer to the string or file that the writer is meant to write
// to.
void *output;

// raw_write is a function that, when called on output with a char* and
// length, will copy the number of bytes equal to the length from the char*
// onto the end of output. It should return a positive number or zero on
// success, or a negative number on failure.
WriteStrFunc str_write;
WriteCharsFunc chars_write;
WriteCharFunc char_write;
// The stream writer will be called when the buffer is full. It will be passed
// string_views to write to the stream.
StreamWriter stream_writer;
void *output_target;

LIBC_INLINE WriteBuffer(char *Buff, size_t Buff_len, StreamWriter hook,
void *target)
: buff(Buff), buff_len(Buff_len), stream_writer(hook),
output_target(target) {}

LIBC_INLINE WriteBuffer(char *Buff, size_t Buff_len)
: buff(Buff), buff_len(Buff_len), stream_writer(nullptr),
output_target(nullptr) {}

// 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.
int overflow_write(cpp::string_view 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 {
// 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;
}
}
};

class Writer final {
WriteBuffer *wb;
int chars_written = 0;

// This is a separate, non-inlined function so that the inlined part of the
// write function is shorter.
int pad(char new_char, size_t length);

public:
Writer(void *init_output, WriteStrFunc init_str_write,
WriteCharsFunc init_chars_write, WriteCharFunc init_char_write)
: output(init_output), str_write(init_str_write),
chars_write(init_chars_write), char_write(init_char_write) {}

// write will copy new_string into output using str_write. It increments
// chars_written by the length of new_string. It returns the result of
// str_write.
int write(cpp::string_view new_string);

// this version of write will copy length copies of new_char into output using
// chars_write. This is primarily used for padding. It returns the result of
// chars_write.
int write(char new_char, size_t len);

// this version of write will copy just new_char into output. This is often
// used for negative signs. It returns the result of chars_write.
int write(char new_char);

int get_chars_written() { return chars_written; }
LIBC_INLINE Writer(WriteBuffer *WB) : wb(WB) {}

// Takes a string, copies it into the buffer if there is space, else passes it
// to the overflow mechanism to be handled separately.
LIBC_INLINE int write(cpp::string_view new_string) {
chars_written += new_string.size();
if (LIBC_LIKELY(wb->buff_cur + new_string.size() <= wb->buff_len)) {
inline_memcpy(wb->buff + wb->buff_cur, new_string.data(),
new_string.size());
wb->buff_cur += new_string.size();
return WRITE_OK;
}
return wb->overflow_write(new_string);
}

// Takes a char and a length, memsets the next length characters of the buffer
// if there is space, else calls pad which will loop and call the overflow
// mechanism on a secondary buffer.
LIBC_INLINE int write(char new_char, size_t length) {
chars_written += length;

if (LIBC_LIKELY(wb->buff_cur + length <= wb->buff_len)) {
inline_memset(wb->buff + wb->buff_cur, new_char, length);
wb->buff_cur += length;
return WRITE_OK;
}
return pad(new_char, length);
}

// Takes a char, copies it into the buffer if there is space, else passes it
// to the overflow mechanism to be handled separately.
LIBC_INLINE int write(char new_char) {
chars_written += 1;
if (LIBC_LIKELY(wb->buff_cur + 1 <= wb->buff_len)) {
wb->buff[wb->buff_cur] = new_char;
wb->buff_cur += 1;
return WRITE_OK;
}
cpp::string_view char_string_view(&new_char, 1);
return wb->overflow_write(char_string_view);
}

LIBC_INLINE int get_chars_written() { return chars_written; }
};

} // namespace printf_core
Expand Down
10 changes: 3 additions & 7 deletions libc/src/stdio/snprintf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

#include "src/__support/arg_list.h"
#include "src/stdio/printf_core/printf_main.h"
#include "src/stdio/printf_core/string_writer.h"
#include "src/stdio/printf_core/writer.h"

#include <stdarg.h>
Expand All @@ -27,15 +26,12 @@ LLVM_LIBC_FUNCTION(int, snprintf,
// and pointer semantics, as well as handling
// destruction automatically.
va_end(vlist);
printf_core::StringWriter str_writer(buffer, (buffsz > 0 ? buffsz - 1 : 0));
printf_core::Writer writer(reinterpret_cast<void *>(&str_writer),
printf_core::StringWriter::write_str,
printf_core::StringWriter::write_chars,
printf_core::StringWriter::write_char);
printf_core::WriteBuffer wb(buffer, (buffsz > 0 ? buffsz - 1 : 0));
printf_core::Writer writer(&wb);

int ret_val = printf_core::printf_main(&writer, format, args);
if (buffsz > 0) // if the buffsz is 0 the buffer may be a null pointer.
str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';
return ret_val;
}

Expand Down
12 changes: 5 additions & 7 deletions libc/src/stdio/sprintf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

#include "src/stdio/sprintf.h"

#include "src/__support/CPP/limits.h"
#include "src/__support/arg_list.h"
#include "src/stdio/printf_core/printf_main.h"
#include "src/stdio/printf_core/string_writer.h"
#include "src/stdio/printf_core/writer.h"

#include <stdarg.h>
Expand All @@ -26,14 +26,12 @@ LLVM_LIBC_FUNCTION(int, sprintf,
// and pointer semantics, as well as handling
// destruction automatically.
va_end(vlist);
printf_core::StringWriter str_writer(buffer);
printf_core::Writer writer(reinterpret_cast<void *>(&str_writer),
printf_core::StringWriter::write_str,
printf_core::StringWriter::write_chars,
printf_core::StringWriter::write_char);

printf_core::WriteBuffer wb(buffer, cpp::numeric_limits<size_t>::max());
printf_core::Writer writer(&wb);

int ret_val = printf_core::printf_main(&writer, format, args);
str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';
return ret_val;
}

Expand Down
32 changes: 15 additions & 17 deletions libc/test/src/stdio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,35 +135,33 @@ add_libc_unittest(
libc.src.stdio.snprintf
)

# In fullbuild mode, fprintf's tests use the internal FILE for other functions.
if(LLVM_LIBC_FULL_BUILD)
add_libc_unittest(
fprintf_test
SUITE
libc_stdio_unittests
SRCS
fprintf_test.cpp
DEPENDS
libc.src.stdio.fprintf
libc.src.stdio.fclose
libc.src.stdio.ferror
libc.src.stdio.fopen
libc.src.stdio.fread
list(APPEND fprintf_test_deps
libc.src.stdio.fprintf
)
if(LLVM_LIBC_FULL_BUILD)
# In fullbuild mode, fprintf's tests use the internal FILE for other functions.
list(APPEND fprintf_test_deps
libc.src.stdio.fclose
libc.src.stdio.ferror
libc.src.stdio.fopen
libc.src.stdio.fread
)
else()
# Else in overlay mode they use the system's FILE.
set(fprintf_test_copts "-DLIBC_COPT_PRINTF_USE_SYSTEM_FILE")
endif()

add_libc_unittest(
fprintf_test
SUITE
libc_stdio_unittests
SRCS
fprintf_test.cpp
DEPENDS
libc.src.stdio.fprintf
${fprintf_test_deps}
COMPILE_OPTIONS
-DLIBC_COPT_PRINTF_USE_SYSTEM_FILE
${fprintf_test_copts}
)
endif()

add_libc_unittest(
printf_test
Expand Down
7 changes: 3 additions & 4 deletions libc/test/src/stdio/printf_core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ add_libc_unittest(
)

add_libc_unittest(
string_writer_test
writer_test
SUITE
libc_stdio_unittests
SRCS
string_writer_test.cpp
writer_test.cpp
DEPENDS
libc.src.stdio.printf_core.writer
libc.src.stdio.printf_core.string_writer
libc.src.string.memory_utils.inline_memcpy
libc.src.__support.CPP.string_view
)

Expand All @@ -34,6 +34,5 @@ add_libc_unittest(
DEPENDS
libc.src.stdio.printf_core.converter
libc.src.stdio.printf_core.writer
libc.src.stdio.printf_core.string_writer
libc.src.stdio.printf_core.core_structs
)
40 changes: 18 additions & 22 deletions libc/test/src/stdio/printf_core/converter_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

#include "src/stdio/printf_core/converter.h"
#include "src/stdio/printf_core/core_structs.h"
#include "src/stdio/printf_core/string_writer.h"
#include "src/stdio/printf_core/writer.h"

#include "test/UnitTest/Test.h"
Expand All @@ -19,13 +18,10 @@ class LlvmLibcPrintfConverterTest : public __llvm_libc::testing::Test {
// void TearDown() override {}

char str[60];
__llvm_libc::printf_core::StringWriter str_writer =
__llvm_libc::printf_core::StringWriter(str);
__llvm_libc::printf_core::Writer writer = __llvm_libc::printf_core::Writer(
reinterpret_cast<void *>(&str_writer),
__llvm_libc::printf_core::StringWriter::write_str,
__llvm_libc::printf_core::StringWriter::write_chars,
__llvm_libc::printf_core::StringWriter::write_char);
__llvm_libc::printf_core::WriteBuffer wb =
__llvm_libc::printf_core::WriteBuffer(str, sizeof(str) - 1);
__llvm_libc::printf_core::Writer writer =
__llvm_libc::printf_core::Writer(&wb);
};

TEST_F(LlvmLibcPrintfConverterTest, SimpleRawConversion) {
Expand All @@ -35,7 +31,7 @@ TEST_F(LlvmLibcPrintfConverterTest, SimpleRawConversion) {

__llvm_libc::printf_core::convert(&writer, raw_section);

str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ(str, "abc");
ASSERT_EQ(writer.get_chars_written(), 3);
Expand All @@ -49,7 +45,7 @@ TEST_F(LlvmLibcPrintfConverterTest, PercentConversion) {

__llvm_libc::printf_core::convert(&writer, simple_conv);

str[1] = '\0';
wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ(str, "%");
ASSERT_EQ(writer.get_chars_written(), 1);
Expand All @@ -67,7 +63,7 @@ TEST_F(LlvmLibcPrintfConverterTest, CharConversionSimple) {

__llvm_libc::printf_core::convert(&writer, simple_conv);

str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ(str, "D");
ASSERT_EQ(writer.get_chars_written(), 1);
Expand All @@ -82,7 +78,7 @@ TEST_F(LlvmLibcPrintfConverterTest, CharConversionRightJustified) {
right_justified_conv.conv_val_raw = 'E';
__llvm_libc::printf_core::convert(&writer, right_justified_conv);

str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ(str, " E");
ASSERT_EQ(writer.get_chars_written(), 4);
Expand All @@ -99,7 +95,7 @@ TEST_F(LlvmLibcPrintfConverterTest, CharConversionLeftJustified) {
left_justified_conv.conv_val_raw = 'F';
__llvm_libc::printf_core::convert(&writer, left_justified_conv);

str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ(str, "F ");
ASSERT_EQ(writer.get_chars_written(), 4);
Expand All @@ -115,7 +111,7 @@ TEST_F(LlvmLibcPrintfConverterTest, StringConversionSimple) {

__llvm_libc::printf_core::convert(&writer, simple_conv);

str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ(str, "DEF");
ASSERT_EQ(writer.get_chars_written(), 3);
Expand All @@ -130,7 +126,7 @@ TEST_F(LlvmLibcPrintfConverterTest, StringConversionPrecisionHigh) {
high_precision_conv.conv_val_ptr = const_cast<char *>("456");
__llvm_libc::printf_core::convert(&writer, high_precision_conv);

str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ(str, "456");
ASSERT_EQ(writer.get_chars_written(), 3);
Expand All @@ -145,7 +141,7 @@ TEST_F(LlvmLibcPrintfConverterTest, StringConversionPrecisionLow) {
low_precision_conv.conv_val_ptr = const_cast<char *>("xyz");
__llvm_libc::printf_core::convert(&writer, low_precision_conv);

str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ(str, "xy");
ASSERT_EQ(writer.get_chars_written(), 2);
Expand All @@ -160,7 +156,7 @@ TEST_F(LlvmLibcPrintfConverterTest, StringConversionRightJustified) {
right_justified_conv.conv_val_ptr = const_cast<char *>("789");
__llvm_libc::printf_core::convert(&writer, right_justified_conv);

str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ(str, " 789");
ASSERT_EQ(writer.get_chars_written(), 4);
Expand All @@ -177,7 +173,7 @@ TEST_F(LlvmLibcPrintfConverterTest, StringConversionLeftJustified) {
left_justified_conv.conv_val_ptr = const_cast<char *>("ghi");
__llvm_libc::printf_core::convert(&writer, left_justified_conv);

str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ(str, "ghi ");
ASSERT_EQ(writer.get_chars_written(), 4);
Expand All @@ -191,7 +187,7 @@ TEST_F(LlvmLibcPrintfConverterTest, IntConversionSimple) {
section.conv_val_raw = 12345;
__llvm_libc::printf_core::convert(&writer, section);

str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ(str, "12345");
ASSERT_EQ(writer.get_chars_written(), 5);
Expand All @@ -209,7 +205,7 @@ TEST_F(LlvmLibcPrintfConverterTest, HexConversion) {
section.conv_val_raw = 0x123456ab;
__llvm_libc::printf_core::convert(&writer, section);

str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';
ASSERT_STREQ(str, "0x00000000123456ab");
ASSERT_EQ(writer.get_chars_written(), 18);
}
Expand All @@ -223,7 +219,7 @@ TEST_F(LlvmLibcPrintfConverterTest, PointerConversion) {
section.conv_val_ptr = (void *)(0x123456ab);
__llvm_libc::printf_core::convert(&writer, section);

str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';
ASSERT_STREQ(str, "0x123456ab");
ASSERT_EQ(writer.get_chars_written(), 10);
}
Expand All @@ -237,7 +233,7 @@ TEST_F(LlvmLibcPrintfConverterTest, OctConversion) {
section.conv_val_raw = 01234;
__llvm_libc::printf_core::convert(&writer, section);

str_writer.terminate();
wb.buff[wb.buff_cur] = '\0';
ASSERT_STREQ(str, "1234");
ASSERT_EQ(writer.get_chars_written(), 4);
}
205 changes: 0 additions & 205 deletions libc/test/src/stdio/printf_core/string_writer_test.cpp

This file was deleted.

310 changes: 310 additions & 0 deletions libc/test/src/stdio/printf_core/writer_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
//===-- 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/__support/CPP/string_view.h"
#include "src/stdio/printf_core/writer.h"

#include "src/string/memory_utils/inline_memcpy.h"

#include "test/UnitTest/Test.h"

using __llvm_libc::cpp::string_view;
using __llvm_libc::printf_core::WriteBuffer;
using __llvm_libc::printf_core::Writer;

TEST(LlvmLibcPrintfWriterTest, Constructor) {
char str[10];
WriteBuffer wb(str, sizeof(str) - 1);
Writer writer(&wb);
(void)writer;
}

TEST(LlvmLibcPrintfWriterTest, Write) {
char str[4] = {'D', 'E', 'F', 'G'};
WriteBuffer wb(str, sizeof(str) - 1);
Writer writer(&wb);
writer.write({"abc", 3});

EXPECT_EQ(str[3], 'G');

// The string must be null terminated manually since the writer cannot tell
// when it's done.
wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ("abc", str);
ASSERT_EQ(writer.get_chars_written(), 3);
}

TEST(LlvmLibcPrintfWriterTest, WriteMultipleTimes) {
char str[10];
WriteBuffer wb(str, sizeof(str) - 1);
Writer writer(&wb);
writer.write({"abc", 3});
writer.write({"DEF", 3});
writer.write({"1234", 3});

wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ("abcDEF123", str);
ASSERT_EQ(writer.get_chars_written(), 9);
}

TEST(LlvmLibcPrintfWriterTest, WriteChars) {
char str[4] = {'D', 'E', 'F', 'G'};
WriteBuffer wb(str, sizeof(str) - 1);
Writer writer(&wb);
writer.write('a', 3);

EXPECT_EQ(str[3], 'G');
wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ("aaa", str);
ASSERT_EQ(writer.get_chars_written(), 3);
}

TEST(LlvmLibcPrintfWriterTest, WriteCharsMultipleTimes) {
char str[10];
WriteBuffer wb(str, sizeof(str) - 1);
Writer writer(&wb);
writer.write('a', 3);
writer.write('D', 3);
writer.write('1', 3);

wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ("aaaDDD111", str);
ASSERT_EQ(writer.get_chars_written(), 9);
}

TEST(LlvmLibcPrintfWriterTest, WriteManyChars) {
char str[100];
WriteBuffer wb(str, sizeof(str) - 1);
Writer writer(&wb);
writer.write('Z', 99);

wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ("ZZZZZZZZZZ"
"ZZZZZZZZZZ"
"ZZZZZZZZZZ"
"ZZZZZZZZZZ"
"ZZZZZZZZZZ"
"ZZZZZZZZZZ"
"ZZZZZZZZZZ"
"ZZZZZZZZZZ"
"ZZZZZZZZZZ"
"ZZZZZZZZZ",
str);
ASSERT_EQ(writer.get_chars_written(), 99);
}

TEST(LlvmLibcPrintfWriterTest, MixedWrites) {
char str[13];
WriteBuffer wb(str, sizeof(str) - 1);
Writer writer(&wb);
writer.write('a', 3);
writer.write({"DEF", 3});
writer.write('1', 3);
writer.write({"456", 3});

wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ("aaaDEF111456", str);
ASSERT_EQ(writer.get_chars_written(), 12);
}

TEST(LlvmLibcPrintfWriterTest, WriteWithMaxLength) {
char str[11];
WriteBuffer wb(str, sizeof(str) - 1);
Writer writer(&wb);
writer.write({"abcDEF123456", 12});

wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ("abcDEF1234", str);
ASSERT_EQ(writer.get_chars_written(), 12);
}

TEST(LlvmLibcPrintfWriterTest, WriteCharsWithMaxLength) {
char str[11];
WriteBuffer wb(str, sizeof(str) - 1);
Writer writer(&wb);
writer.write('1', 15);

wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ("1111111111", str);
ASSERT_EQ(writer.get_chars_written(), 15);
}

TEST(LlvmLibcPrintfWriterTest, MixedWriteWithMaxLength) {
char str[11];
WriteBuffer wb(str, sizeof(str) - 1);

Writer writer(&wb);
writer.write('a', 3);
writer.write({"DEF", 3});
writer.write('1', 3);
writer.write({"456", 3});

wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ("aaaDEF1114", str);
ASSERT_EQ(writer.get_chars_written(), 12);
}

TEST(LlvmLibcPrintfWriterTest, StringWithMaxLengthOne) {
char str[1];
// This is because the max length should be at most 1 less than the size of
// the buffer it's writing to.
WriteBuffer wb(str, 0);

Writer writer(&wb);
writer.write('a', 3);
writer.write({"DEF", 3});
writer.write('1', 3);
writer.write({"456", 3});

wb.buff[wb.buff_cur] = '\0';

ASSERT_STREQ("", str);
ASSERT_EQ(writer.get_chars_written(), 12);
}

TEST(LlvmLibcPrintfWriterTest, NullStringWithZeroMaxLength) {
WriteBuffer wb(nullptr, 0);

Writer writer(&wb);
writer.write('a', 3);
writer.write({"DEF", 3});
writer.write('1', 3);
writer.write({"456", 3});

ASSERT_EQ(writer.get_chars_written(), 12);
}

struct OutBuff {
char *out_str;
size_t cur_pos = 0;
};

int copy_to_out(string_view new_str, void *raw_out_buff) {
if (new_str.size() == 0) {
return 0;
}

OutBuff *out_buff = reinterpret_cast<OutBuff *>(raw_out_buff);

__llvm_libc::inline_memcpy(out_buff->out_str + out_buff->cur_pos,
new_str.data(), new_str.size());

out_buff->cur_pos += new_str.size();
return 0;
}

TEST(LlvmLibcPrintfWriterTest, WriteWithMaxLengthWithCallback) {
char str[16];

OutBuff out_buff = {str, 0};

char wb_buff[8];
WriteBuffer wb(wb_buff, sizeof(wb_buff), &copy_to_out,
reinterpret_cast<void *>(&out_buff));
Writer writer(&wb);
writer.write({"abcDEF123456", 12});

// Flush the buffer
wb.overflow_write("");
str[out_buff.cur_pos] = '\0';

ASSERT_STREQ("abcDEF123456", str);
ASSERT_EQ(writer.get_chars_written(), 12);
}

TEST(LlvmLibcPrintfWriterTest, WriteCharsWithMaxLengthWithCallback) {
char str[16];

OutBuff out_buff = {str, 0};

char wb_buff[8];
WriteBuffer wb(wb_buff, sizeof(wb_buff), &copy_to_out,
reinterpret_cast<void *>(&out_buff));
Writer writer(&wb);
writer.write('1', 15);

// Flush the buffer
wb.overflow_write("");
str[out_buff.cur_pos] = '\0';

ASSERT_STREQ("111111111111111", str);
ASSERT_EQ(writer.get_chars_written(), 15);
}

TEST(LlvmLibcPrintfWriterTest, MixedWriteWithMaxLengthWithCallback) {
char str[16];

OutBuff out_buff = {str, 0};

char wb_buff[8];
WriteBuffer wb(wb_buff, sizeof(wb_buff), &copy_to_out,
reinterpret_cast<void *>(&out_buff));
Writer writer(&wb);
writer.write('a', 3);
writer.write({"DEF", 3});
writer.write('1', 3);
writer.write({"456", 3});

// Flush the buffer
wb.overflow_write("");
str[out_buff.cur_pos] = '\0';

ASSERT_STREQ("aaaDEF111456", str);
ASSERT_EQ(writer.get_chars_written(), 12);
}

TEST(LlvmLibcPrintfWriterTest, ZeroLengthBufferWithCallback) {
char str[16];

OutBuff out_buff = {str, 0};

char wb_buff[1];
WriteBuffer wb(wb_buff, 0, &copy_to_out, reinterpret_cast<void *>(&out_buff));

Writer writer(&wb);
writer.write('a', 3);
writer.write({"DEF", 3});
writer.write('1', 3);
writer.write({"456", 3});

// Flush the buffer
wb.overflow_write("");
str[out_buff.cur_pos] = '\0';

ASSERT_STREQ("aaaDEF111456", str);
ASSERT_EQ(writer.get_chars_written(), 12);
}

TEST(LlvmLibcPrintfWriterTest, NullStringWithZeroMaxLengthWithCallback) {
char str[16];

OutBuff out_buff = {str, 0};

WriteBuffer wb(nullptr, 0, &copy_to_out, reinterpret_cast<void *>(&out_buff));

Writer writer(&wb);
writer.write('a', 3);
writer.write({"DEF", 3});
writer.write('1', 3);
writer.write({"456", 3});

wb.overflow_write("");
str[out_buff.cur_pos] = '\0';

ASSERT_EQ(writer.get_chars_written(), 12);
ASSERT_STREQ("aaaDEF111456", str);
}
14 changes: 13 additions & 1 deletion libc/test/src/stdio/snprintf_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// these tests will focus on snprintf exclusive features.

TEST(LlvmLibcSNPrintfTest, CutOff) {
char buff[64];
char buff[100];
int written;

written =
Expand All @@ -26,6 +26,18 @@ TEST(LlvmLibcSNPrintfTest, CutOff) {
EXPECT_EQ(written, 10);
ASSERT_STREQ(buff, "1234");

written = __llvm_libc::snprintf(buff, 67, "%-101c", 'a');
EXPECT_EQ(written, 101);
ASSERT_STREQ(buff, "a "
" " // Each of these is 8 spaces, and there are 8.
" " // In total there are 65 spaces
" " // 'a' + 65 spaces + '\0' = 67
" "
" "
" "
" "
" ");

// passing null as the output pointer is allowed as long as buffsz is 0.
written = __llvm_libc::snprintf(nullptr, 0, "%s and more", "1234567890");
EXPECT_EQ(written, 19);
Expand Down
34 changes: 6 additions & 28 deletions utils/bazel/llvm-project-overlay/libc/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2576,39 +2576,17 @@ libc_support_library(
],
)

libc_support_library(
name = "printf_string_writer",
srcs = ["src/stdio/printf_core/string_writer.cpp"],
hdrs = ["src/stdio/printf_core/string_writer.h"],
defines = PRINTF_COPTS,
deps = [
":__support_cpp_string_view",
":libc_root",
":printf_core_structs",
":string_memory_utils",
],
)

libc_support_library(
name = "printf_file_writer",
hdrs = ["src/stdio/printf_core/file_writer.h"],
defines = PRINTF_COPTS,
deps = [
":__support_cpp_string_view",
":__support_file_file",
":libc_root",
":printf_core_structs",
],
)

libc_support_library(
name = "printf_writer",
srcs = ["src/stdio/printf_core/writer.cpp"],
hdrs = ["src/stdio/printf_core/writer.h"],
defines = PRINTF_COPTS,
deps = [
":__support_cpp_string_view",
":__support_macros_optimization",
":libc_root",
":printf_core_structs",
":string_memory_utils",
],
)

Expand Down Expand Up @@ -2671,9 +2649,9 @@ libc_function(
defines = PRINTF_COPTS,
deps = [
":__support_arg_list",
":__support_cpp_limits",
":errno",
":printf_main",
":printf_string_writer",
":printf_writer",
],
)
Expand All @@ -2687,7 +2665,6 @@ libc_function(
":__support_arg_list",
":errno",
":printf_main",
":printf_string_writer",
":printf_writer",
],
)
Expand All @@ -2700,7 +2677,6 @@ libc_support_library(
":__support_arg_list",
":__support_file_file",
":__support_macros_attributes",
":printf_file_writer",
":printf_main",
":printf_writer",
],
Expand All @@ -2713,6 +2689,7 @@ libc_function(
defines = PRINTF_COPTS,
deps = [
":__support_arg_list",
":__support_file_file",
":errno",
":vfprintf_internal",
],
Expand All @@ -2725,6 +2702,7 @@ libc_function(
defines = PRINTF_COPTS,
deps = [
":__support_arg_list",
":__support_file_file",
":errno",
":vfprintf_internal",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@ libc_test(
)

libc_test(
name = "printf_string_writer_test",
srcs = ["printf_core/string_writer_test.cpp"],
name = "printf_writer_test",
srcs = ["printf_core/writer_test.cpp"],
libc_function_deps = [
],
deps = [
"//libc:__support_arg_list",
"//libc:__support_cpp_string_view",
"//libc:printf_core_structs",
"//libc:printf_string_writer",
"//libc:printf_writer",
"//libc:string_memory_utils",
],
)

Expand All @@ -49,7 +49,6 @@ libc_test(
"//libc:__support_cpp_string_view",
"//libc:printf_converter",
"//libc:printf_core_structs",
"//libc:printf_string_writer",
"//libc:printf_writer",
],
)
Expand Down