PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
scratch_backing.hpp
Go to the documentation of this file.
1#pragma once
2
3// CR150-CX L4 / CR-221 scope B: contracts, not an OS backing factory.
4#include <cstddef>
5#include <cstdint>
6#include <cstring>
7#include <limits>
8#include <optional>
9
10namespace pineforge::detail {
11
13
14// No capacity/refusal status and no RunFailureCode conversion in this API.
15// end: an ordered seek found no record. cancelled: a visitor returned false.
16// not_found: an absent handle, a page past the extent, or nothing to reopen.
17// programming_error: caller contract violations. io_error: backing I/O faults,
18// including medium exhaustion or a backing that cannot extend.
19// corrupt: stored bytes failed validation.
29
30using ScratchPageId = std::uint64_t;
32 std::numeric_limits<ScratchPageId>::max();
33
35 const std::byte* data = nullptr;
36 std::size_t size = 0;
37};
38
40 std::byte* data = nullptr;
41 std::size_t size = 0;
42};
43
44// The creator must select and disclose the physical medium. There is no
45// default TMPDIR, memfd, tmpfs, mmap, or fallback to another medium here.
46enum class ScratchBackingKind : std::uint32_t {
51};
52
53enum class ScratchDurability : std::uint32_t {
54 buffered, // complete positional writes; no power-loss guarantee
55 data, // data and the metadata needed to retrieve it (fdatasync-like)
56 full, // data and all backing metadata (fsync-like)
57};
58
59enum class ScratchIoOperation : std::uint32_t { read_page, write_page, sync };
60
61// One charge per attempted public operation, including errors, journal I/O,
62// rollback I/O and memory-backed I/O. bytes is REQUESTED bytes, not a claim
63// about physical device traffic. sync uses scratch_no_page and zero bytes.
71
73 void* context = nullptr;
74 void (*charge)(void*, const ScratchIoCharge&) noexcept = nullptr;
75};
76
77// Exclusive, borrowed, positional backing for one store at a time. It owns
78// neither the store nor observer context. Pages are exactly page_size bytes;
79// ids are [0,scratch_no_page). Reads at/after page_count return not_found.
80// Successful writes extend page_count to max(old,id+1), zero-filling holes.
81// A failed write MAY have partially modified its target: transactions must
82// protect previously committed pages. No operation throws or allocates an
83// O(page_count) userspace directory. Production backing must extend/spill;
84// backing I/O faults, including exhaustion, are io_error. An arena backing
85// can additionally carry its original typed exception for an owner to rethrow;
86// the status itself is never a capacity code. programming_error is reserved
87// for caller contract violations.
89public:
90 virtual ~ScratchBacking() noexcept = default;
92 ScratchBacking& operator=(const ScratchBacking&) = delete;
94 ScratchBacking& operator=(ScratchBacking&&) = delete;
95
96 ScratchBackingKind kind() const noexcept { return kind_; }
97 std::size_t page_size() const noexcept { return page_size_; }
98 virtual std::uint64_t page_count() const noexcept = 0;
99 // Construction-time arena region bound, including a zero-byte share.
100 // No value for disk/test backings; this policy is not logical store state.
101 virtual std::optional<std::uint64_t> arena_capacity_bytes() const noexcept {
102 return std::nullopt;
103 }
104 // Optional original exception for the last backing operation. Borrowed
105 // until the next operation/destruction; inspect before cleanup I/O.
106 // Disk/test backings retain their existing status-only behavior.
107 virtual const ReportScratchIoError* carried_failure() const noexcept { return nullptr; }
108
109 ScratchStatus read_page(ScratchPageId id, ScratchBuffer destination) const noexcept {
110 const auto status = valid_page(id, destination.data, destination.size)
112 charge(ScratchIoOperation::read_page, id, destination.size, status);
113 return status;
114 }
115
117 const auto status = valid_page(id, source.data, source.size)
119 charge(ScratchIoOperation::write_page, id, source.size, status);
120 return status;
121 }
122
123 ScratchStatus sync(ScratchDurability durability) noexcept {
124 const bool valid = durability == ScratchDurability::buffered
125 || durability == ScratchDurability::data || durability == ScratchDurability::full;
126 const auto status = valid ? do_sync(durability) : ScratchStatus::programming_error;
127 charge(ScratchIoOperation::sync, scratch_no_page, 0, status);
128 return status;
129 }
130
131protected:
133 ScratchIoObserver observer = {}) noexcept
134 : kind_(kind), page_size_(page_size), observer_(observer) {}
135
139
140private:
141 bool valid_page(ScratchPageId id, const void* bytes, std::size_t size) const noexcept {
142 return id != scratch_no_page && page_size_ != 0 && bytes && size == page_size_;
143 }
144 void charge(ScratchIoOperation operation, ScratchPageId id, std::size_t bytes,
145 ScratchStatus status) const noexcept {
146 if (observer_.charge)
147 observer_.charge(observer_.context,
148 {kind_, operation, id, static_cast<std::uint64_t>(bytes), status});
149 }
150
151 const ScratchBackingKind kind_;
152 const std::size_t page_size_;
153 const ScratchIoObserver observer_;
154};
155
156// Test fixture only: fixed caller-owned bytes, no allocator, file, mapping or
157// capacity growth. A test must provision enough BACKING pages for its entire
158// workload, separately from the store's tiny resident cache. Exhausting this
159// fixture is a programming error in the fixture, not a store refusal policy.
160// Reopen a store against the SAME live backing object (extent is retained).
161// data/full sync are unsupported: memory is not falsely advertised durable.
163public:
165 ScratchIoObserver observer = {}) noexcept
167 storage_(storage) {}
168
169 ScratchStatus status() const noexcept {
170 return page_size() && storage_.data && storage_.size
171 && storage_.size % page_size() == 0
173 }
174 std::uint64_t capacity_pages() const noexcept {
175 return page_size() ? storage_.size / page_size() : 0;
176 }
177 std::uint64_t page_count() const noexcept override { return extent_; }
178
179protected:
180 ScratchStatus do_read_page(ScratchPageId id, ScratchBuffer destination) const noexcept override {
182 if (id >= extent_) return ScratchStatus::not_found;
183 const auto offset = static_cast<std::size_t>(id) * page_size();
184 std::memmove(destination.data, storage_.data + offset, page_size());
185 return ScratchStatus::ok;
186 }
188 if (status() != ScratchStatus::ok || id >= capacity_pages())
190 const auto offset = static_cast<std::size_t>(id) * page_size();
191 if (id > extent_) {
192 const auto old_end = static_cast<std::size_t>(extent_) * page_size();
193 std::memset(storage_.data + old_end, 0, offset - old_end);
194 }
195 std::memmove(storage_.data + offset, source.data, page_size());
196 if (id >= extent_) extent_ = id + 1;
197 return ScratchStatus::ok;
198 }
203
204private:
205 const ScratchBuffer storage_;
206 std::uint64_t extent_ = 0;
207};
208
209} // namespace pineforge::detail
ScratchStatus do_sync(ScratchDurability durability) noexcept override
ScratchStatus do_write_page(ScratchPageId id, ScratchBytes source) noexcept override
std::uint64_t capacity_pages() const noexcept
std::uint64_t page_count() const noexcept override
ScratchStatus do_read_page(ScratchPageId id, ScratchBuffer destination) const noexcept override
FixedMemoryScratchBacking(ScratchBuffer storage, std::size_t page_size, ScratchIoObserver observer={}) noexcept
virtual std::uint64_t page_count() const noexcept=0
ScratchBackingKind kind() const noexcept
std::size_t page_size() const noexcept
virtual ScratchStatus do_read_page(ScratchPageId, ScratchBuffer) const noexcept=0
ScratchBacking(const ScratchBacking &)=delete
virtual const ReportScratchIoError * carried_failure() const noexcept
virtual ScratchStatus do_write_page(ScratchPageId, ScratchBytes) noexcept=0
ScratchStatus read_page(ScratchPageId id, ScratchBuffer destination) const noexcept
virtual ScratchStatus do_sync(ScratchDurability) noexcept=0
ScratchBacking(ScratchBackingKind kind, std::size_t page_size, ScratchIoObserver observer={}) noexcept
virtual std::optional< std::uint64_t > arena_capacity_bytes() const noexcept
virtual ~ScratchBacking() noexcept=default
ScratchStatus sync(ScratchDurability durability) noexcept
ScratchStatus write_page(ScratchPageId id, ScratchBytes source) noexcept
constexpr ScratchPageId scratch_no_page
std::uint64_t ScratchPageId
void(* charge)(void *, const ScratchIoCharge &) noexcept