217 lines · c
1//===-- Writer definition for printf ----------------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_WRITER_H10#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_WRITER_H11 12#include "src/__support/CPP/string_view.h"13#include "src/__support/macros/config.h"14#include "src/__support/macros/optimization.h"15#include "src/stdio/printf_core/core_structs.h"16#include "src/string/memory_utils/inline_memcpy.h"17#include "src/string/memory_utils/inline_memset.h"18 19#include <stddef.h>20 21namespace LIBC_NAMESPACE_DECL {22namespace printf_core {23 24enum class WriteMode {25 FILL_BUFF_AND_DROP_OVERFLOW,26 FLUSH_TO_STREAM,27 RESIZE_AND_FILL_BUFF,28 RUNTIME_DISPATCH,29};30 31// Helper to omit the template argument if we are using runtime dispatch and32// avoid multiple copies of the converter functions.33template <WriteMode write_mode> struct Mode {34#ifdef LIBC_COPT_PRINTF_RUNTIME_DISPATCH35 static constexpr WriteMode value = WriteMode::RUNTIME_DISPATCH;36#else37 static constexpr WriteMode value = write_mode;38#endif39};40 41template <WriteMode write_mode> struct WriteBuffer {42 using StreamWriter = int (*)(cpp::string_view, void *);43 char *buff;44 const char *init_buff; // for checking when resize.45 size_t buff_len;46 size_t buff_cur = 0;47 48 // The stream writer will be called when the buffer is full. It will be passed49 // string_views to write to the stream.50 const StreamWriter stream_writer;51 void *output_target;52 53 // The current writing mode in case the user wants runtime dispatch of the54 // stream writer with function pointers.55 [[maybe_unused]] WriteMode write_mode_;56 57 LIBC_INLINE WriteBuffer(char *buff, size_t buff_len, StreamWriter hook,58 void *target)59 : buff(buff), init_buff(buff), buff_len(buff_len), stream_writer(hook),60 output_target(target), write_mode_(WriteMode::FLUSH_TO_STREAM) {}61 62 LIBC_INLINE WriteBuffer(char *buff, size_t buff_len)63 : buff(buff), init_buff(buff), buff_len(buff_len), stream_writer(nullptr),64 output_target(nullptr),65 write_mode_(WriteMode::FILL_BUFF_AND_DROP_OVERFLOW) {}66 67 LIBC_INLINE WriteBuffer(char *buff, size_t buff_len, StreamWriter hook)68 : buff(buff), init_buff(buff), buff_len(buff_len), stream_writer(hook),69 output_target(this), write_mode_(WriteMode::RESIZE_AND_FILL_BUFF) {}70 71 LIBC_INLINE int flush_to_stream(cpp::string_view new_str) {72 if (buff_cur > 0) {73 int retval = stream_writer({buff, buff_cur}, output_target);74 if (retval < 0)75 return retval;76 }77 if (new_str.size() > 0) {78 int retval = stream_writer(new_str, output_target);79 if (retval < 0)80 return retval;81 }82 buff_cur = 0;83 return WRITE_OK;84 }85 86 LIBC_INLINE int fill_remaining_to_buff(cpp::string_view new_str) {87 if (buff_cur < buff_len) {88 size_t bytes_to_write = buff_len - buff_cur;89 if (bytes_to_write > new_str.size()) {90 bytes_to_write = new_str.size();91 }92 inline_memcpy(buff + buff_cur, new_str.data(), bytes_to_write);93 buff_cur += bytes_to_write;94 }95 return WRITE_OK;96 }97 98 LIBC_INLINE int resize_and_write(cpp::string_view new_str) {99 return stream_writer(new_str, output_target);100 }101 102 // The overflow_write method is intended to be called to write the contents of103 // the buffer and new_str to the stream_writer if it exists. If a resizing104 // hook is provided, it will resize the buffer and write the contents. If105 // neither a stream_writer nor a resizing hook is provided, it will fill the106 // remaining space in the buffer with new_str and drop the overflow. Calling107 // this with an empty string will flush the buffer if relevant.108 109 LIBC_INLINE int overflow_write(cpp::string_view new_str) {110 if constexpr (write_mode == WriteMode::RUNTIME_DISPATCH) {111 if (write_mode_ == WriteMode::FILL_BUFF_AND_DROP_OVERFLOW)112 return fill_remaining_to_buff(new_str);113 else if (write_mode_ == WriteMode::FLUSH_TO_STREAM)114 return flush_to_stream(new_str);115 else if (write_mode_ == WriteMode::RESIZE_AND_FILL_BUFF)116 return resize_and_write(new_str);117 } else if constexpr (write_mode == WriteMode::FILL_BUFF_AND_DROP_OVERFLOW) {118 return fill_remaining_to_buff(new_str);119 } else if constexpr (write_mode == WriteMode::FLUSH_TO_STREAM) {120 return flush_to_stream(new_str);121 } else if constexpr (write_mode == WriteMode::RESIZE_AND_FILL_BUFF) {122 return resize_and_write(new_str);123 }124 __builtin_unreachable();125 }126};127 128template <WriteMode write_mode> class Writer final {129 WriteBuffer<write_mode> &wb;130 size_t chars_written = 0;131 132 LIBC_INLINE int pad(char new_char, size_t length) {133 // First, fill as much of the buffer as possible with the padding char.134 size_t written = 0;135 const size_t buff_space = wb.buff_len - wb.buff_cur;136 // ASSERT: length > buff_space137 if (buff_space > 0) {138 inline_memset(wb.buff + wb.buff_cur, new_char, buff_space);139 wb.buff_cur += buff_space;140 written = buff_space;141 }142 143 // Next, overflow write the rest of length using the mini_buff.144 constexpr size_t MINI_BUFF_SIZE = 64;145 char mini_buff[MINI_BUFF_SIZE];146 inline_memset(mini_buff, new_char, MINI_BUFF_SIZE);147 cpp::string_view mb_string_view(mini_buff, MINI_BUFF_SIZE);148 while (written + MINI_BUFF_SIZE < length) {149 int result = wb.overflow_write(mb_string_view);150 if (result != WRITE_OK)151 return result;152 written += MINI_BUFF_SIZE;153 }154 cpp::string_view mb_substr = mb_string_view.substr(0, length - written);155 return wb.overflow_write(mb_substr);156 }157 158public:159 LIBC_INLINE Writer(WriteBuffer<write_mode> &wb) : wb(wb) {}160 161 // Takes a string, copies it into the buffer if there is space, else passes it162 // to the overflow mechanism to be handled separately.163 LIBC_INLINE int write(cpp::string_view new_string) {164 chars_written += new_string.size();165 if (LIBC_LIKELY(wb.buff_cur + new_string.size() <= wb.buff_len)) {166 inline_memcpy(wb.buff + wb.buff_cur, new_string.data(),167 new_string.size());168 wb.buff_cur += new_string.size();169 return WRITE_OK;170 }171 return wb.overflow_write(new_string);172 }173 174 // Takes a char and a length, memsets the next length characters of the buffer175 // if there is space, else calls pad which will loop and call the overflow176 // mechanism on a secondary buffer.177 LIBC_INLINE int write(char new_char, size_t length) {178 chars_written += length;179 180 if (LIBC_LIKELY(wb.buff_cur + length <= wb.buff_len)) {181 inline_memset(wb.buff + wb.buff_cur, static_cast<unsigned char>(new_char),182 length);183 wb.buff_cur += length;184 return WRITE_OK;185 }186 return pad(new_char, length);187 }188 189 // Takes a char, copies it into the buffer if there is space, else passes it190 // to the overflow mechanism to be handled separately.191 LIBC_INLINE int write(char new_char) {192 chars_written += 1;193 if (LIBC_LIKELY(wb.buff_cur + 1 <= wb.buff_len)) {194 wb.buff[wb.buff_cur] = new_char;195 wb.buff_cur += 1;196 return WRITE_OK;197 }198 cpp::string_view char_string_view(&new_char, 1);199 return wb.overflow_write(char_string_view);200 }201 202 LIBC_INLINE size_t get_chars_written() { return chars_written; }203};204 205// Class-template auto deduction helpers.206Writer(WriteBuffer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>)207 -> Writer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>;208Writer(WriteBuffer<WriteMode::RESIZE_AND_FILL_BUFF>)209 -> Writer<WriteMode::RESIZE_AND_FILL_BUFF>;210Writer(WriteBuffer<WriteMode::FLUSH_TO_STREAM>)211 -> Writer<WriteMode::FLUSH_TO_STREAM>;212 213} // namespace printf_core214} // namespace LIBC_NAMESPACE_DECL215 216#endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_WRITER_H217