microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
pmr_sink.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
9#include "../microfmt.hpp"
10#include <cassert>
11#include <cstddef>
12#include <memory_resource>
13#include <new>
14#include <string>
15#include <string_view>
16
17namespace microfmt::pmr {
18
19// ============================================================================
20// PMR Typedef Aliases
21// ============================================================================
22
23using string = std::pmr::string;
24using vector = std::pmr::vector<char>;
25
26// ============================================================================
27// PMR String Factory
28// ============================================================================
29
30// bridges to a C/platform API (FILE*, syslog, android log, raw chunk storage) that takes raw pointers.
32template <typename... Args>
33[[nodiscard]] std::pmr::string
34// NOLINTNEXTLINE(reloco-noexcept): std container allocation may throw (std-interop contract)
35format(std::pmr::memory_resource *mr, microfmt::string_view fmt_str, const Args &...args) {
36 std::pmr::string result(mr);
37
38 auto out_sink = sink{&result, [](void *ctx, microfmt::string_view sv) noexcept {
39 auto *str = static_cast<std::pmr::string *>(ctx);
40 str->append(sv.data(), sv.size());
41 }};
42
44 return result;
45}
47
48// ============================================================================
49// PMR Vector Factory
50// ============================================================================
51
52// bridges to a C/platform API (FILE*, syslog, android log, raw chunk storage) that takes raw pointers.
54template <typename... Args>
55[[nodiscard]] std::pmr::vector<char>
56// NOLINTNEXTLINE(reloco-noexcept): std container allocation may throw (std-interop contract)
57format_vector(std::pmr::memory_resource *mr, microfmt::string_view fmt_str, const Args &...args) {
58 std::pmr::vector<char> result(mr);
59
60 auto out_sink = sink{&result, [](void *ctx, microfmt::string_view sv) noexcept {
61 auto *vec = static_cast<std::pmr::vector<char> *>(ctx);
62 vec->insert(vec->end(), sv.begin(), sv.end());
63 }};
64
66 return result;
67}
69
70// ============================================================================
71// PMR Monotonic Arena Sink (Direct Stream Allocator)
72// ============================================================================
73
75public:
76 explicit arena_sink(std::pmr::memory_resource *mr RELOCO_LIFETIMEBOUND, std::size_t initial_chunk_size = 128) noexcept
77 : mr_(mr) {
78 assert(mr_);
80 allocate_chunk(initial_chunk_size);
81 }
82
83 ~arena_sink() noexcept { release_chunks(); }
84
85 arena_sink(const arena_sink &) = delete;
86 arena_sink &operator=(const arena_sink &) = delete;
88 : mr_(other.mr_), current_chunk_(other.current_chunk_), size_(other.size_), current_cap_(other.current_cap_) {
89 other.current_chunk_ = nullptr;
90 other.size_ = 0;
91 other.current_cap_ = 0;
92 }
93
95 if (this != &other) {
96 release_chunks();
97 mr_ = other.mr_;
98 current_chunk_ = other.current_chunk_;
99 size_ = other.size_;
100 current_cap_ = other.current_cap_;
101 other.current_chunk_ = nullptr;
102 other.size_ = 0;
103 other.current_cap_ = 0;
104 }
105 return *this;
106 }
107
109 return sink{this, [](void *ctx, microfmt::string_view sv) noexcept { static_cast<arena_sink *>(ctx)->write(sv); }};
110 }
111
112 void put(char c) noexcept {
113 if (size_ >= current_cap_) {
114 grow();
115 }
116 RELOCO_ASSERT(size_ < current_cap_, "Array access out of bounds");
118 current_chunk_->data[size_++] = c;
120 }
121
122 void write(microfmt::string_view sv) noexcept {
123 for (char c : sv) {
124 put(c);
125 }
126 }
127
128 [[nodiscard]] microfmt::string_view view() const noexcept RELOCO_LIFETIMEBOUND {
129 return microfmt::string_view(current_chunk_->data, size_);
130 }
131
132private:
133 // NOLINTBEGIN(reloco-pointer-annotation,reloco-no-raw-pointer,reloco-no-c-array): intrusive list node with trailing
134 // storage, living in the memory resource's block
135 struct chunk {
136 chunk *previous = nullptr;
137 std::size_t capacity = 0;
138 char data[1]{};
139 };
140 // NOLINTEND(reloco-pointer-annotation,reloco-no-raw-pointer,reloco-no-c-array)
141
142 [[nodiscard]] static std::size_t allocation_size(std::size_t capacity) noexcept {
143 return sizeof(chunk) - sizeof(char) + capacity;
144 }
145
146 void allocate_chunk(std::size_t capacity) noexcept {
147 void *storage = mr_->allocate(allocation_size(capacity), alignof(chunk));
148 current_chunk_ = ::new (storage) chunk{current_chunk_, capacity, {}};
149 current_cap_ = capacity;
150 size_ = 0;
151 }
152
153 void grow() noexcept {
154 const std::size_t new_capacity = current_cap_ * 2;
155 chunk *previous_chunk = current_chunk_;
156 const std::size_t previous_size = size_;
157 allocate_chunk(new_capacity);
158 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
159 for (std::size_t i = 0; i < previous_size; ++i) {
160 current_chunk_->data[i] = previous_chunk->data[i];
161 }
162 RELOCO_END_UNSAFE_BUFFER_USAGE;
163 size_ = previous_size;
164 }
165
166 void release_chunks() noexcept {
167 while (current_chunk_ != nullptr) {
168 chunk *previous = current_chunk_->previous;
169 const std::size_t capacity = current_chunk_->capacity;
170 current_chunk_->~chunk();
171 mr_->deallocate(current_chunk_, allocation_size(capacity), alignof(chunk));
172 current_chunk_ = previous;
173 }
174 }
175
176 value_ptr<std::pmr::memory_resource> mr_;
177 chunk *current_chunk_{nullptr};
178 std::size_t size_{0};
179 std::size_t current_cap_{0};
180};
181
182} // namespace microfmt::pmr
Definition pmr_sink.hpp:74
arena_sink & operator=(const arena_sink &)=delete
arena_sink(std::pmr::memory_resource *mr RELOCO_LIFETIMEBOUND, std::size_t initial_chunk_size=128) noexcept
Definition pmr_sink.hpp:76
arena_sink(arena_sink &&other) noexcept
Definition pmr_sink.hpp:87
void put(char c) noexcept
Definition pmr_sink.hpp:112
arena_sink & operator=(arena_sink &&other) noexcept
Definition pmr_sink.hpp:94
void write(microfmt::string_view sv) noexcept
Definition pmr_sink.hpp:122
microfmt::string_view view() const noexcept RELOCO_LIFETIMEBOUND
Definition pmr_sink.hpp:128
arena_sink(const arena_sink &)=delete
~arena_sink() noexcept
Definition pmr_sink.hpp:83
sink as_sink() noexcept RELOCO_LIFETIMEBOUND
Definition pmr_sink.hpp:108
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Definition pmr_sink.hpp:17
std::pmr::vector< char > vector
Definition pmr_sink.hpp:24
std::pmr::string string
Definition pmr_sink.hpp:23
RELOCO_BEGIN_UNSAFE_CODE std::pmr::string format(std::pmr::memory_resource *mr, microfmt::string_view fmt_str, const Args &...args)
Definition pmr_sink.hpp:35
RELOCO_END_UNSAFE_CODE RELOCO_BEGIN_UNSAFE_CODE std::pmr::vector< char > format_vector(std::pmr::memory_resource *mr, microfmt::string_view fmt_str, const Args &...args)
Definition pmr_sink.hpp:57
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
RELOCO_CONSTEXPR20 void format_to(const sink &out, microfmt::string_view fmt, const Args &...args) noexcept
Definition microfmt.hpp:1766
constexpr auto vec(const T(&arr)[N]) noexcept
Definition math.hpp:28
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132