The std::pmr adapter: placement, not failability (L0 substrate)¶
tr::mem::source_resource_t is a std::pmr::memory_resource holding one block_source_t*:
do_allocate forwards to try_alloc, do_deallocate forwards to the seam’s sized
release — std::pmr carries the original size and alignment into deallocate, which is
exactly the pair a header-free pool needs — and do_is_equal is address identity
(ADR-0079).
Warning
This delivers placement and bounding, not failability. std::pmr’s only exhaustion signal is
a throw, so the adapter’s boundary is a std::bad_alloc on a hosted build and a std::abort()
under -fno-exceptions — byte-for-byte what libstdc++ produces for the same throw on that
profile. A peer-provoked store does not become safe by moving onto a std::pmr container
over a bounded slab; it migrates onto block_array_t and fails by value.
The example deliberately never provokes that boundary, because there is nothing to demonstrate
there but the abort.
What to notice¶
Reach for it in exactly one case: a
std::pmrcontainer whose element type is neither trivially copyable nor trivially destructible, soblock_array_t’s two static assertions reject it and the “invert the ownership at the public type” rule cannot be applied to a type the store does not own.tr::net::can_reassembly_t— whose slice map holds a refcountedtr::view::view_t— is the shipped example, which is why the adapter was owed to #873’s family 5 rather than built speculatively.What you get is worth having anyway. The container’s bytes come from the deployer’s slab instead of the global heap, and the slab’s size is the bound. The example checks the
std::pmr::vector’s element pointer lands inside the caller’s array and that the pool counted the bytes.std::pmr’s sizeddeallocatemaps 1:1 onto the seam’s sizedrelease, so apool_source_trecycles these blocks with no per-block header — the example destroys the vector and finds the freed block is the very next one the adapter hands out.It runs one direction, and the reverse must never be added. Wrapping a
std::pmr::memory_resourceso it could be used as ablock_source_twould have to answernullptrfrom anallocateannotatedreturns_nonnullthat signals only by throwing — the exact defect the block seam exists to escape, andcore/tests/mem_source_pmr_test.cppasserts the two types stay non-interconvertible.Two adapters over the same source compare unequal.
do_is_equalis address identity rather than adynamic_cast(the reference node ships-fno-rtti), so containers built over two adapters will copy rather than steal storage on a move-assign. Construct one adapter per source and pass it around.One source per receiver still applies. Wrapping a shared
pool_source_tin amemory_resourceinherits its ~15× multi-thread collapse unchanged; the adapter adds no shared state of its own and fixes nothing.It costs nothing to a target that does not name it. No library translation unit includes the header and it is absent from the
tracer.hppumbrella, so a build that never mentionssource_resource_tproduces byte-identical objects.Nothing here is conditional — the target builds and runs under every CI leg.
Source¶
1/*
2 * SPDX-License-Identifier: Apache-2.0
3 * SPDX-FileCopyrightText: Copyright 2026 avatarsd LLC
4 */
5
6/**
7 * @file
8 * @brief ONE CONCEPT — the `std::pmr` adapter buys PLACEMENT and BOUNDING, not failability.
9 *
10 * `tr::mem::source_resource_t` is a `std::pmr::memory_resource` holding one
11 * `block_source_t*`: `do_allocate` forwards to `try_alloc`, `do_deallocate` forwards to the
12 * seam's SIZED `release` — `std::pmr` carries the original size and alignment into
13 * `deallocate`, which is exactly the pair a header-free pool needs — and `do_is_equal` is
14 * address identity. It exists for the one case a migration cannot retype away: a `std::pmr`
15 * container whose element type is neither trivially copyable nor trivially destructible, so
16 * `block_array_t`'s two static assertions reject it (ADR-0079).
17 *
18 * @warning What it does NOT deliver is the reason the block seam exists. `std::pmr`'s only
19 * exhaustion signal is a throw, so this adapter's boundary is a `std::bad_alloc` on
20 * a hosted build and a `std::abort()` under `-fno-exceptions`. A PEER-PROVOKED store
21 * therefore does not become safe by moving onto a `std::pmr` container over a
22 * bounded slab; it migrates onto `block_array_t` and fails by value (see
23 * mem-block-array). This example never provokes that boundary, because there is
24 * nothing to demonstrate there but the abort.
25 *
26 * The adapter runs ONE direction. Wrapping a `std::pmr::memory_resource` so it could be used
27 * AS a `block_source_t` is not offered and must not be added: `allocate` is annotated
28 * `returns_nonnull`, so the wrapper's null check is deleted at exactly the `-Os`/`-Oz` levels
29 * the reference node ships at.
30 *
31 * Runs under ctest as `example_mem_source_resource`; returns non-zero on any failed check.
32 */
33
34#include <array>
35#include <cstddef>
36#include <cstdio>
37#include <memory_resource>
38#include <vector>
39
40#include "libtracer/mem_source.hpp"
41#include "libtracer/mem_source_pmr.hpp"
42
43namespace {
44
45/** @brief Report expectation @p what and record a failure on @p ok. */
46void check(bool& ok, bool cond, const char* what) {
47 std::printf(" [%s] %s\n", cond ? "ok" : "FAIL", what);
48 ok = ok && cond;
49}
50
51} // namespace
52
53int main() {
54 bool ok = true;
55 alignas(std::max_align_t) std::array<std::byte, 256> slab{};
56 std::array<tr::mem::size_class_t, 4> classes{};
57 tr::mem::pool_source_t<> pool{slab, classes};
58 tr::mem::source_resource_t adapter{pool};
59 check(ok, &adapter.source() == &pool, "the adapter names the source its bytes come from");
60
61 {
62 std::pmr::vector<int> samples{&adapter};
63 samples.reserve(16);
64 for (int i = 0; i < 16; ++i) samples.push_back(i);
65
66 const auto* const bytes = reinterpret_cast<const std::byte*>(samples.data());
67 check(ok, bytes >= slab.data() && bytes < slab.data() + slab.size(),
68 "PLACEMENT: the container's elements live in the caller's slab, not the heap");
69 check(ok, pool.used() == 64, "BOUNDING: 16 ints, 64 bytes, counted against the slab");
70 std::printf("std::pmr::vector of %zu ints: %zu of %zu slab bytes\n", samples.size(),
71 pool.used(), slab.size());
72 }
73
74 // The container's destructor deallocates with the SAME (bytes, align) it allocated with,
75 // which is the seam's sized-reclaim contract — so the pool files the block and reuses it.
76 check(ok, pool.used() == 64, "the freed block was filed, not re-carved");
77 void* const recycled = adapter.allocate(64, alignof(int));
78 check(ok, recycled == static_cast<void*>(slab.data()),
79 "and the very block the vector gave back is the next one the adapter hands out");
80 adapter.deallocate(recycled, 64, alignof(int));
81
82 // Address identity, not RTTI — the reference node ships -fno-rtti. Two adapters over the
83 // SAME source compare UNEQUAL, so build one adapter per source and pass it around.
84 tr::mem::source_resource_t second{pool};
85 check(ok, !adapter.is_equal(second), "two adapters over one source are not interchangeable");
86 check(ok, adapter.is_equal(adapter), "an adapter is equal only to itself");
87 return ok ? 0 : 1;
88}
See also: backends · memory substrate reference · a container that fails by value · a long-lived seam has to recycle.