PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
scratch_store_impl.hpp
Go to the documentation of this file.
1#pragma once
2
3// CR150-CX L4 / CR-221 scope B. A header-only definition of CommandScratchStore, kept
4// apart from the contract header so it can be replaced without touching the
5// declarations. Included by scratch_store.hpp; never include it first.
6//
7// Design in one paragraph: fixed-size pages with a 16-byte reserved trailer; a
8// copy-in copy-out page cache (no pointer into the cache survives a call); an
9// in-place undo journal whose images are written through before the first
10// change of a page in a transaction (a stamp in each page header says whether
11// that already happened); two superblocks written alternately at commit; an
12// ordered B+tree of handles compared through the caller's comparator; a radix
13// table from handle to blob locations; blobs as chains of 64-byte cells or of
14// whole pages; free lists for pages and cells. All growth is in the backing.
17
18#include <cstddef>
19#include <cstdint>
20#include <cstring>
21#include <new>
22#include <optional>
23
24namespace pineforge::detail {
25namespace scratch_impl {
26
27using Byte = unsigned char;
28
29inline std::uint64_t ld64(const Byte* p) noexcept {
30 std::uint64_t value = 0;
31 for (int i = 7; i >= 0; --i) value = (value << 8) | p[i];
32 return value;
33}
34inline void st64(Byte* p, std::uint64_t value) noexcept {
35 for (int i = 0; i < 8; ++i) p[i] = static_cast<Byte>((value >> (8 * i)) & 0xFF);
36}
37inline std::uint32_t ld32(const Byte* p) noexcept {
38 return static_cast<std::uint32_t>(p[0]) | (static_cast<std::uint32_t>(p[1]) << 8)
39 | (static_cast<std::uint32_t>(p[2]) << 16) | (static_cast<std::uint32_t>(p[3]) << 24);
40}
41inline void st32(Byte* p, std::uint32_t value) noexcept {
42 for (int i = 0; i < 4; ++i) p[i] = static_cast<Byte>((value >> (8 * i)) & 0xFF);
43}
44
45struct Fold {
46 std::uint64_t value = 14695981039346656037ull;
47 void bytes(const Byte* data, std::size_t length) noexcept {
48 for (std::size_t i = 0; i < length; ++i) {
49 value ^= data[i];
50 value *= 1099511628211ull;
51 }
52 }
53 void le64(std::uint64_t v) noexcept {
54 Byte raw[8];
55 st64(raw, v);
56 bytes(raw, 8);
57 }
58};
59
60constexpr std::uint64_t kMagic = 0x3142535243534650ull;
61constexpr std::uint64_t kVersion = 1;
62constexpr std::uint32_t kTypeIndexLeaf = 2;
63constexpr std::uint32_t kTypeIndexNode = 3;
64constexpr std::uint32_t kTypeHandleLeaf = 4;
65constexpr std::uint32_t kTypeHandleNode = 5;
66constexpr std::uint32_t kTypeCells = 6;
67constexpr std::uint32_t kTypeBlob = 7;
68constexpr std::uint32_t kTypeFree = 8;
69constexpr std::size_t kHeader = 16;
70constexpr std::size_t kTrailer = 16;
71constexpr std::size_t kCell = 64;
72constexpr std::size_t kCellPayload = 56;
73constexpr std::size_t kWorkPages = 6;
74constexpr std::uint64_t kLive = 1;
75constexpr std::uint64_t kGhost = 2;
76constexpr unsigned kMaxDepth = 48;
77// Handle-table height: 60 slots per node of the smallest page reach 2^62 handles in 11 levels.
78constexpr std::size_t kMaxHeight = 12;
79constexpr std::size_t kSuperFields = 18;
80constexpr std::uint64_t kNoSlot = UINT64_MAX;
81
82class Core {
83public:
85
86 struct Super {
87 std::uint64_t seq = 0;
88 std::uint64_t next_handle = 1;
89 std::uint64_t record_count = 0;
90 std::uint64_t hw = 2;
91 std::uint64_t free_head = 0;
92 std::uint64_t hroot = 0;
93 std::uint64_t hheight = 0;
94 std::uint64_t iroot = 0;
95 std::uint64_t iheight = 0;
96 std::uint64_t cell_free = 0;
97 std::uint64_t cell_bump = 0;
98 std::uint64_t cell_next = 0;
99 std::uint64_t pool_head = 0;
100 };
101
102 struct Entry {
103 std::uint64_t key_loc = 0;
104 std::uint64_t val_loc = 0;
105 std::uint64_t key_len = 0;
106 std::uint64_t val_word = 0; // (value length << 2) | kLive or kGhost; zero is dead
107 };
108
109 struct Slot {
110 std::uint64_t id;
111 std::uint64_t tick;
112 std::int32_t next;
113 bool valid;
114 bool dirty;
115 };
116
117 struct BlobPos {
118 std::uint64_t index = 0;
119 std::uint64_t elem = 0;
120 };
121
122 struct Path {
123 std::uint64_t page[kMaxDepth];
124 std::uint32_t slot[kMaxDepth];
125 unsigned depth = 0;
126 };
127
128 // A stored key or value as a byte source. It resolves logical bytes through
129 // the cache on every read and keeps only a chain cursor.
130 class BlobSource final : public ScratchByteSource {
131 public:
132 BlobSource(Core* core, std::uint64_t loc, std::uint64_t len) noexcept
133 : core_(core), loc_(loc), len_(len) {}
134 std::uint64_t size() const noexcept override { return len_; }
135 ScratchStatus read(std::uint64_t offset, ScratchBuffer destination) const noexcept override {
136 if (offset > len_ || destination.size > len_ - offset
137 || (destination.size != 0 && destination.data == nullptr))
139 return core_->blob_read(loc_, offset, destination.size, reinterpret_cast<Byte*>(destination.data), pos_);
140 }
141
142 private:
143 Core* core_;
144 std::uint64_t loc_;
145 std::uint64_t len_;
146 mutable BlobPos pos_{};
147 };
148
149 // ---- geometry -----------------------------------------------------------
150
151 static bool geometry_ok(const ScratchStoreConfig& c, const ScratchBacking* b) noexcept {
152 return b != nullptr && c.page_count >= scratch_min_page_count && c.page_count <= (std::size_t{1} << 24)
153 && c.page_size >= scratch_min_page_size && c.page_size <= (std::size_t{1} << 20)
154 && (c.page_size & (c.page_size - 1)) == 0 && c.journal_slots >= 1 && c.index_slots >= 1
155 && c.order.id != 0 && c.order.compare != nullptr && c.page_size == b->page_size();
156 }
157 static std::size_t round_up(std::size_t n, std::size_t a) noexcept { return (n + a - 1) / a * a; }
158 static std::size_t bucket_count(std::size_t slots) noexcept {
159 std::size_t n = 4;
160 while (n < slots * 2) n <<= 1;
161 return n;
162 }
163 static std::size_t required_bytes(const ScratchStoreConfig& c, const ScratchBacking* b) noexcept {
164 std::size_t total = round_up(sizeof(Core), 16);
165 if (!geometry_ok(c, b)) return total;
166 total += round_up(c.page_count * sizeof(Slot), 16);
167 total += round_up(bucket_count(c.page_count) * sizeof(std::int32_t), 64);
168 total += (c.page_count + kWorkPages) * c.page_size;
169 return total;
170 }
171
172 Core(const ScratchStoreConfig& c, ScratchBacking* b, std::size_t total) noexcept : total_(total) {
173 if (!geometry_ok(c, b)) {
174 open_status_ = Status::programming_error;
175 return;
176 }
177 backing_ = b;
178 order_ = c.order;
179 page_ = c.page_size;
181 slots_ = c.page_count;
183 leaf_cap_ = (usable_ - 32) / 8;
184 node_cap_ = (usable_ - 24) / 16;
185 hleaf_cap_ = (usable_ - 16) / 32;
186 hnode_cap_ = (usable_ - 16) / 8;
187 cells_per_page_ = (usable_ - 16) / kCell;
188 blob_payload_ = usable_ - 24;
189 cell_max_ = page_ / 2;
190 Byte* base = reinterpret_cast<Byte*>(this);
191 std::size_t off = round_up(sizeof(Core), 16);
192 slot_ = reinterpret_cast<Slot*>(base + off);
193 off += round_up(slots_ * sizeof(Slot), 16);
194 bucket_ = reinterpret_cast<std::int32_t*>(base + off);
195 off += round_up(nbuckets_ * sizeof(std::int32_t), 64);
196 cache_ = base + off;
197 off += slots_ * page_;
198 w0_ = base + off;
199 off += page_;
200 w1_ = base + off;
201 off += page_;
202 w2_ = base + off;
203 off += page_;
204 w3_ = base + off;
205 off += page_;
206 jbuf_ = base + off;
207 off += page_;
208 sbuf_ = base + off;
209 std::memset(slot_, 0, slots_ * sizeof(Slot));
210 for (std::size_t i = 0; i < slots_; ++i) slot_[i].next = -1;
211 for (std::size_t i = 0; i < nbuckets_; ++i) bucket_[i] = -1;
212 if (c.mode == ScratchOpenMode::create_empty) {
213 if (b->page_count() != 0) open_status_ = Status::programming_error;
214 } else if (c.mode == ScratchOpenMode::reopen) {
216 } else {
217 open_status_ = Status::programming_error;
218 }
219 }
220
221 // An abandoned transaction is undone; the destructor never commits.
222 void shutdown() noexcept {
223 if (open_status_ == Status::ok && active_) (void)rollback_impl();
224 }
225
226 // ---- backing traffic ----------------------------------------------------
227
228 void note_backing_failure(Status status) noexcept {
229 if (status != Status::io_error) return;
230 backing_io_failed_ = true;
231 // Diagnostic transport, not durable logical state. Successful cleanup
232 // I/O must not erase the exception for the status an owner will raise.
233 // A later status-only I/O failure replaces it; a fresh begin resets it.
234 carried_failure_.reset();
235 if (const auto* failure = backing_->carried_failure()) carried_failure_ = *failure;
236 }
237 Status bread(std::uint64_t id, Byte* destination) noexcept {
238 ++reads_;
239 const Status st = backing_->read_page(id, ScratchBuffer{reinterpret_cast<std::byte*>(destination), page_});
241 return st;
242 }
243 Status bwrite(std::uint64_t id, const Byte* source) noexcept {
244 ++writes_;
245 const Status st = backing_->write_page(id, ScratchBytes{reinterpret_cast<const std::byte*>(source), page_});
247 return st;
248 }
249 Status bsync(ScratchDurability durability) noexcept {
250 ++syncs_;
251 const Status st = backing_->sync(durability);
253 return st;
254 }
255 static Status structural(Status status) noexcept {
256 return status == Status::not_found ? Status::corrupt : status;
257 }
258
259 // ---- the page cache: copy in, copy out ---------------------------------
260
261 Byte* page_at(std::int32_t s) noexcept { return cache_ + static_cast<std::size_t>(s) * page_; }
262 std::size_t bucket_of(std::uint64_t id) const noexcept {
263 return static_cast<std::size_t>(((id + 1) * 0x9E3779B97F4A7C15ull) >> 40) & (nbuckets_ - 1);
264 }
265 std::int32_t find(std::uint64_t id) noexcept {
266 std::int32_t s = bucket_[bucket_of(id)];
267 while (s >= 0) {
268 if (slot_[s].valid && slot_[s].id == id) return s;
269 s = slot_[s].next;
270 }
271 return -1;
272 }
273 void unlink(std::int32_t s) noexcept {
274 std::int32_t* link = &bucket_[bucket_of(slot_[s].id)];
275 while (*link >= 0) {
276 if (*link == s) {
277 *link = slot_[s].next;
278 return;
279 }
280 link = &slot_[*link].next;
281 }
282 }
283 void link_in(std::int32_t s) noexcept {
284 std::int32_t* head = &bucket_[bucket_of(slot_[s].id)];
285 slot_[s].next = *head;
286 *head = s;
287 }
288 // Free a slot for reuse: an invalid one first, else the least recently
289 // used, whose dirty bytes go to the backing before it is dropped.
290 Status victim(std::int32_t& out) noexcept {
291 std::int32_t best = -1;
292 for (std::size_t i = 0; i < slots_; ++i) {
293 const std::int32_t s = static_cast<std::int32_t>(i);
294 if (!slot_[s].valid) {
295 best = s;
296 break;
297 }
298 if (best < 0 || slot_[s].tick < slot_[best].tick) best = s;
299 }
300 if (slot_[best].valid) {
301 if (slot_[best].dirty) {
302 const Status st = bwrite(slot_[best].id, page_at(best));
303 if (st != Status::ok) return st;
304 slot_[best].dirty = false;
305 }
306 unlink(best);
307 slot_[best].valid = false;
308 }
309 out = best;
310 return Status::ok;
311 }
312 Status fetch(std::uint64_t id, bool load, std::int32_t& out) noexcept {
313 std::int32_t s = find(id);
314 if (s >= 0) {
315 slot_[s].tick = ++tick_;
316 out = s;
317 return Status::ok;
318 }
319 Status st = victim(s);
320 if (st != Status::ok) return st;
321 slot_[s].id = id;
322 slot_[s].valid = false;
323 slot_[s].dirty = false;
324 if (load) {
325 st = bread(id, page_at(s));
326 if (st != Status::ok) return structural(st);
327 }
328 slot_[s].valid = true;
329 slot_[s].tick = ++tick_;
330 link_in(s);
331 out = s;
332 return Status::ok;
333 }
334 Status flush() noexcept {
335 for (std::size_t i = 0; i < slots_; ++i) {
336 const std::int32_t s = static_cast<std::int32_t>(i);
337 if (!slot_[s].valid || !slot_[s].dirty) continue;
338 const Status st = bwrite(slot_[s].id, page_at(s));
339 if (st != Status::ok) return st;
340 slot_[s].dirty = false;
341 }
342 return Status::ok;
343 }
344 void invalidate_all() noexcept {
345 for (std::size_t i = 0; i < slots_; ++i) {
346 slot_[i].valid = false;
347 slot_[i].dirty = false;
348 slot_[i].next = -1;
349 }
350 for (std::size_t i = 0; i < nbuckets_; ++i) bucket_[i] = -1;
351 }
352
353 Status rd(std::uint64_t id, std::size_t off, std::size_t len, Byte* destination) noexcept {
354 std::int32_t s = -1;
355 const Status st = fetch(id, true, s);
356 if (st != Status::ok) return st;
357 std::memcpy(destination, page_at(s) + off, len);
358 return Status::ok;
359 }
360
361 // Before the first change of an existing page in a transaction, write its
362 // image through to a journal page and stamp the page. Pages at or above the
363 // high-water mark of the transaction start are new and need no image.
364 Status touch(std::uint64_t id) noexcept {
365 if (!active_) return Status::programming_error;
366 if (id >= hw0_) return Status::ok;
367 std::int32_t s = -1;
368 Status st = fetch(id, true, s);
369 if (st != Status::ok) return st;
370 if (ld64(page_at(s) + 8) == txn_) return Status::ok;
371 std::memcpy(jbuf_, page_at(s), usable_);
372 // A pool page leaves the pool before it is overwritten: if that write is
373 // refused or torn the page's link may be damaged, so it must not stay in
374 // the chain. A refused journal write therefore forfeits that one page.
375 std::uint64_t image;
376 if (cur_.pool_head != 0) {
377 image = cur_.pool_head;
378 st = bread(image, sbuf_);
379 if (st != Status::ok) return structural(st);
380 cur_.pool_head = ld64(sbuf_ + usable_ + 8);
381 } else {
382 image = cur_.hw++;
383 }
384 st64(jbuf_ + usable_, id);
386 st = bwrite(image, jbuf_);
387 if (st != Status::ok) return st;
388 used_head_ = image;
389 if (used_tail_ == 0) used_tail_ = image;
390 ++used_count_;
391 st = fetch(id, true, s);
392 if (st != Status::ok) return st;
393 st64(page_at(s) + 8, txn_);
394 slot_[s].dirty = true;
395 return Status::ok;
396 }
397 Status wr(std::uint64_t id, std::size_t off, std::size_t len, const Byte* source) noexcept {
398 Status st = touch(id);
399 if (st != Status::ok) return st;
400 std::int32_t s = -1;
401 st = fetch(id, true, s);
402 if (st != Status::ok) return st;
403 std::memcpy(page_at(s) + off, source, len);
404 slot_[s].dirty = true;
405 return Status::ok;
406 }
407 Status wr64(std::uint64_t id, std::size_t off, std::uint64_t value) noexcept {
408 Byte raw[8];
409 st64(raw, value);
410 return wr(id, off, 8, raw);
411 }
412 Status wr32(std::uint64_t id, std::size_t off, std::uint32_t value) noexcept {
413 Byte raw[4];
414 st32(raw, value);
415 return wr(id, off, 4, raw);
416 }
417 // Replace the whole usable area of a page from a work buffer.
418 Status wr_page(std::uint64_t id, Byte* buffer) noexcept {
419 st64(buffer + 8, txn_);
420 std::memset(buffer + usable_, 0, kTrailer);
421 Status st = touch(id);
422 if (st != Status::ok) return st;
423 std::int32_t s = -1;
424 st = fetch(id, false, s);
425 if (st != Status::ok) return st;
426 std::memcpy(page_at(s), buffer, page_);
427 slot_[s].dirty = true;
428 return Status::ok;
429 }
430
431 // ---- page allocation ------------------------------------------------------
432
433 Status alloc_page(std::uint64_t& id) noexcept {
434 if (cur_.free_head != 0) {
435 id = cur_.free_head;
436 Byte raw[8];
437 const Status st = rd(id, 16, 8, raw);
438 if (st != Status::ok) return st;
439 cur_.free_head = ld64(raw);
440 return Status::ok;
441 }
442 id = cur_.hw++;
443 return Status::ok;
444 }
445 Status free_page(std::uint64_t id) noexcept {
446 Status st = wr32(id, 0, kTypeFree);
447 if (st != Status::ok) return st;
448 st = wr64(id, 16, cur_.free_head);
449 if (st != Status::ok) return st;
450 cur_.free_head = id;
451 return Status::ok;
452 }
453
454 // ---- blobs: chains of cells for small bytes, chains of pages for large ----
455
456 std::uint64_t cell_page(std::uint64_t cell) const noexcept { return cell >> 16; }
457 std::size_t cell_off(std::uint64_t cell) const noexcept {
458 return kHeader + static_cast<std::size_t>(cell & 0xFFFF) * kCell;
459 }
460 Status cell_alloc(std::uint64_t& cell) noexcept {
461 if (cur_.cell_free != 0) {
462 cell = cur_.cell_free;
463 Byte raw[8];
464 const Status st = rd(cell_page(cell), cell_off(cell), 8, raw);
465 if (st != Status::ok) return st;
466 cur_.cell_free = ld64(raw);
467 return Status::ok;
468 }
469 if (cur_.cell_bump == 0 || cur_.cell_next >= cells_per_page_) {
470 std::uint64_t page = 0;
471 Status st = alloc_page(page);
472 if (st != Status::ok) return st;
473 std::memset(w3_, 0, page_);
475 st = wr_page(page, w3_);
476 if (st != Status::ok) return st;
477 cur_.cell_bump = page;
478 cur_.cell_next = 0;
479 }
480 cell = (cur_.cell_bump << 16) | cur_.cell_next;
481 ++cur_.cell_next;
482 return Status::ok;
483 }
484
485 Status read_source(const ScratchByteSource& source, std::uint64_t offset, Byte* destination,
486 std::size_t length) noexcept {
487 ++busy_;
488 const Status st = source.read(offset, ScratchBuffer{reinterpret_cast<std::byte*>(destination), length});
489 --busy_;
490 return st;
491 }
492
493 Status blob_create(const ScratchByteSource& source, std::uint64_t length, std::uint64_t& loc) noexcept {
494 loc = 0;
495 if (length == 0) return Status::ok;
496 Status st;
497 if (length <= cell_max_) {
498 std::uint64_t cell = 0;
499 st = cell_alloc(cell);
500 if (st != Status::ok) return st;
501 loc = cell << 1;
502 std::uint64_t done = 0;
503 for (;;) {
504 const std::size_t n = static_cast<std::size_t>(length - done < kCellPayload ? length - done : kCellPayload);
505 std::uint64_t next = 0;
506 if (done + n < length) {
507 st = cell_alloc(next);
508 if (st != Status::ok) return st;
509 }
510 Byte raw[kCell];
511 st64(raw, next);
512 st = read_source(source, done, raw + 8, n);
513 if (st != Status::ok) return st;
514 st = wr(cell_page(cell), cell_off(cell), 8 + n, raw);
515 if (st != Status::ok) return st;
516 done += n;
517 if (done == length) break;
518 cell = next;
519 }
520 return Status::ok;
521 }
522 std::uint64_t page = 0;
523 st = alloc_page(page);
524 if (st != Status::ok) return st;
525 loc = (page << 1) | 1;
526 std::uint64_t done = 0;
527 for (;;) {
528 const std::size_t n = static_cast<std::size_t>(length - done < blob_payload_ ? length - done : blob_payload_);
529 std::uint64_t next = 0;
530 if (done + n < length) {
531 st = alloc_page(next);
532 if (st != Status::ok) return st;
533 }
534 std::memset(w3_, 0, page_);
536 st64(w3_ + 16, next);
537 st = read_source(source, done, w3_ + 24, n);
538 if (st != Status::ok) return st;
539 st = wr_page(page, w3_);
540 if (st != Status::ok) return st;
541 done += n;
542 if (done == length) break;
543 page = next;
544 }
545 return Status::ok;
546 }
547
548 Status blob_read(std::uint64_t loc, std::uint64_t offset, std::size_t length, Byte* destination,
549 BlobPos& pos) noexcept {
550 if (length == 0) return Status::ok;
551 if (loc == 0) return Status::corrupt;
552 const bool paged = (loc & 1) != 0;
553 const std::size_t span = paged ? blob_payload_ : kCellPayload;
554 const std::uint64_t want = offset / span;
555 if (pos.elem == 0 || pos.index > want) {
556 pos.index = 0;
557 pos.elem = loc >> 1;
558 }
559 Byte raw[8];
560 while (pos.index < want) {
561 const Status st = paged ? rd(pos.elem, 16, 8, raw) : rd(cell_page(pos.elem), cell_off(pos.elem), 8, raw);
562 if (st != Status::ok) return st;
563 pos.elem = ld64(raw);
564 ++pos.index;
565 if (pos.elem == 0) return Status::corrupt;
566 }
567 std::size_t inner = static_cast<std::size_t>(offset % span);
568 while (length > 0) {
569 const std::size_t n = length < span - inner ? length : span - inner;
570 const Status st = paged ? rd(pos.elem, 24 + inner, n, destination)
571 : rd(cell_page(pos.elem), cell_off(pos.elem) + 8 + inner, n, destination);
572 if (st != Status::ok) return st;
573 destination += n;
574 length -= n;
575 inner = 0;
576 if (length > 0) {
577 const Status hop = paged ? rd(pos.elem, 16, 8, raw)
578 : rd(cell_page(pos.elem), cell_off(pos.elem), 8, raw);
579 if (hop != Status::ok) return hop;
580 pos.elem = ld64(raw);
581 ++pos.index;
582 if (pos.elem == 0) return Status::corrupt;
583 }
584 }
585 return Status::ok;
586 }
587
588 Status blob_free(std::uint64_t loc) noexcept {
589 if (loc == 0) return Status::ok;
590 Byte raw[8];
591 if ((loc & 1) != 0) {
592 std::uint64_t page = loc >> 1;
593 while (page != 0) {
594 Status st = rd(page, 16, 8, raw);
595 if (st != Status::ok) return st;
596 const std::uint64_t next = ld64(raw);
597 st = free_page(page);
598 if (st != Status::ok) return st;
599 page = next;
600 }
601 return Status::ok;
602 }
603 std::uint64_t cell = loc >> 1;
604 while (cell != 0) {
605 Status st = rd(cell_page(cell), cell_off(cell), 8, raw);
606 if (st != Status::ok) return st;
607 const std::uint64_t next = ld64(raw);
608 st = wr64(cell_page(cell), cell_off(cell), cur_.cell_free);
609 if (st != Status::ok) return st;
610 cur_.cell_free = cell;
611 cell = next;
612 }
613 return Status::ok;
614 }
615
616 // ---- the handle table: a radix tree of 32-byte entries ------------------
617
618 static std::uint64_t mul_sat(std::uint64_t a, std::uint64_t b) noexcept {
619 if (a != 0 && b > UINT64_MAX / a) return UINT64_MAX;
620 return a * b;
621 }
622 // Leaves a node of the given level (1 = a leaf) covers.
623 std::uint64_t span_of(std::uint64_t level) const noexcept {
624 std::uint64_t span = 1;
625 for (std::uint64_t i = 1; i < level; ++i) span = mul_sat(span, hnode_cap_);
626 return span;
627 }
628
629 // The interior nodes from the root down to the parent of a leaf, and the
630 // child slot taken in each. Offset 4 of an interior node counts its
631 // nonzero child slots, as offset 4 of a leaf counts its present entries.
632 struct HPath {
633 std::uint64_t node[kMaxHeight];
634 std::size_t slot[kMaxHeight];
635 std::size_t depth;
636 };
637
638 Status hfind_leaf(std::uint64_t leaf_no, bool create, std::uint64_t& leaf, HPath& path) noexcept {
639 leaf = 0;
640 path.depth = 0;
641 Status st;
642 if (cur_.hroot == 0) {
643 if (!create) return Status::ok;
644 std::uint64_t page = 0;
645 st = alloc_page(page);
646 if (st != Status::ok) return st;
647 std::memset(w3_, 0, page_);
649 st = wr_page(page, w3_);
650 if (st != Status::ok) return st;
651 cur_.hroot = page;
652 cur_.hheight = 1;
653 }
654 while (leaf_no >= span_of(cur_.hheight)) {
655 if (!create) return Status::ok;
656 std::uint64_t page = 0;
657 st = alloc_page(page);
658 if (st != Status::ok) return st;
659 std::memset(w3_, 0, page_);
661 st32(w3_ + 4, 1);
662 st64(w3_ + 16, cur_.hroot);
663 st = wr_page(page, w3_);
664 if (st != Status::ok) return st;
665 cur_.hroot = page;
666 ++cur_.hheight;
667 }
668 std::uint64_t node = cur_.hroot;
669 std::uint64_t rest = leaf_no;
670 for (std::uint64_t level = cur_.hheight; level > 1; --level) {
671 if (path.depth >= kMaxHeight) return Status::corrupt;
672 const std::uint64_t span = span_of(level - 1);
673 const std::size_t ci = static_cast<std::size_t>(rest / span);
674 rest -= static_cast<std::uint64_t>(ci) * span;
675 Byte raw[8];
676 st = rd(node, 16 + 8 * ci, 8, raw);
677 if (st != Status::ok) return st;
678 std::uint64_t child = ld64(raw);
679 if (child == 0) {
680 if (!create) return Status::ok;
681 st = alloc_page(child);
682 if (st != Status::ok) return st;
683 std::memset(w3_, 0, page_);
684 st32(w3_, level == 2 ? kTypeHandleLeaf : kTypeHandleNode);
685 st = wr_page(child, w3_);
686 if (st != Status::ok) return st;
687 st = wr64(node, 16 + 8 * ci, child);
688 if (st != Status::ok) return st;
689 Byte count_raw[4];
690 st = rd(node, 4, 4, count_raw);
691 if (st != Status::ok) return st;
692 st = wr32(node, 4, ld32(count_raw) + 1);
693 if (st != Status::ok) return st;
694 }
695 path.node[path.depth] = node;
696 path.slot[path.depth] = ci;
697 ++path.depth;
698 node = child;
699 }
700 leaf = node;
701 return Status::ok;
702 }
703
704 Status hget(std::uint64_t handle, Entry& entry, bool& present) noexcept {
705 present = false;
706 if (handle == 0 || handle >= cur_.next_handle) return Status::ok;
707 const std::uint64_t index = handle - 1;
708 std::uint64_t leaf = 0;
709 HPath path;
710 const Status st = hfind_leaf(index / hleaf_cap_, false, leaf, path);
711 if (st != Status::ok || leaf == 0) return st;
712 Byte raw[32];
713 const Status rs = rd(leaf, kHeader + static_cast<std::size_t>(index % hleaf_cap_) * 32, 32, raw);
714 if (rs != Status::ok) return rs;
715 entry.key_loc = ld64(raw);
716 entry.val_loc = ld64(raw + 8);
717 entry.key_len = ld64(raw + 16);
718 entry.val_word = ld64(raw + 24);
719 present = (entry.val_word & 3) != 0;
720 return Status::ok;
721 }
722
723 Status hset(std::uint64_t handle, const Entry& entry) noexcept {
724 const std::uint64_t index = handle - 1;
725 std::uint64_t leaf = 0;
726 HPath path;
727 Status st = hfind_leaf(index / hleaf_cap_, true, leaf, path);
728 if (st != Status::ok) return st;
729 const std::size_t off = kHeader + static_cast<std::size_t>(index % hleaf_cap_) * 32;
730 Byte raw[32];
731 st = rd(leaf, off, 32, raw);
732 if (st != Status::ok) return st;
733 const bool was = (ld64(raw + 24) & 3) != 0;
734 const bool now = (entry.val_word & 3) != 0;
735 st64(raw, entry.key_loc);
736 st64(raw + 8, entry.val_loc);
737 st64(raw + 16, entry.key_len);
738 st64(raw + 24, entry.val_word);
739 st = wr(leaf, off, 32, raw);
740 if (st != Status::ok) return st;
741 if (was == now) return Status::ok;
742 Byte count_raw[4];
743 st = rd(leaf, 4, 4, count_raw);
744 if (st != Status::ok) return st;
745 std::uint32_t count = ld32(count_raw);
746 if (now) ++count;
747 else --count;
748 st = wr32(leaf, 4, count);
749 if (st != Status::ok) return st;
750 if (count != 0 || path.depth == 0) return Status::ok;
751 // The leaf is empty and is not the root: release it, and each interior
752 // node that this leaves without children, up to but not including the
753 // root. Handle numbers are never reissued, so nothing would reuse them.
754 st = free_page(leaf);
755 if (st != Status::ok) return st;
756 for (std::size_t k = path.depth; k-- > 0;) {
757 st = wr64(path.node[k], 16 + 8 * path.slot[k], 0);
758 if (st != Status::ok) return st;
759 st = rd(path.node[k], 4, 4, count_raw);
760 if (st != Status::ok) return st;
761 const std::uint32_t children = ld32(count_raw) - 1;
762 st = wr32(path.node[k], 4, children);
763 if (st != Status::ok) return st;
764 if (children != 0 || k == 0) break;
765 st = free_page(path.node[k]);
766 if (st != Status::ok) return st;
767 }
768 return Status::ok;
769 }
770
771 // ---- comparisons through the caller's comparator -------------------------
772
773 Status compare_with(const ScratchByteSource& probe, std::uint64_t handle, int& order) noexcept {
774 Entry entry;
775 bool present = false;
776 Status st = hget(handle, entry, present);
777 if (st != Status::ok) return st;
778 if (!present) return Status::corrupt;
779 BlobSource stored(this, entry.key_loc, entry.key_len);
780 ++busy_;
781 st = order_.compare(order_.context, probe, stored, order);
782 --busy_;
783 return st;
784 }
785
786 // ---- the ordered index: a B+tree of handles -------------------------------
787
788 Status index_count(std::uint64_t page, std::uint32_t& count) noexcept {
789 Byte raw[4];
790 const Status st = rd(page, 4, 4, raw);
791 if (st != Status::ok) return st;
792 count = ld32(raw);
793 return Status::ok;
794 }
795
796 // Find the leaf position of the first key not below the probe, and whether
797 // that entry equals the probe. An empty tree answers leaf 0.
798 Status descend(const ScratchByteSource& probe, Path& path, bool& equal, std::uint32_t& pos,
799 std::uint64_t& leaf) noexcept {
800 path.depth = 0;
801 equal = false;
802 pos = 0;
803 leaf = 0;
804 if (cur_.iroot == 0) return Status::ok;
805 std::uint64_t page = cur_.iroot;
806 for (std::uint64_t level = cur_.iheight; level > 1; --level) {
807 if (path.depth >= kMaxDepth) return Status::corrupt;
808 std::uint32_t count = 0;
809 Status st = index_count(page, count);
810 if (st != Status::ok) return st;
811 std::uint32_t lo = 0, hi = count;
812 while (lo < hi) {
813 const std::uint32_t mid = lo + (hi - lo) / 2;
814 Byte raw[8];
815 st = rd(page, 24 + 16 * static_cast<std::size_t>(mid), 8, raw);
816 if (st != Status::ok) return st;
817 int order = 0;
818 st = compare_with(probe, ld64(raw), order);
819 if (st != Status::ok) return st;
820 if (order >= 0) lo = mid + 1;
821 else hi = mid;
822 }
823 Byte child_raw[8];
824 st = rd(page, 16 + 16 * static_cast<std::size_t>(lo), 8, child_raw);
825 if (st != Status::ok) return st;
826 path.page[path.depth] = page;
827 path.slot[path.depth] = lo;
828 ++path.depth;
829 page = ld64(child_raw);
830 if (page == 0) return Status::corrupt;
831 }
832 std::uint32_t count = 0;
833 Status st = index_count(page, count);
834 if (st != Status::ok) return st;
835 std::uint32_t lo = 0, hi = count;
836 while (lo < hi) {
837 const std::uint32_t mid = lo + (hi - lo) / 2;
838 Byte raw[8];
839 st = rd(page, 32 + 8 * static_cast<std::size_t>(mid), 8, raw);
840 if (st != Status::ok) return st;
841 int order = 0;
842 st = compare_with(probe, ld64(raw), order);
843 if (st != Status::ok) return st;
844 if (order > 0) lo = mid + 1;
845 else hi = mid;
846 }
847 pos = lo;
848 leaf = page;
849 if (lo < count) {
850 Byte raw[8];
851 st = rd(page, 32 + 8 * static_cast<std::size_t>(lo), 8, raw);
852 if (st != Status::ok) return st;
853 int order = 0;
854 st = compare_with(probe, ld64(raw), order);
855 if (st != Status::ok) return st;
856 equal = order == 0;
857 }
858 return Status::ok;
859 }
860
861 Status leaf_entry(std::uint64_t leaf, std::uint32_t pos, std::uint64_t& handle) noexcept {
862 Byte raw[8];
863 const Status st = rd(leaf, 32 + 8 * static_cast<std::size_t>(pos), 8, raw);
864 if (st != Status::ok) return st;
865 handle = ld64(raw);
866 return Status::ok;
867 }
868
869 Status leftmost_leaf(std::uint64_t& leaf) noexcept {
870 leaf = 0;
871 if (cur_.iroot == 0) return Status::ok;
872 std::uint64_t page = cur_.iroot;
873 for (std::uint64_t level = cur_.iheight; level > 1; --level) {
874 Byte raw[8];
875 const Status st = rd(page, 16, 8, raw);
876 if (st != Status::ok) return st;
877 page = ld64(raw);
878 if (page == 0) return Status::corrupt;
879 }
880 leaf = page;
881 return Status::ok;
882 }
883
884 Status new_root(std::uint64_t separator, std::uint64_t right) noexcept {
885 std::uint64_t page = 0;
886 Status st = alloc_page(page);
887 if (st != Status::ok) return st;
888 std::memset(w3_, 0, page_);
890 st32(w3_ + 4, 1);
891 st64(w3_ + 16, cur_.iroot);
892 st64(w3_ + 24, separator);
893 st64(w3_ + 32, right);
894 st = wr_page(page, w3_);
895 if (st != Status::ok) return st;
896 cur_.iroot = page;
897 ++cur_.iheight;
898 return Status::ok;
899 }
900
901 // Insert a separator and its right sibling above a node that split.
902 Status insert_up(const Path& path, std::uint64_t separator, std::uint64_t right) noexcept {
903 for (int level = static_cast<int>(path.depth) - 1;; --level) {
904 if (level < 0) return new_root(separator, right);
905 const std::uint64_t parent = path.page[level];
906 const std::uint32_t c = path.slot[level];
907 Status st = rd_full(parent, w0_);
908 if (st != Status::ok) return st;
909 const std::uint32_t count = ld32(w0_ + 4);
910 if (count < node_cap_) {
911 Byte* pairs = w0_ + 24;
912 std::memmove(pairs + 16 * static_cast<std::size_t>(c + 1), pairs + 16 * static_cast<std::size_t>(c),
913 16 * static_cast<std::size_t>(count - c));
914 st64(pairs + 16 * static_cast<std::size_t>(c), separator);
915 st64(pairs + 16 * static_cast<std::size_t>(c) + 8, right);
916 st32(w0_ + 4, count + 1);
917 return wr_page(parent, w0_);
918 }
919 const std::uint32_t total = count + 1;
920 const std::uint32_t mid = total / 2;
921 // pair(i) of the conceptual node with the new pair inserted at c
922 auto pair_sep = [&](std::uint32_t i) -> std::uint64_t {
923 return i < c ? ld64(w0_ + 24 + 16 * static_cast<std::size_t>(i))
924 : (i == c ? separator : ld64(w0_ + 24 + 16 * static_cast<std::size_t>(i - 1)));
925 };
926 auto pair_child = [&](std::uint32_t i) -> std::uint64_t {
927 return i < c ? ld64(w0_ + 32 + 16 * static_cast<std::size_t>(i))
928 : (i == c ? right : ld64(w0_ + 32 + 16 * static_cast<std::size_t>(i - 1)));
929 };
930 std::memset(w1_, 0, page_);
932 st32(w1_ + 4, total - mid - 1);
933 st64(w1_ + 16, pair_child(mid));
934 for (std::uint32_t j = 0; j + mid + 1 < total; ++j) {
935 st64(w1_ + 24 + 16 * static_cast<std::size_t>(j), pair_sep(mid + 1 + j));
936 st64(w1_ + 32 + 16 * static_cast<std::size_t>(j), pair_child(mid + 1 + j));
937 }
938 std::memset(w2_, 0, page_);
940 st32(w2_ + 4, mid);
941 st64(w2_ + 16, ld64(w0_ + 16));
942 for (std::uint32_t j = 0; j < mid; ++j) {
943 st64(w2_ + 24 + 16 * static_cast<std::size_t>(j), pair_sep(j));
944 st64(w2_ + 32 + 16 * static_cast<std::size_t>(j), pair_child(j));
945 }
946 const std::uint64_t promoted = pair_sep(mid);
947 std::uint64_t new_page = 0;
948 st = alloc_page(new_page);
949 if (st != Status::ok) return st;
950 st = wr_page(new_page, w1_);
951 if (st != Status::ok) return st;
952 st = wr_page(parent, w2_);
953 if (st != Status::ok) return st;
954 separator = promoted;
955 right = new_page;
956 }
957 }
958
959 Status rd_full(std::uint64_t page, Byte* buffer) noexcept { return rd(page, 0, usable_, buffer); }
960
961 Status tree_insert(const Path& path, std::uint64_t leaf, std::uint32_t pos, std::uint64_t handle) noexcept {
962 Status st;
963 if (leaf == 0) {
964 std::uint64_t page = 0;
965 st = alloc_page(page);
966 if (st != Status::ok) return st;
967 std::memset(w0_, 0, page_);
969 st32(w0_ + 4, 1);
970 st64(w0_ + 32, handle);
971 st = wr_page(page, w0_);
972 if (st != Status::ok) return st;
973 cur_.iroot = page;
974 cur_.iheight = 1;
975 return Status::ok;
976 }
977 st = rd_full(leaf, w0_);
978 if (st != Status::ok) return st;
979 const std::uint32_t count = ld32(w0_ + 4);
980 if (count < leaf_cap_) {
981 Byte* entries = w0_ + 32;
982 std::memmove(entries + 8 * static_cast<std::size_t>(pos + 1), entries + 8 * static_cast<std::size_t>(pos),
983 8 * static_cast<std::size_t>(count - pos));
984 st64(entries + 8 * static_cast<std::size_t>(pos), handle);
985 st32(w0_ + 4, count + 1);
986 return wr_page(leaf, w0_);
987 }
988 const std::uint32_t total = count + 1;
989 const std::uint32_t mid = (total + 1) / 2;
990 auto at = [&](std::uint32_t i) -> std::uint64_t {
991 return i < pos ? ld64(w0_ + 32 + 8 * static_cast<std::size_t>(i))
992 : (i == pos ? handle : ld64(w0_ + 32 + 8 * static_cast<std::size_t>(i - 1)));
993 };
994 std::uint64_t right_page = 0;
995 st = alloc_page(right_page);
996 if (st != Status::ok) return st;
997 const std::uint64_t old_next = ld64(w0_ + 24);
998 const std::uint64_t old_prev = ld64(w0_ + 16);
999 std::memset(w1_, 0, page_);
1001 st32(w1_ + 4, total - mid);
1002 st64(w1_ + 16, leaf);
1003 st64(w1_ + 24, old_next);
1004 for (std::uint32_t i = mid; i < total; ++i) st64(w1_ + 32 + 8 * static_cast<std::size_t>(i - mid), at(i));
1005 std::memset(w2_, 0, page_);
1007 st32(w2_ + 4, mid);
1008 st64(w2_ + 16, old_prev);
1009 st64(w2_ + 24, right_page);
1010 for (std::uint32_t i = 0; i < mid; ++i) st64(w2_ + 32 + 8 * static_cast<std::size_t>(i), at(i));
1011 const std::uint64_t separator = at(mid);
1012 st = wr_page(right_page, w1_);
1013 if (st != Status::ok) return st;
1014 st = wr_page(leaf, w2_);
1015 if (st != Status::ok) return st;
1016 if (old_next != 0) {
1017 st = wr64(old_next, 16, right_page);
1018 if (st != Status::ok) return st;
1019 }
1020 return insert_up(path, separator, right_page);
1021 }
1022
1023 // A ghost separator keeps its key bytes only while some index node names
1024 // it; release it when that name goes away.
1025 Status release_separator(std::uint64_t handle) noexcept {
1026 Entry entry;
1027 bool present = false;
1028 Status st = hget(handle, entry, present);
1029 if (st != Status::ok) return st;
1030 if (!present || (entry.val_word & 3) != kGhost) return Status::ok;
1031 st = blob_free(entry.key_loc);
1032 if (st != Status::ok) return st;
1033 return hset(handle, Entry{});
1034 }
1035
1036 Status remove_child(const Path& path) noexcept {
1037 int level = static_cast<int>(path.depth) - 1;
1038 Status st;
1039 for (;; --level) {
1040 if (level < 0) {
1041 cur_.iroot = 0;
1042 cur_.iheight = 0;
1043 return Status::ok;
1044 }
1045 const std::uint64_t parent = path.page[level];
1046 const std::uint32_t c = path.slot[level];
1047 st = rd_full(parent, w0_);
1048 if (st != Status::ok) return st;
1049 const std::uint32_t count = ld32(w0_ + 4);
1050 if (count == 0) {
1051 st = free_page(parent);
1052 if (st != Status::ok) return st;
1053 continue;
1054 }
1055 Byte* pairs = w0_ + 24;
1056 std::uint64_t separator;
1057 if (c >= 1) {
1058 separator = ld64(pairs + 16 * static_cast<std::size_t>(c - 1));
1059 std::memmove(pairs + 16 * static_cast<std::size_t>(c - 1), pairs + 16 * static_cast<std::size_t>(c),
1060 16 * static_cast<std::size_t>(count - c));
1061 } else {
1062 separator = ld64(pairs);
1063 st64(w0_ + 16, ld64(pairs + 8));
1064 std::memmove(pairs, pairs + 16, 16 * static_cast<std::size_t>(count - 1));
1065 }
1066 st32(w0_ + 4, count - 1);
1067 st = wr_page(parent, w0_);
1068 if (st != Status::ok) return st;
1069 st = release_separator(separator);
1070 if (st != Status::ok) return st;
1071 break;
1072 }
1073 while (cur_.iheight > 1) {
1074 std::uint32_t count = 0;
1075 st = index_count(cur_.iroot, count);
1076 if (st != Status::ok) return st;
1077 if (count != 0) break;
1078 Byte raw[8];
1079 st = rd(cur_.iroot, 16, 8, raw);
1080 if (st != Status::ok) return st;
1081 const std::uint64_t old_root = cur_.iroot;
1082 cur_.iroot = ld64(raw);
1083 --cur_.iheight;
1084 st = free_page(old_root);
1085 if (st != Status::ok) return st;
1086 }
1087 return Status::ok;
1088 }
1089
1090 Status tree_erase(const Path& path, std::uint64_t leaf, std::uint32_t pos, std::uint64_t handle) noexcept {
1091 Status st = rd_full(leaf, w0_);
1092 if (st != Status::ok) return st;
1093 const std::uint32_t count = ld32(w0_ + 4);
1094 if (pos >= count || ld64(w0_ + 32 + 8 * static_cast<std::size_t>(pos)) != handle) return Status::corrupt;
1095 Byte* entries = w0_ + 32;
1096 std::memmove(entries + 8 * static_cast<std::size_t>(pos), entries + 8 * static_cast<std::size_t>(pos + 1),
1097 8 * static_cast<std::size_t>(count - pos - 1));
1098 st32(w0_ + 4, count - 1);
1099 if (count > 1) return wr_page(leaf, w0_);
1100 const std::uint64_t prev = ld64(w0_ + 16);
1101 const std::uint64_t next = ld64(w0_ + 24);
1102 if (prev != 0) {
1103 st = wr64(prev, 24, next);
1104 if (st != Status::ok) return st;
1105 }
1106 if (next != 0) {
1107 st = wr64(next, 16, prev);
1108 if (st != Status::ok) return st;
1109 }
1110 st = free_page(leaf);
1111 if (st != Status::ok) return st;
1112 return remove_child(path);
1113 }
1114
1115 // ---- the superblock -----------------------------------------------------
1116
1117 void encode_super(const Super& s, Byte* buffer) const noexcept {
1118 std::memset(buffer, 0, page_);
1119 const std::uint64_t fields[kSuperFields] = {kMagic, kVersion, page_, order_.id, s.seq, s.next_handle,
1120 s.record_count, s.hw, s.free_head, s.hroot, s.hheight, s.iroot,
1121 s.iheight, s.cell_free, s.cell_bump, s.cell_next, s.pool_head, 0};
1122 Fold fold;
1123 for (std::size_t i = 0; i < kSuperFields; ++i) {
1124 st64(buffer + 8 * i, fields[i]);
1125 fold.le64(fields[i]);
1126 }
1127 st64(buffer + 8 * kSuperFields, fold.value);
1128 }
1129 bool decode_super(const Byte* buffer, Super& s, std::uint64_t& order_id) const noexcept {
1130 std::uint64_t fields[kSuperFields];
1131 Fold fold;
1132 for (std::size_t i = 0; i < kSuperFields; ++i) {
1133 fields[i] = ld64(buffer + 8 * i);
1134 fold.le64(fields[i]);
1135 }
1136 if (fields[0] != kMagic || fields[1] != kVersion || fields[2] != page_) return false;
1137 if (ld64(buffer + 8 * kSuperFields) != fold.value) return false;
1138 order_id = fields[3];
1139 s.seq = fields[4];
1140 s.next_handle = fields[5];
1141 s.record_count = fields[6];
1142 s.hw = fields[7];
1143 s.free_head = fields[8];
1144 s.hroot = fields[9];
1145 s.hheight = fields[10];
1146 s.iroot = fields[11];
1147 s.iheight = fields[12];
1148 s.cell_free = fields[13];
1149 s.cell_bump = fields[14];
1150 s.cell_next = fields[15];
1151 s.pool_head = fields[16];
1152 return true;
1153 }
1154
1155 Status reopen_load() noexcept {
1156 if (backing_->page_count() == 0) return Status::not_found;
1157 Super best;
1158 std::uint64_t best_order = 0;
1159 bool have = false;
1160 for (std::uint64_t slot = 0; slot < 2; ++slot) {
1161 const Status st = bread(slot, sbuf_);
1162 if (st == Status::not_found) continue;
1163 if (st != Status::ok) return st;
1164 Super candidate;
1165 std::uint64_t order_id = 0;
1166 if (!decode_super(sbuf_, candidate, order_id)) continue;
1167 if (!have || candidate.seq > best.seq) {
1168 best = candidate;
1169 best_order = order_id;
1170 have = true;
1171 }
1172 }
1173 if (!have) return Status::corrupt;
1174 if (best_order != order_.id) return Status::programming_error;
1175 if (best.hw < 2) return Status::corrupt;
1176 cur_ = best;
1177 return Status::ok;
1178 }
1179
1180 // ---- transactions -----------------------------------------------------------
1181
1182 Status begin() noexcept {
1183 if (open_status_ != Status::ok || active_ || busy_ != 0) return Status::programming_error;
1184 carried_failure_.reset();
1185 snap_ = cur_;
1186 txn_ = cur_.seq + 1;
1187 hw0_ = cur_.hw;
1188 active_ = true;
1189 poisoned_ = false;
1190 used_head_ = 0;
1191 used_tail_ = 0;
1192 used_count_ = 0;
1193 return Status::ok;
1194 }
1195
1196 Status commit_impl(ScratchDurability durability) noexcept {
1197 std::uint64_t new_pool = cur_.pool_head;
1198 Status st;
1199 if (used_count_ != 0) {
1200 st = bread(used_tail_, sbuf_);
1201 if (st != Status::ok) return structural(st);
1202 st64(sbuf_ + usable_ + 8, cur_.pool_head);
1203 st = bwrite(used_tail_, sbuf_);
1204 if (st != Status::ok) return st;
1205 new_pool = used_head_;
1206 }
1207 st = flush();
1208 if (st != Status::ok) return st;
1209 if (durability != ScratchDurability::buffered) {
1210 st = bsync(durability);
1211 if (st != Status::ok) return st;
1212 }
1213 Super next = cur_;
1214 next.seq = cur_.seq + 1;
1215 next.pool_head = new_pool;
1217 // A refused write may still have landed this whole page (it is far
1218 // smaller than a page), and a refused final sync leaves it published,
1219 // so the slot stays marked until rollback blanks it.
1220 tainted_slot_ = next.seq & 1;
1221 st = bwrite(tainted_slot_, sbuf_);
1222 if (st != Status::ok) return st;
1223 if (durability != ScratchDurability::buffered) {
1224 st = bsync(durability);
1225 if (st != Status::ok) return st;
1226 }
1228 cur_ = next;
1229 active_ = false;
1230 used_head_ = 0;
1231 used_tail_ = 0;
1232 used_count_ = 0;
1233 return Status::ok;
1234 }
1235
1236 Status commit(ScratchDurability durability) noexcept {
1237 if (open_status_ != Status::ok || !active_ || poisoned_ || busy_ != 0) return Status::programming_error;
1238 if (durability != ScratchDurability::buffered && durability != ScratchDurability::data
1239 && durability != ScratchDurability::full)
1240 return Status::programming_error;
1241 const Status st = commit_impl(durability);
1242 if (st != Status::ok) poisoned_ = true;
1243 return st;
1244 }
1245
1246 // Every journal page is spent the moment its target is restored, so a
1247 // rollback that failed and is tried again resumes at the first page not yet
1248 // restored. A journal page that came from the pool goes back to it; one that
1249 // extended the backing is dropped with the high-water mark.
1252 while (used_count_ != 0) {
1253 const std::uint64_t image = used_head_;
1254 Status st = bread(image, jbuf_);
1255 if (st != Status::ok) return structural(st);
1256 const std::uint64_t target = ld64(jbuf_ + usable_);
1257 const std::uint64_t next = ld64(jbuf_ + usable_ + 8);
1258 if (target < 2 || target >= hw0_) return Status::corrupt;
1259 std::memcpy(w3_, jbuf_, usable_);
1260 std::memset(w3_ + usable_, 0, kTrailer);
1261 st = bwrite(target, w3_);
1262 if (st != Status::ok) return st;
1263 used_head_ = next;
1264 --used_count_;
1265 if (image < hw0_) {
1266 st64(jbuf_ + usable_, 0);
1267 st64(jbuf_ + usable_ + 8, cur_.pool_head);
1268 st = bwrite(image, jbuf_);
1269 if (st != Status::ok) return st;
1270 cur_.pool_head = image;
1271 }
1272 }
1273 if (tainted_slot_ != kNoSlot) {
1274 std::memset(w3_, 0, page_);
1275 const Status st = bwrite(tainted_slot_, w3_);
1276 if (st != Status::ok) return st;
1278 }
1279 const std::uint64_t pool = cur_.pool_head;
1280 cur_ = snap_;
1281 cur_.pool_head = pool;
1282 active_ = false;
1283 poisoned_ = false;
1284 used_head_ = 0;
1285 used_tail_ = 0;
1286 return Status::ok;
1287 }
1288
1289 // A rollback that fails leaves the transaction active and rollback-only.
1290 Status rollback() noexcept {
1291 if (open_status_ != Status::ok || !active_ || busy_ != 0) return Status::programming_error;
1292 const Status st = rollback_impl();
1293 if (st != Status::ok) poisoned_ = true;
1294 return st;
1295 }
1296
1297 // ---- mutations ----------------------------------------------------------------
1298
1299 Status put_impl(const ScratchByteSource& key, const ScratchByteSource& value, std::uint64_t& result) noexcept {
1300 const std::uint64_t key_len = key.size();
1301 const std::uint64_t val_len = value.size();
1302 if (val_len >= (std::uint64_t{1} << 61) || key_len >= (std::uint64_t{1} << 61)) return Status::programming_error;
1303 Path path;
1304 bool equal = false;
1305 std::uint32_t pos = 0;
1306 std::uint64_t leaf = 0;
1307 Status st = descend(key, path, equal, pos, leaf);
1308 if (st != Status::ok) return st;
1309 Entry entry;
1310 entry.key_len = key_len;
1311 entry.val_word = (val_len << 2) | kLive;
1312 if (equal) {
1313 std::uint64_t handle = 0;
1314 st = leaf_entry(leaf, pos, handle);
1315 if (st != Status::ok) return st;
1316 Entry old;
1317 bool present = false;
1318 st = hget(handle, old, present);
1319 if (st != Status::ok) return st;
1320 if (!present) return Status::corrupt;
1321 st = blob_create(key, key_len, entry.key_loc);
1322 if (st != Status::ok) return st;
1323 st = blob_create(value, val_len, entry.val_loc);
1324 if (st != Status::ok) return st;
1325 st = hset(handle, entry);
1326 if (st != Status::ok) return st;
1327 st = blob_free(old.key_loc);
1328 if (st != Status::ok) return st;
1329 st = blob_free(old.val_loc);
1330 if (st != Status::ok) return st;
1331 result = handle;
1332 return Status::ok;
1333 }
1334 if (cur_.next_handle >= (std::uint64_t{1} << 62)) return Status::programming_error;
1335 st = blob_create(key, key_len, entry.key_loc);
1336 if (st != Status::ok) return st;
1337 st = blob_create(value, val_len, entry.val_loc);
1338 if (st != Status::ok) return st;
1339 const std::uint64_t handle = cur_.next_handle++;
1340 st = hset(handle, entry);
1341 if (st != Status::ok) return st;
1342 st = tree_insert(path, leaf, pos, handle);
1343 if (st != Status::ok) return st;
1344 ++cur_.record_count;
1345 result = handle;
1346 return Status::ok;
1347 }
1348
1350 if (open_status_ != Status::ok || !active_ || poisoned_ || busy_ != 0) return Status::programming_error;
1351 backing_io_failed_ = false;
1352 std::uint64_t handle = 0;
1353 const Status st = put_impl(key, value, handle);
1354 if (st != Status::ok) {
1355 poisoned_ = true;
1356 return backing_io_failed_ ? Status::io_error : Status::programming_error;
1357 }
1358 result = handle;
1359 return Status::ok;
1360 }
1361
1362 Status erase_impl(std::uint64_t handle, const Entry& entry) noexcept {
1363 BlobSource key(this, entry.key_loc, entry.key_len);
1364 Path path;
1365 bool equal = false;
1366 std::uint32_t pos = 0;
1367 std::uint64_t leaf = 0;
1368 Status st = descend(key, path, equal, pos, leaf);
1369 if (st != Status::ok) return st;
1370 if (!equal || leaf == 0) return Status::corrupt;
1371 std::uint64_t found = 0;
1372 st = leaf_entry(leaf, pos, found);
1373 if (st != Status::ok) return st;
1374 if (found != handle) return Status::corrupt;
1375 bool named = false;
1376 for (unsigned level = 0; level < path.depth; ++level) {
1377 const std::uint32_t c = path.slot[level];
1378 if (c == 0) continue;
1379 Byte raw[8];
1380 st = rd(path.page[level], 24 + 16 * static_cast<std::size_t>(c - 1), 8, raw);
1381 if (st != Status::ok) return st;
1382 if (ld64(raw) == handle) named = true;
1383 }
1384 if (named) {
1385 st = blob_free(entry.val_loc);
1386 if (st != Status::ok) return st;
1387 Entry ghost;
1388 ghost.key_loc = entry.key_loc;
1389 ghost.key_len = entry.key_len;
1390 ghost.val_word = kGhost;
1391 st = hset(handle, ghost);
1392 } else {
1393 st = blob_free(entry.key_loc);
1394 if (st != Status::ok) return st;
1395 st = blob_free(entry.val_loc);
1396 if (st != Status::ok) return st;
1397 st = hset(handle, Entry{});
1398 }
1399 if (st != Status::ok) return st;
1400 st = tree_erase(path, leaf, pos, handle);
1401 if (st != Status::ok) return st;
1402 --cur_.record_count;
1403 return Status::ok;
1404 }
1405
1406 Status erase(ScratchHandle handle) noexcept {
1407 if (open_status_ != Status::ok || !active_ || poisoned_ || busy_ != 0) return Status::programming_error;
1408 backing_io_failed_ = false;
1409 Entry entry;
1410 bool present = false;
1411 Status st = hget(handle, entry, present);
1412 if (st != Status::ok) {
1413 poisoned_ = true;
1414 return backing_io_failed_ ? Status::io_error : Status::programming_error;
1415 }
1416 if (!present || (entry.val_word & 3) != kLive) return Status::not_found;
1417 st = erase_impl(handle, entry);
1418 if (st != Status::ok) {
1419 poisoned_ = true;
1420 return backing_io_failed_ ? Status::io_error : Status::programming_error;
1421 }
1422 return Status::ok;
1423 }
1424
1425 // ---- reads ------------------------------------------------------------------------
1426
1427 bool readable() const noexcept { return open_status_ == Status::ok && !poisoned_; }
1428 bool page_ok(const ScratchBuffer& page) const noexcept { return page.data != nullptr && page.size == page_; }
1429
1430 Status live_entry(std::uint64_t handle, Entry& entry) noexcept {
1431 bool present = false;
1432 const Status st = hget(handle, entry, present);
1433 if (st != Status::ok) return st;
1434 if (!present || (entry.val_word & 3) != kLive) return Status::not_found;
1435 return Status::ok;
1436 }
1437
1439 if (!readable()) return Status::programming_error;
1440 Entry entry;
1441 const Status st = live_entry(handle, entry);
1442 if (st != Status::ok) return st;
1443 info.handle = handle;
1444 info.key_bytes = entry.key_len;
1445 info.value_bytes = entry.val_word >> 2;
1446 return Status::ok;
1447 }
1448
1449 Status read(ScratchHandle handle, ScratchField field, std::uint64_t offset, ScratchBuffer destination) noexcept {
1450 if (!readable()) return Status::programming_error;
1451 Entry entry;
1452 Status st = live_entry(handle, entry);
1453 if (st != Status::ok) return st;
1454 const bool is_key = field == ScratchField::key;
1455 if (!is_key && field != ScratchField::value) return Status::programming_error;
1456 const std::uint64_t length = is_key ? entry.key_len : entry.val_word >> 2;
1457 if (offset > length || destination.size > length - offset
1458 || (destination.size != 0 && destination.data == nullptr))
1459 return Status::programming_error;
1460 BlobSource source(this, is_key ? entry.key_loc : entry.val_loc, length);
1461 return source.read(offset, destination);
1462 }
1463
1464 Status visit(ScratchHandle handle, ScratchBuffer page, ScratchChunkVisitor visitor, void* context) noexcept {
1465 if (!readable() || !page_ok(page) || visitor == nullptr) return Status::programming_error;
1466 Entry entry;
1467 const Status st = live_entry(handle, entry);
1468 if (st != Status::ok) return st;
1469 ScratchRecordInfo info;
1470 info.handle = handle;
1471 info.key_bytes = entry.key_len;
1472 info.value_bytes = entry.val_word >> 2;
1473 for (int f = 0; f < 2; ++f) {
1474 const ScratchField field = f == 0 ? ScratchField::key : ScratchField::value;
1475 const std::uint64_t length = f == 0 ? entry.key_len : entry.val_word >> 2;
1476 const std::uint64_t loc = f == 0 ? entry.key_loc : entry.val_loc;
1477 if (length == 0) {
1478 ScratchChunkView view{info, field, 0, ScratchBytes{page.data, 0}, true};
1479 ++busy_;
1480 const bool go = visitor(context, view);
1481 --busy_;
1482 if (!go) return Status::cancelled;
1483 continue;
1484 }
1485 BlobPos pos;
1486 std::uint64_t done = 0;
1487 while (done < length) {
1488 const std::size_t n = static_cast<std::size_t>(length - done < page_ ? length - done : page_);
1489 const Status rs = blob_read(loc, done, n, reinterpret_cast<Byte*>(page.data), pos);
1490 if (rs != Status::ok) return rs;
1491 ScratchChunkView view{info, field, done, ScratchBytes{page.data, n}, done + n == length};
1492 ++busy_;
1493 const bool go = visitor(context, view);
1494 --busy_;
1495 if (!go) return Status::cancelled;
1496 done += n;
1497 }
1498 }
1499 return Status::ok;
1500 }
1501
1502 Status for_each(ScratchBuffer page, ScratchRecordVisitor visitor, void* context) noexcept {
1503 if (!readable() || !page_ok(page) || visitor == nullptr) return Status::programming_error;
1504 std::uint64_t leaf = 0;
1505 Status st = leftmost_leaf(leaf);
1506 if (st != Status::ok) return st;
1507 while (leaf != 0) {
1508 std::uint32_t count = 0;
1509 st = index_count(leaf, count);
1510 if (st != Status::ok) return st;
1511 for (std::uint32_t i = 0; i < count; ++i) {
1512 std::uint64_t handle = 0;
1513 st = leaf_entry(leaf, i, handle);
1514 if (st != Status::ok) return st;
1515 Entry entry;
1516 st = live_entry(handle, entry);
1517 if (st != Status::ok) return structural(st == Status::not_found ? Status::corrupt : st);
1518 BlobSource key(this, entry.key_loc, entry.key_len);
1519 BlobSource value(this, entry.val_loc, entry.val_word >> 2);
1520 ScratchRecordInfo info;
1521 info.handle = handle;
1522 info.key_bytes = entry.key_len;
1523 info.value_bytes = entry.val_word >> 2;
1524 ScratchRecordView view{info, key, value};
1525 ++busy_;
1526 const bool go = visitor(context, view);
1527 --busy_;
1528 if (!go) return Status::cancelled;
1529 }
1530 Byte raw[8];
1531 st = rd(leaf, 24, 8, raw);
1532 if (st != Status::ok) return st;
1533 leaf = ld64(raw);
1534 }
1535 return Status::ok;
1536 }
1537
1538 Status first(ScratchHandle& result) noexcept {
1539 if (!readable()) return Status::programming_error;
1540 std::uint64_t leaf = 0;
1541 Status st = leftmost_leaf(leaf);
1542 if (st != Status::ok) return st;
1543 if (leaf == 0) {
1544 result = scratch_no_handle;
1545 return Status::end;
1546 }
1547 std::uint64_t handle = 0;
1548 st = leaf_entry(leaf, 0, handle);
1549 if (st != Status::ok) return st;
1550 result = handle;
1551 return Status::ok;
1552 }
1553
1554 Status next(ScratchHandle current, ScratchHandle& result) noexcept {
1555 if (!readable()) return Status::programming_error;
1556 Entry entry;
1557 Status st = live_entry(current, entry);
1558 if (st != Status::ok) return st;
1559 BlobSource key(this, entry.key_loc, entry.key_len);
1560 Path path;
1561 bool equal = false;
1562 std::uint32_t pos = 0;
1563 std::uint64_t leaf = 0;
1564 st = descend(key, path, equal, pos, leaf);
1565 if (st != Status::ok) return st;
1566 std::uint64_t found = 0;
1567 if (equal && leaf != 0) {
1568 st = leaf_entry(leaf, pos, found);
1569 if (st != Status::ok) return st;
1570 }
1571 if (found != current) return Status::corrupt;
1572 std::uint32_t count = 0;
1573 st = index_count(leaf, count);
1574 if (st != Status::ok) return st;
1575 if (pos + 1 < count) {
1576 std::uint64_t handle = 0;
1577 st = leaf_entry(leaf, pos + 1, handle);
1578 if (st != Status::ok) return st;
1579 result = handle;
1580 return Status::ok;
1581 }
1582 Byte raw[8];
1583 st = rd(leaf, 24, 8, raw);
1584 if (st != Status::ok) return st;
1585 const std::uint64_t next_leaf = ld64(raw);
1586 if (next_leaf == 0) {
1587 result = scratch_no_handle;
1588 return Status::end;
1589 }
1590 std::uint64_t handle = 0;
1591 st = leaf_entry(next_leaf, 0, handle);
1592 if (st != Status::ok) return st;
1593 result = handle;
1594 return Status::ok;
1595 }
1596
1597 Status lower_bound(const ScratchByteSource& probe, ScratchHandle& result) noexcept {
1598 if (!readable()) return Status::programming_error;
1599 Path path;
1600 bool equal = false;
1601 std::uint32_t pos = 0;
1602 std::uint64_t leaf = 0;
1603 Status st = descend(probe, path, equal, pos, leaf);
1604 if (st != Status::ok) return st;
1605 if (leaf == 0) {
1606 result = scratch_no_handle;
1607 return Status::end;
1608 }
1609 std::uint32_t count = 0;
1610 st = index_count(leaf, count);
1611 if (st != Status::ok) return st;
1612 std::uint64_t handle = 0;
1613 if (pos < count) {
1614 st = leaf_entry(leaf, pos, handle);
1615 if (st != Status::ok) return st;
1616 result = handle;
1617 return Status::ok;
1618 }
1619 Byte raw[8];
1620 st = rd(leaf, 24, 8, raw);
1621 if (st != Status::ok) return st;
1622 const std::uint64_t next_leaf = ld64(raw);
1623 if (next_leaf == 0) {
1624 result = scratch_no_handle;
1625 return Status::end;
1626 }
1627 st = leaf_entry(next_leaf, 0, handle);
1628 if (st != Status::ok) return st;
1629 result = handle;
1630 return Status::ok;
1631 }
1632
1633 Status fold_source(Fold& fold, std::uint64_t loc, std::uint64_t length, ScratchBuffer page) noexcept {
1634 BlobPos pos;
1635 std::uint64_t done = 0;
1636 while (done < length) {
1637 const std::size_t n = static_cast<std::size_t>(length - done < page_ ? length - done : page_);
1638 const Status st = blob_read(loc, done, n, reinterpret_cast<Byte*>(page.data), pos);
1639 if (st != Status::ok) return st;
1640 fold.bytes(reinterpret_cast<const Byte*>(page.data), n);
1641 done += n;
1642 }
1643 return Status::ok;
1644 }
1645
1646 Status digest(bool with_state, ScratchBuffer page, std::uint64_t& result) noexcept {
1647 if (!readable() || !page_ok(page)) return Status::programming_error;
1648 if (with_state && active_) return Status::programming_error;
1649 Fold fold;
1650 if (with_state) {
1651 fold.le64(order_.id);
1652 fold.le64(cur_.next_handle);
1653 }
1654 fold.le64(cur_.record_count);
1655 std::uint64_t leaf = 0;
1656 Status st = leftmost_leaf(leaf);
1657 if (st != Status::ok) return st;
1658 while (leaf != 0) {
1659 std::uint32_t count = 0;
1660 st = index_count(leaf, count);
1661 if (st != Status::ok) return st;
1662 for (std::uint32_t i = 0; i < count; ++i) {
1663 std::uint64_t handle = 0;
1664 st = leaf_entry(leaf, i, handle);
1665 if (st != Status::ok) return st;
1666 Entry entry;
1667 st = live_entry(handle, entry);
1668 if (st != Status::ok) return structural(st == Status::not_found ? Status::corrupt : st);
1669 if (with_state) fold.le64(handle);
1670 fold.le64(entry.key_len);
1671 st = fold_source(fold, entry.key_loc, entry.key_len, page);
1672 if (st != Status::ok) return st;
1673 fold.le64(entry.val_word >> 2);
1674 st = fold_source(fold, entry.val_loc, entry.val_word >> 2, page);
1675 if (st != Status::ok) return st;
1676 }
1677 Byte raw[8];
1678 st = rd(leaf, 24, 8, raw);
1679 if (st != Status::ok) return st;
1680 leaf = ld64(raw);
1681 }
1682 result = fold.value;
1683 return Status::ok;
1684 }
1685
1686 ScratchStoreStats stats() const noexcept {
1688 out.resident_bytes = total_;
1689 if (open_status_ != Status::ok && backing_ == nullptr) return out;
1690 out.backing_pages = backing_ != nullptr ? backing_->page_count() : 0;
1691 out.backing_bytes = out.backing_pages * page_;
1692 out.page_reads = reads_;
1693 out.page_writes = writes_;
1694 out.syncs = syncs_;
1695 return out;
1696 }
1697
1698 // ---- state ----------------------------------------------------------------------------
1699
1700 Status open_status_ = Status::ok;
1702 std::optional<ReportScratchIoError> carried_failure_;
1704 std::size_t total_ = 0;
1705 std::size_t page_ = 0;
1706 std::size_t usable_ = 0;
1707 std::size_t slots_ = 0;
1708 std::size_t nbuckets_ = 0;
1709 std::size_t leaf_cap_ = 0;
1710 std::size_t node_cap_ = 0;
1711 std::size_t hleaf_cap_ = 0;
1712 std::size_t hnode_cap_ = 0;
1713 std::size_t cells_per_page_ = 0;
1714 std::size_t blob_payload_ = 0;
1715 std::size_t cell_max_ = 0;
1716 Slot* slot_ = nullptr;
1717 std::int32_t* bucket_ = nullptr;
1718 Byte* cache_ = nullptr;
1719 Byte* w0_ = nullptr;
1720 Byte* w1_ = nullptr;
1721 Byte* w2_ = nullptr;
1722 Byte* w3_ = nullptr;
1723 Byte* jbuf_ = nullptr;
1724 Byte* sbuf_ = nullptr;
1725 std::uint64_t tick_ = 0;
1726 std::uint64_t reads_ = 0;
1727 std::uint64_t writes_ = 0;
1728 std::uint64_t syncs_ = 0;
1731 bool active_ = false;
1732 bool poisoned_ = false;
1733 std::uint64_t txn_ = 0;
1734 std::uint64_t hw0_ = 0;
1735 std::uint64_t used_head_ = 0;
1736 std::uint64_t used_tail_ = 0;
1737 std::uint64_t used_count_ = 0;
1738 std::uint64_t tainted_slot_ = kNoSlot;
1739 std::uint32_t busy_ = 0;
1740
1741private:
1742 bool backing_io_failed_ = false;
1743};
1744
1745} // namespace scratch_impl
1746
1748 State(const ScratchStoreConfig& config, ScratchBacking* backing, std::size_t total) noexcept
1749 : scratch_impl::Core(config, backing, total) {}
1750};
1751
1753 static_assert(sizeof(State) == sizeof(scratch_impl::Core), "the workspace layout assumes no extra members");
1754 const std::size_t total = scratch_impl::Core::required_bytes(config, backing);
1755 void* block = ::operator new(total, std::nothrow);
1756 if (block == nullptr) return;
1757 state_ = new (block) State(config, backing, total);
1758}
1759
1761 if (state_ == nullptr) return;
1762 state_->shutdown();
1763 state_->~State();
1764 ::operator delete(static_cast<void*>(state_));
1765}
1766
1768 return state_ == nullptr ? ScratchStatus::programming_error : state_->open_status_;
1769}
1771 return state_ && state_->carried_failure_ ? &*state_->carried_failure_ : nullptr;
1772}
1773inline std::size_t CommandScratchStore::page_size() const noexcept { return state_ == nullptr ? 0 : state_->page_; }
1774inline std::uint64_t CommandScratchStore::record_count() const noexcept {
1775 return state_ == nullptr || state_->open_status_ != ScratchStatus::ok ? 0 : state_->cur_.record_count;
1776}
1777inline bool CommandScratchStore::transaction_active() const noexcept {
1778 return state_ != nullptr && state_->open_status_ == ScratchStatus::ok && state_->active_;
1779}
1781 return state_ == nullptr ? ScratchStatus::programming_error : state_->begin();
1782}
1784 ScratchHandle& result) noexcept {
1785 return state_ == nullptr ? ScratchStatus::programming_error : state_->put(key, value, result);
1786}
1788 return state_ == nullptr ? ScratchStatus::programming_error : state_->erase(handle);
1789}
1791 return state_ == nullptr ? ScratchStatus::programming_error : state_->commit(durability);
1792}
1794 return state_ == nullptr ? ScratchStatus::programming_error : state_->rollback();
1795}
1797 return state_ == nullptr ? ScratchStatus::programming_error : state_->point(handle, info);
1798}
1799inline ScratchStatus CommandScratchStore::read(ScratchHandle handle, ScratchField field, std::uint64_t offset,
1800 ScratchBuffer destination) const noexcept {
1801 return state_ == nullptr ? ScratchStatus::programming_error : state_->read(handle, field, offset, destination);
1802}
1804 void* context) const noexcept {
1805 return state_ == nullptr ? ScratchStatus::programming_error : state_->visit(handle, page, visitor, context);
1806}
1808 void* context) const noexcept {
1809 return state_ == nullptr ? ScratchStatus::programming_error : state_->for_each(page, visitor, context);
1810}
1812 return state_ == nullptr ? ScratchStatus::programming_error : state_->first(result);
1813}
1815 return state_ == nullptr ? ScratchStatus::programming_error : state_->next(current, result);
1816}
1818 return state_ == nullptr ? ScratchStatus::programming_error : state_->lower_bound(probe, result);
1819}
1820inline ScratchStatus CommandScratchStore::ordered_digest(ScratchBuffer page, std::uint64_t& result) const noexcept {
1821 return state_ == nullptr ? ScratchStatus::programming_error : state_->digest(false, page, result);
1822}
1823inline ScratchStatus CommandScratchStore::state_digest(ScratchBuffer page, std::uint64_t& result) const noexcept {
1824 return state_ == nullptr ? ScratchStatus::programming_error : state_->digest(true, page, result);
1825}
1827 return state_ == nullptr ? ScratchStoreStats{} : state_->stats();
1828}
1829
1830} // namespace pineforge::detail
std::size_t page_size() const noexcept override
ScratchStatus ordered_digest(ScratchBuffer page, std::uint64_t &result) const noexcept
ScratchStatus state_digest(ScratchBuffer page, std::uint64_t &result) const noexcept
CommandScratchStore(ScratchStoreConfig config, ScratchBacking *backing) noexcept
ScratchStoreStats stats() const noexcept
const ReportScratchIoError * carried_failure() const noexcept override
ScratchStatus read(ScratchHandle, ScratchField, std::uint64_t offset, ScratchBuffer destination) const noexcept override
ScratchStatus first(ScratchHandle &result) const noexcept
ScratchStatus for_each(ScratchBuffer page, ScratchRecordVisitor, void *context) const noexcept override
ScratchStatus point(ScratchHandle, ScratchRecordInfo &) const noexcept override
ScratchStatus commit(ScratchDurability durability) noexcept
ScratchStatus next(ScratchHandle current, ScratchHandle &result) const noexcept
ScratchStatus lower_bound(const ScratchByteSource &probe, ScratchHandle &result) const noexcept
ScratchStatus erase(ScratchHandle) noexcept
std::uint64_t record_count() const noexcept override
ScratchStatus put(const ScratchByteSource &key, const ScratchByteSource &value, ScratchHandle &result) noexcept
ScratchStatus visit(ScratchHandle, ScratchBuffer page, ScratchChunkVisitor, void *context) const noexcept override
std::uint64_t size() const noexcept override
ScratchStatus read(std::uint64_t offset, ScratchBuffer destination) const noexcept override
BlobSource(Core *core, std::uint64_t loc, std::uint64_t len) noexcept
Status put_impl(const ScratchByteSource &key, const ScratchByteSource &value, std::uint64_t &result) noexcept
Status hset(std::uint64_t handle, const Entry &entry) noexcept
Status wr64(std::uint64_t id, std::size_t off, std::uint64_t value) noexcept
Status compare_with(const ScratchByteSource &probe, std::uint64_t handle, int &order) noexcept
Byte * page_at(std::int32_t s) noexcept
Status fetch(std::uint64_t id, bool load, std::int32_t &out) noexcept
Status bread(std::uint64_t id, Byte *destination) noexcept
std::int32_t find(std::uint64_t id) noexcept
Status erase(ScratchHandle handle) noexcept
Status leftmost_leaf(std::uint64_t &leaf) noexcept
static std::size_t required_bytes(const ScratchStoreConfig &c, const ScratchBacking *b) noexcept
static std::uint64_t mul_sat(std::uint64_t a, std::uint64_t b) noexcept
Status victim(std::int32_t &out) noexcept
Status lower_bound(const ScratchByteSource &probe, ScratchHandle &result) noexcept
std::size_t bucket_of(std::uint64_t id) const noexcept
Status for_each(ScratchBuffer page, ScratchRecordVisitor visitor, void *context) noexcept
Core(const ScratchStoreConfig &c, ScratchBacking *b, std::size_t total) noexcept
Status fold_source(Fold &fold, std::uint64_t loc, std::uint64_t length, ScratchBuffer page) noexcept
Status next(ScratchHandle current, ScratchHandle &result) noexcept
void link_in(std::int32_t s) noexcept
Status release_separator(std::uint64_t handle) noexcept
std::uint64_t cell_page(std::uint64_t cell) const noexcept
Status insert_up(const Path &path, std::uint64_t separator, std::uint64_t right) noexcept
void encode_super(const Super &s, Byte *buffer) const noexcept
Status tree_insert(const Path &path, std::uint64_t leaf, std::uint32_t pos, std::uint64_t handle) noexcept
Status rd_full(std::uint64_t page, Byte *buffer) noexcept
Status alloc_page(std::uint64_t &id) noexcept
static Status structural(Status status) noexcept
Status put(const ScratchByteSource &key, const ScratchByteSource &value, ScratchHandle &result) noexcept
Status cell_alloc(std::uint64_t &cell) noexcept
Status rd(std::uint64_t id, std::size_t off, std::size_t len, Byte *destination) noexcept
Status tree_erase(const Path &path, std::uint64_t leaf, std::uint32_t pos, std::uint64_t handle) noexcept
Status commit(ScratchDurability durability) noexcept
Status blob_read(std::uint64_t loc, std::uint64_t offset, std::size_t length, Byte *destination, BlobPos &pos) noexcept
Status wr32(std::uint64_t id, std::size_t off, std::uint32_t value) noexcept
Status wr(std::uint64_t id, std::size_t off, std::size_t len, const Byte *source) noexcept
Status remove_child(const Path &path) noexcept
Status erase_impl(std::uint64_t handle, const Entry &entry) noexcept
Status index_count(std::uint64_t page, std::uint32_t &count) noexcept
Status read_source(const ScratchByteSource &source, std::uint64_t offset, Byte *destination, std::size_t length) noexcept
Status blob_free(std::uint64_t loc) noexcept
static std::size_t round_up(std::size_t n, std::size_t a) noexcept
Status live_entry(std::uint64_t handle, Entry &entry) noexcept
ScratchStoreStats stats() const noexcept
Status visit(ScratchHandle handle, ScratchBuffer page, ScratchChunkVisitor visitor, void *context) noexcept
Status hget(std::uint64_t handle, Entry &entry, bool &present) noexcept
Status blob_create(const ScratchByteSource &source, std::uint64_t length, std::uint64_t &loc) noexcept
void note_backing_failure(Status status) noexcept
bool decode_super(const Byte *buffer, Super &s, std::uint64_t &order_id) const noexcept
Status first(ScratchHandle &result) noexcept
Status point(ScratchHandle handle, ScratchRecordInfo &info) noexcept
Status wr_page(std::uint64_t id, Byte *buffer) noexcept
Status touch(std::uint64_t id) noexcept
Status read(ScratchHandle handle, ScratchField field, std::uint64_t offset, ScratchBuffer destination) noexcept
std::size_t cell_off(std::uint64_t cell) const noexcept
std::uint64_t span_of(std::uint64_t level) const noexcept
std::optional< ReportScratchIoError > carried_failure_
Status new_root(std::uint64_t separator, std::uint64_t right) noexcept
Status free_page(std::uint64_t id) noexcept
Status commit_impl(ScratchDurability durability) noexcept
static bool geometry_ok(const ScratchStoreConfig &c, const ScratchBacking *b) noexcept
Status bwrite(std::uint64_t id, const Byte *source) noexcept
static std::size_t bucket_count(std::size_t slots) noexcept
bool page_ok(const ScratchBuffer &page) const noexcept
Status bsync(ScratchDurability durability) noexcept
Status hfind_leaf(std::uint64_t leaf_no, bool create, std::uint64_t &leaf, HPath &path) noexcept
Status digest(bool with_state, ScratchBuffer page, std::uint64_t &result) noexcept
Status descend(const ScratchByteSource &probe, Path &path, bool &equal, std::uint32_t &pos, std::uint64_t &leaf) noexcept
Status leaf_entry(std::uint64_t leaf, std::uint32_t pos, std::uint64_t &handle) noexcept
constexpr std::uint32_t kTypeIndexLeaf
std::uint64_t ld64(const Byte *p) noexcept
void st32(Byte *p, std::uint32_t value) noexcept
void st64(Byte *p, std::uint64_t value) noexcept
constexpr std::uint32_t kTypeHandleLeaf
constexpr std::uint32_t kTypeHandleNode
std::uint32_t ld32(const Byte *p) noexcept
constexpr std::uint32_t kTypeIndexNode
constexpr std::size_t scratch_min_page_count
bool(*)(void *, const ScratchChunkView &) noexcept ScratchChunkVisitor
std::uint64_t ScratchHandle
constexpr ScratchHandle scratch_no_handle
constexpr std::size_t scratch_min_page_size
bool(*)(void *, const ScratchRecordView &) noexcept ScratchRecordVisitor
State(const ScratchStoreConfig &config, ScratchBacking *backing, std::size_t total) noexcept
void bytes(const Byte *data, std::size_t length) noexcept
void le64(std::uint64_t v) noexcept