Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
shm_pool_offset_ptr_data.hpp
Go to the documentation of this file.
1/* Flow-IPC: SHM-jemalloc
2 * Copyright (c) 2023 Akamai Technologies, Inc.; and other contributors.
3 * Each commit is copyright by its respective author or author's employer.
4 *
5 * Licensed under the MIT License:
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE. */
24
25/// @file
26#pragma once
27
28#include "ipc/common.hpp"
30#include <flow/util/util.hpp>
31#include <boost/interprocess/sync/named_mutex.hpp>
32#include <limits>
33#include <type_traits>
34
35/// @cond
36// -^- Doxygen, please ignore the following. It's undef-ed later anyway.
37
38// 1 if and only if the compiler is gcc proper (not clang, which also defines `__GNUC__`); keys pragmas below.
39#if defined(__GNUC__) && !defined(__clang__)
40# define IPC_SHM_ARENA_LEND_DETAIL_GCC_COMPILER 1
41#else
42# define IPC_SHM_ARENA_LEND_DETAIL_GCC_COMPILER 0
43#endif
44
45// -v- Doxygen, please stop ignoring.
46/// @endcond
47
48/* gcc (gcc-9 at least) is pretty paranoid about some bit-field paths below and gives some *very* cryptic warnings
49 * that amount to maybe-uninitialized. The code appears solid, so let's bypass it temporarily. */
50#if IPC_SHM_ARENA_LEND_DETAIL_GCC_COMPILER
51# pragma GCC diagnostic push
52# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
53#endif
54
55/// Segregated private stuff for ipc::shm::arena_lend.
57{
58
59// Types.
60
61/**
62 * Parameterization-independent aspects of Shm_pool_offset_ptr_data segregated in non-template base. In terms of
63 * public API the user should care about:
64 * - The proper type aliases for pool ID, pool offset.
65 * - Pool ID generation: generate_pool_id().
66 *
67 * However you should read the Shm_pool_offset_ptr_data doc header first.
68 *
69 * ### Bit order, endianness ###
70 * Terminology: MSB = most-significant (higher) bits; LSB = least-significant (lower) bits. So in a 64-bit
71 * unsigned number, the MSB is called bit 63, while the LSB is bit 0.
72 *
73 * In all the surrounding code endianness is intentionally not a factor:
74 * We treat multi-byte things as numbers, not individual bytes; so, e.g., `uint64_t n = 0x0001'0203'0405'0607ull` is
75 * stored in memory as `07 06 05 04 03 02 01 00`; but in code we might do `n & 0xFF` to isolate the LS-byte and indeed
76 * get `0x07`. (Or we might use a bit-field for the same purpose, but disregard that here.) We do *not* in this
77 * code do something like `reinterpret_cast<uint8_t*>(&n)[7]` (which would be
78 * wrong; in this likely-little-endian system `[0]` would be correct); it would be an unnecessary layer of
79 * non-portability, and it would make the code more difficult to understand anyway.
80 *
81 * As for the bit-field angle (which we asked you to disregard momentarily above):
82 * Okay, so actually endianness is indirectly a factor after all. As discussed elsewhere we do low-level
83 * pointer-tagging which assumes x86-64 architecture; and we use bit fields; as a result, at least in gcc/clang,
84 * bit fields are placed in the resulting composite number in the *reverse* order in which they're declared
85 * in a given `struct`. So for example, if we have `struct { uint64_t m_half2 : 32; uint64_t m_half1 : 32; } bits`,
86 * and `auto& num = *(reinterpret_cast<uint64_t*>(&bits))`, and we execute `bits.m_half1 = 0; bits.m_half2 = 1`, then
87 * `num = 1`. Note the reversed order inside the `struct` bit-field declaration. Presumably gcc/clang does it this
88 * way due to little-endianness of x86-64.
89 *
90 * ### Internals ###
91 * These will make sense upon reading the impl section of the Shm_pool_offset_ptr_data class doc header.
92 */
94{
95public:
96 // Constants.
97
98 /**
99 * # of bits in a pool offset (determines ceiling on size of pool; but note #pool_offset_t signedness-or-not
100 * is a factor). It is public (within `detail` though), as certain calculations involving pool max sizes and such
101 * might need to use this value.
102 */
103 static constexpr unsigned int S_N_POOL_OFFSET_BITS = 32;
104
105 // Types.
106
107 /**
108 * Pool offset type. Outside of Shm_pool_offset_ptr_data no entity can request a pool larger than what
109 * this can index.
110 *
111 * @note Maintenance/context: The width and signedness of Shm_pool_offset_ptr_data_base::pool_offset_t
112 * determines, as of this writing, the value to which SHM-jemalloc elsewhere sets the
113 * jemalloc per-arena option `retain_grow_limit`, having to do with limiting the growth of vaddr areas.
114 * Calling your attention to this, as it is a subtle effect that might affect something unexpectedly.
115 *
116 * ### Impl: Why 32-bit width chosen for `pool_offset_t` ###
117 * Per Shm_pool_offset_ptr_data doc header impl discussion, it must fit into #rep_t in addition to 1
118 * selector bit and #pool_id_t. We choose the full 32 LSB (of 64-bit #rep_t) to be #pool_offset_t; allowing
119 * for comfy and round maximally-2-or-4GiB-sized pools. 32 LSB also synergizes with #diff_t.
120 *
121 * ### Impl: Why signed type chosen for `pool_offset_t` ###
122 * That's a subtle discussion. (To get it out of the way: it limits pool size to 2GiB instead of 4GiB; this
123 * is acceptable (assuming 4GiB is acceptable, which it is).) However it adds entropy/reasoning complexity to
124 * make it signed; so what is gained for that trouble? Answer: Consider `decrement(1)`. If `m_pool_offset >= 1`,
125 * then all is cool; it becomes zero; nice. Now suppose Shm_pool_offset_ptr_data::m_pool_offset is zero
126 * and consider `get()` which forwards to `Repository::to_address()` which will act based on what we decide here
127 * type-wise (#pool_offset_t is propagated as the offset-type alias all over the code). `to_address()`, having
128 * looked up the pool base vaddr `base` (internally a `uint64_t` thing), will now do the following depending
129 * on our decision.
130 * - If unsigned: it will add an overflowed 32-bit positive 0xFFFF'FFFF (~4 billion) to the `uint64_t` base and
131 * return that; and we will return that in turn from `get()`. That is it'll return some arbitrary location
132 * in vaddr space.
133 * - If signed: it will subtract 1 and return that. That is it will return a somewhat less arbitrary location
134 * just ahead of the pool's start.
135 *
136 * Now, which is better? The answer is subjective, but we can make a pretty good case for the 2nd one.
137 * Firstly refer to the explanation in `get()` doc header for why `Repository_type::to_address()`, and therefore
138 * `get()`, would ever return an out-of-pool vaddr (spoiler alert: something like a `vector<uint8_t>::end()`
139 * iterator -- as an example -- may well be used for comparison -- not dereferencing! -- in a legit algorithm,
140 * and such a buffer might happen to reside at the tail of a pool). The question is, could that legitimately
141 * be a use case in the opposite end of a pool/buffer? Well... yes; a buffer might be at the start of a pool;
142 * and one might let a Shm_pool_offset_ptr_data get past its `begin()` in the reverse direction; for example
143 * something like: `for (...; p >= x.begin(); --p)`. (Think about an integer counter instead:
144 * `for (int i = ...; i >= 0; --i)`; if it were `unsigned int` instead then `i >= 0` would *always* be true;
145 * hence why in a reverse-direction such loop it's normal to use a signed type. We're following similar logic.)
146 * Granted, such code would not be exactly prudent on the user's part, and certainly one can avoid such situations
147 * by changing their code a bit; but it is nevertheless conceivable.
148 *
149 * So that is why it is signed. However it is not a slam-dunk. Vaguely speaking signed indices and signed integers
150 * in a context like this can add pain.
151 */
152 using pool_offset_t = int32_t;
153
154 /// Pool ID type alias. Please see detail::pool_id_t doc header for discussion as to the chosen bit width.
156
157 /**
158 * Analogous to `difference_type` in STL containers (but uses our naming conventions).
159 *
160 * ### Impl: Why `int32_t` was chosen ###
161 * As of this writing: for offset pointers, we've chosen the 32 LSB as the offset; and for raw pointers x86-64
162 * mandates 48 LSB as storing the vaddr. The lesser is 32; so there you go. (Note that it's normal
163 * to use the signed version of the size-type; e.g., `ptrdiff_t` is signed version of `size_t` normally;
164 * overflow can of course occur, still, but this is seen as the nature of the business we're in so to speak.)
165 *
166 * Secondarily Shm_pool_offset_ptr::difference_type as of this writing ~is `int`; and we specifically require
167 * x86-64 arch (see class Shm_pool_offset_ptr_data doc header); so `int` would be `int32_t`. There is a to-do
168 * as of this writing, elsewhere, to eliminate the `Difference_type` template-param configurability that determines
169 * Shm_pool_offset_ptr::difference_type; in that case Shm_pool_offset_ptr::difference_type would directly be set
170 * to `diff_t` (and in practice remain `int32_t`). */
171 using diff_t = int32_t;
172
173 // Methods.
174
175 /**
176 * Returns a heretofore-unused pool ID so as to be used to identify (in a Shm_pool_offset_ptr_data::Repository)
177 * a pool being created at this time. Note that one shall not use this, necessarily, for every insertion into
178 * such a table: the pool ID is cross-process, so a borrowing process will use the pool ID that was
179 * `generate_pool_id()`ed by the owner process (hence both the pool name and pool ID shall be IPC-transmitted
180 * during the borrowing process).
181 *
182 * ### Design / what is "heretofore-unused"? ###
183 * Formally: one just uses generate_pool_id() when a new SHM-pool is being created and added to the `Repository`.
184 * (It might be convenient for other reasons to include the ID in the pool name; but I digress.) Internally,
185 * though, what actually happens and why?
186 *
187 * (Firstly this is all about kernel-persistence, meaning once the machine restarts, it's a new day.)
188 * The decision here is to use a 31-bit ID -- hence ~2 billion unique IDs -- across *all* processes on the
189 * machine, regardless of application, split, or anything else; in that sense it's different from all other
190 * cross-process namespaces in ::ipc as of this writing, which at least segregate things by owner application.
191 * In this case we don't have a string namespace to work with, though (unlike util::Shared_name). We could
192 * alternatively split up the 2 billion-wide space among (e.g.) splits. The downside of this is complexity
193 * of API/code; and limiting the number of pools each (say) split can generate across its lifetime.
194 * (Trying to register/unregister IDs is also too complicated.) The bottom line is: 2 billion pools,
195 * by all applications, between machine boots should be sufficient; for example we consider PID to be a
196 * unique-enough-across-all-time (between boots) process ID in other parts of the system. (Granted there are
197 * potentially many pools per process, but still, ballpark, this reasoning is okay.)
198 *
199 * Moreover we will wrap-around having reached the max 31-bit number, back to 1 (0 is special and shall not
200 * be used). If by some incredible miracle we actually do reach this overflow condition, the chances that
201 * that the wrapped-around-to-processes are still around/relevant = virtually nil. So even that should work.
202 *
203 * @return See above.
204 */
206
207 /**
208 * The max size of a SHM-pool such that any offset into it -- including the one-past-the-last-byte offset (e.g.,
209 * an `end()` iterator of a buffer at the tail of the pool) -- is representable as #pool_offset_t.
210 * Any SHM-pool-creating code whose pools may be pointed-into by Shm_pool_offset_ptr_data must not exceed it;
211 * an offset past #pool_offset_t range would be silently truncated (there is intentionally no runtime check on
212 * that perf-critical path).
213 *
214 * It is a page (4Ki) short of the precise max, as slack.
215 *
216 * @return See above.
217 */
218 static constexpr size_t max_pool_size();
219
220protected:
221 // Types.
222
223#if !(((defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__)) || (defined(_MSC_VER) && defined(_M_X64)))
224 static_assert(false, "This pointer-tagging impl relies on the target architecture being x86-64 a/k/a AMD64.");
225#endif
226
227#if (!defined(__GNUC__)) && (!defined(__clang__))
228 static_assert(false, "The bit-field code below is tested only with gcc and clang.");
229#endif
230
231 /// The unsigned integer type used to store Shm_pool_offset_ptr_data pointer payloads.
232 using rep_t = uint64_t;
233 static_assert(sizeof(void*) == sizeof(rep_t), "x86-64 should have 64-bit pointers.");
234
235 /**
236 * A bit-field overlaid over any #rep_t, this represents a pointer with the selector MSB indicating
237 * a pool-and-offset-bearing non-null payload. Alternatively, if #m_selector_offset_else_raw is 0,
238 * then the remaining bits in the bit-field shall be ignored; a `*this` holds either null or a raw-pointer
239 * bearing payload.
240 */
242 {
243 // Data. In reverse bit order as @noted below!
244
245 /**
246 * If and only if #m_selector_offset_else_raw: The offset within pool IDed by #m_pool_id.
247 *
248 * @note If #pool_offset_t is signed, then this *can* be negative! Please read that alias's doc header.
249 */
251
252 /// If and only if #m_selector_offset_else_raw: The pool ID.
254
255 /**
256 * Either 0 (raw or null pointer; do not use the other bit members) or 1 (offset pointer).
257 *
258 * @note With gcc/clang/x64-64 this is listed *last*, because it is the *most* significant datum.
259 * The fields are thus in reverse order of bit order.
260 */
262 }; // struct Offset_ptr_rep
263 static_assert(sizeof(Offset_ptr_rep) == sizeof(rep_t),
264 "Expecting bit-field to be packed tightly; not guaranteed by standard but in practice is.");
265
266 /**
267 * A bit-field overlaid over any #rep_t, this represents a pointer with the selector MSB indicating
268 * a ra-pointer-bearing non-null payload. Alternatively, if #m_selector_offset_else_raw is 1,
269 * then the remaining bits in the bit-field shall be ignored; a `*this` holds either null or an offset-pointer
270 * bearing payload.
271 */
273 {
274 // Constants.
275
276 /// # of bits in #m_val_bits.
277 static constexpr size_t S_N_VAL_BITS = 48;
278
279 // Data. In reverse bit order as @noted below!
280
281 /**
282 * If `!m_selector_offset_else_raw`, and the entire value is not all-zero-bits:
283 * #m_ext_sign_msb repeated yet again, followed by 47 more bits; altogether the 48 bits represent
284 * the vaddr itself, sans all the bits to the left which are sign-extension bits that complete the
285 * canonical form (except #m_selector_offset_else_raw which we use as metadata in our pointer-tagging
286 * scheme).
287 */
289
290 /**
291 * If `!m_selector_offset_else_raw`, and the entire value is not all-zero-bits:
292 * #m_ext_sign_msb repeated.
293 */
295
296 /**
297 * If `!m_selector_offset_else_raw`, and the entire value is not all-zero-bits: The most
298 * significant, after the leading bit taken up by #m_selector_offset_else_raw, sign-extension bit
299 * in this raw pointer. In a properly constructed pointer:
300 * - If this is 0, then the next (64 - 1 - 48) = 15 bits shall also be 0. Those 3 terms are respectively:
301 * bit count of #rep_t (formally `sizeof(rep_t) * 8`); 1 bit taken up by #m_selector_offset_else_raw
302 * pointer-tagging bit; then the 48 LSB storing the actual vaddr minus the canonicalizing padding
303 * (a/k/a *sign extension*).
304 * - If this is 1, then the next 15 bits shall also be 1.
305 */
307
308 /**
309 * Either 0 (raw or null pointer; if all other bits are also 0, then do not use the other bit members) or 1
310 * (do not use the other bit members). When converting to a canonical `void*`: leave the rest of the
311 * bits alone; but set this one to equal #m_ext_sign_msb (the next LSB).
312 *
313 * @note With gcc/clang/x64-64 this is listed *last*, because it is the *most* significant datum.
314 * The fields are thus in reverse order of bit order.
315 */
317 }; // struct Raw_ptr_rep
318 static_assert(sizeof(Raw_ptr_rep) == sizeof(rep_t),
319 "Expecting bit-field to be packed tightly; not guaranteed by standard but in practice is.");
320 /* @todo It'd be nice to write a static_assert() directly ensuring that members of a bit field in this arch
321 * appear in the opposite order of declaration (LSB declared first).
322 * This is easily doable with (constexpr) std::bit_cast (available in C++20), but as of this writing we're on
323 * C++17.
324 * static_assert(std::bit_cast<rep_t>(Offset_ptr_rep{ 0, 0, 1 }) == (rep_t(1) << 63), "..."); // Etc.
325 * An assert() is easy enough to write; but so far my (ygoldfel) attempts to write a compile-time type-punning
326 * expression have been fruitless, so a static_assert() has not been forthcoming so far. It's not a *huge*
327 * deal, as we do ensure the architecture is specifically so-and-so at compile-time; but it'd be a little
328 * nicer to instead assert aspects of the architecture we care about as opposed to its identity.
329 * Judge an architecture by the content of its character! At least we did do so regarding the bits getting
330 * properly packed into a rep_t. */
331
332private:
333 // Data.
334
335 /// Used with `std::call_once()` to ensure #s_pool_id_shm_region_or_none is initialized no more than once.
336 static std::once_flag s_pool_id_shm_region_init_flag;
337
338 /**
339 * The SHM-object (pool) mapped by #s_pool_id_shm_region_or_none. Default-cted until initialization.
340 *
341 * Stored so that the pool does not disappear (if all handles close, then the pool is removed by system).
342 */
344
345 /**
346 * Initialized no more than once (in this process) by generate_pool_id(), a handle to tiny SHM region
347 * storing (only) a `pool_id_t` used to generate unique pool IDs. Default-cted until initialization.
348 *
349 * The pointee pool has the same name across the entire system.
350 *
351 * Stored for performance, so that generate_pool_id() need not map this each time but only once.
352 */
353 static bipc::mapped_region s_pool_id_shm_region_or_none;
354
355 /**
356 * Initialized no more than once (in this process) by generate_pool_id(), a handle to a named-mutex
357 * protecting the state of the pool possibly-pointed-to by #s_pool_id_shm_region_or_none. More formally
358 * the state of that pool (when this mutex is unlocked) is one of:
359 *
360 * - It does not exist.
361 * - It exists; is sized for the stored `pool_id_t`; and the bits therein contain an ID value, such that
362 * `++`ing yields a valid (non-zero) ID.
363 *
364 * Therefore one must lock this before opening or creating the pool; and if creation was indeed necessary,
365 * then before unlocking one must size it; and most importantly initialize it to a pre-valid ID (perhaps 0 or
366 * 1). Not doing the latter breaks the invariant: after pool creation the contents of the pool = incoherent;
367 * a `++` on this zero-looking-value can and does cause undefined behavior.
368 *
369 * The pointee mutex has the same name across the entire system.
370 *
371 * Stored for performance, so that generate_pool_id() need not open this each time but only once.
372 */
373 static std::optional<boost::interprocess::named_mutex> s_pool_id_mutex_or_none;
374}; // class Shm_pool_offset_ptr_data_base
375
376/**
377 * Implementation core of Shm_pool_offset_ptr. It provides the latter's essential capabilities while leaving out
378 * the interface-y necessities of a standard fancy-pointer type such as the concept of the pointed type, `rebind`,
379 * and so on. One can think of us implementing the core of `uint8_t*` only (conceptually speaking);
380 * as opposed to a `T*` parameterized on `T`.
381 *
382 * The requirements for `Repository::to_address()` and `Repository::from_address()` are below, so look for that.
383 *
384 * Implementation design
385 * ---------------------
386 * ### Pointer tagging scheme ###
387 * The goal is to provide a fancy-pointer type capable of pointing into the SHM-pool system as per
388 * shm::arena_lend design, wherein:
389 * - A pointer may be null (not-a-pointer). Otherwise:
390 * - Each pointer is conceptually composed of 2 data: pool ID (that specifies a particular pool in a global
391 * table; such a pool has a base vaddr valid in this process); and a pool offset (within that
392 * pool, off the aforementioned base vaddr, in bytes). The global table is `Repository_type` template
393 * parameter and contains:
394 * - `static void* to_address(pool_id_t pool_id, pool_offset_t pool_offset)`: Get vaddr from the 2 pointer data
395 * (a/k/a *handle*). If `pool_id` is not a known pool, undefined behavior (UB). (We do not assume
396 * it will return null, and your impl may be a bit faster if it skips the check and lets the UB freak flag fly.)
397 * - (As of this writing `Borrower_shm_pool_collection_repository` and `Owner_shm_pool_repository` both
398 * yield UB.)
399 * - `pool_offset` may be negative or exceed pool size; this is *not* UB: `to_address()` shall yield
400 * the out-of-pool-bounds vaddr.
401 * - `static void from_address(const void*, pool_id_t& pool_id, pool_offset_t& pool_offset)`: The opposite.
402 * - If #S_CAN_STORE_RAW_PTR template parameter is `true`:
403 * - Sets `pool_id = 0` (a special invalid ID), if the input `void*` is not in a SHM-pool.
404 * - Otherwise:
405 * - Allowed to yield UB, if the input `void*` is not in a SHM-pool.
406 * Then our subsequent get() also yields UB.
407 * - (As of this writing `Borrower_shm_pool_collection_repository` yields UB.)
408 * - Allowed to set `pool_id = 0` instead as well. Then our get() yields null to try to contain UB entropy.
409 * - Alternatively, for the case where a datum is located outside any SHM pool (perhaps on the stack),
410 * so in particular `from_address()` would yield `pool_id == 0`, it instead stores a raw vaddr.
411 * - Depending on the compile-time situation this alternative may be disallowed (#S_CAN_STORE_RAW_PTR
412 * template parameter is `false`). In particular in the shm::arena_lend design that is the case on the borrower
413 * side (when interpreting a data structure created by another process and transmitted to -- borrowed
414 * by -- this one). (As of this writing `Borrower_shm_pool_collection_repository` is this way in particular.)
415 * - We call this (in this context) a *raw pointer*; whereas otherwise it is an *offset pointer*.
416 * - (Note that `boost::offset_ptr`, while serving a similar role to our offset pointer, is not the same
417 * thing; by using the offset-versus-`this` technique it can represent both in-SHM and raw pointers
418 * without any dichotomy as to its internal representation. This is great, but it doesn't work
419 * for our case: We have more than 1 SHM pool, and if pointer in pool 1 needs to point into pool 2,
420 * and pool 1 and pool 2 base vaddrs are not equidistant to each other in process 1 versus process 2,
421 * then the `boost::offset_ptr` fails.)
422 * - In all cases its `sizeof` must be as small as possible; in fact our explicit goal is to have it equal
423 * `sizeof(void*)` (it obviously cannot be even smaller). Then `*this`es can be copied around as quickly
424 * as regular `T*` pointers. (Consider, e.g, a complex STL-compliant container of containers of....)
425 *
426 * The design of such a thing would be straightforward if not for the latter requirement. Consider that, when
427 * `CAN_STORE_RAW_PTR == true`, the fancy-pointer must be able to store all `8 * sizeof(void*)` bits of a
428 * raw (non-SHM) address; *and* it needs at least 1 bit to specify *whether* it is a raw pointer or
429 * an offset pointer. So that is already more than `8 * size(void*)` bits. (Due to alignment, and so on,
430 * the amount of extra space used will be much greater still than the extra bit, even if that extra bit alone were
431 * somehow okay.)
432 *
433 * @note For the rest of this discussion, which is by necessity low-level and not perfectly portable, let us
434 * assume the compilation-target system is x86-64 a/k/a AMD64. (We `static_assert()` on this.)
435 *
436 * In x86-64 there are 64 bits in a raw pointer. And if they were all potentially used, then we'd be screwed
437 * as shown above. In reality, however, only the 48 LSB bits store the actual vaddr: bits 47, 46, ..., 0
438 * (good for 256TiB of addressable space). The remaining 16 bits (bits 63, 62, ..., 48) are called
439 * the *sign extension* and must equal bit 47; this is the *canonical form* pointers must be stored in, or else
440 * the processor will explode on dereference (SEGV or similar). So, as long as our get() returns a pointer value
441 * in this form, internally we can use those 16 high bits to store additional information a/k/a metadata.
442 * This is called *pointer tagging*.
443 *
444 * In our case we have only 1 bit (pun intended) of metadata: "is this an offset pointer or raw pointer?".
445 * We shall use the most significant bit (bit 63). Call it the *selector* bit:
446 * - The special value `nullptr` is represented as all bits = 0. One can think of this as
447 * the 3rd type of storable pointer: not-a-pointer. It's important that no value in either of the other
448 * forms (below) will also result in all bits = 0. So if not null then:
449 * - Selector bit = 0 => raw pointer.
450 * - Bits 47 through 0 = actual vaddr value. (One of these at least must be 1; otherwise it'd be null.)
451 * - Bits 62 through 48 = sign extension = bit 47 repeated. (Hence get(), in this case, need only:
452 * copy `m_rep`; in the copy flip bit 63 to 1, if and only if bit 47 is 1;
453 * and return the copy. Quite efficient.)
454 * - Selector bit = 1 => offset pointer.
455 * - Now we have 63 bits to store the pool ID and pool offset. We can make various decisions here about
456 * how to split up that real estate; see Shm_pool_offset_ptr_data_base for the decisions made.
457 * - However: The pool ID must never be zero. (Offset certainly can.) This is a reserved value which
458 * `Repository_type::from_address()` can efficiently use to indicate not-found-in-any-SHM-pool condition.
459 *
460 * ### Bit fields and type-punning versus shift/AND/OR/NOT ###
461 * In the functions of this class we need to access individual bits and/or bit sequences (interpreted as numbers
462 * at times; e.g., the selector bit is MSB = bit 63; pool ID would be bits 62 through 32, for 31 bits total).
463 * When accessing or modifying part of `m_rep` we have two choices more or less:
464 * - Use shifting `<<` `>>` and OR `|` and AND `&`, with perhaps a splash of NOT `~`.
465 * - Use bit fields combined with type-punning (accessing a value of one type by treating that memory location
466 * as value(s) of other type(s)).
467 * - (The type-punning itself in turn can be done a couple ways: Via native `union` (not `std::variant` which
468 * introduces more stored data!); or via `reinterpret_cast<>`ing between pointer types.
469 * The latter, in our direct experience, is inferior: One must `pragma`-away warnings; and after that one
470 * must use `volatile` to prevent (at least gcc-9 `-O3`) from causing wrong behavior due to optimizer not
471 * understanding when and how `*this` has been modified; but `volatile` then potentially subverts the
472 * optimizer's actual job of speeding things up. The `union` way is also slightly more readable and slightly
473 * more concise. The only disadvantage of `union`ing (here): this adds yet another instance of using what is,
474 * technically (according to the standard), undefined behavior; as officially only the last-assigned `union`
475 * member has a defined value, a rule we absolutely do and must break. However, (1) we already rely
476 * on architecture-specific behavior, which is much riskier anyway (in for a penny, in for a pound); and (2)
477 * this type-punning technique is quite wide-spread and understood to be in practice correct in any
478 * conceivable environment (in practice).)
479 * - (One could also, or additionally, use `std::bit_cast`;
480 * however this would not be functionally equivalent, as this operation copies underlying bytes and cannot
481 * modify values in-place; it could affect performance. Also `bit_cast` is C++20; as of this writing we are on
482 * C++17. So we'd need to access gcc built-ins instead, until we're at C++20.)
483 *
484 * The aspects of bit field approach:
485 * - Bit field code is easier to understand (once declared at least). (Pro)
486 * - Bit fields are very not-portable across architectures and even compilers within a given architecture:
487 * standard specifically says their packing, order in memory, and byte-straddling behavior are unspecified.
488 * (Con)
489 * - (However, clang+gcc + x64-64 has a well defined behavior.)
490 * - Bit fields are faster or not-slower, according to the Internet (and apparently with clang even more so). (Pro)
491 * - (Performance here is quite important, as pointer-to-address and address-to-pointer are potentially very
492 * common pointer-involving operations, in aggregate even more frequent than dereferencing.)
493 *
494 * Ultimately we decided, for now at least, that since this code is already built around a particular architecture,
495 * non-portability is much less of a concern that normal. On the other hand performance and clarity are extremely
496 * and rather important (respectively). So we went with bit fields.
497 *
498 * @todo Consider extending Shm_pool_offset_ptr impl, currently non-portably limited to x86-64 architecture
499 * (and gcc/clang compilers), to other modern architecture(s) (perhaps ARM and other mobile-related arch?).
500 *
501 * @todo Examine empirical performance impact of bit-field approach in Shm_pool_offset_ptr impl versus alternative
502 * bit-arithmetic approach (shift, AND, OR, NOT). I.e., benchmark it.
503 * @todo Examine portability details of bit-field approach in Shm_pool_offset_ptr impl.
504 *
505 * @tparam Repository_type
506 * The shared memory pool repository type that can turn an offset-pointer *handle* (as represented by
507 * pool ID + pool offset pair) into the vaddr per the present shm::arena_lend-compliant process (a `void*`);
508 * and vice versa. It shall have the 2 `static` APIs shown above, `to_address()` and `from_address()`.
509 * See details above regarding their expected behaviors.
510 * @tparam CAN_STORE_RAW_PTR
511 * Whether a `*this` is allowed to represent a vaddr that is neither null nor belonging to any SHM-pool
512 * registered in the global `Repository_type` at the time of construction of a `*this` from a `void*`.
513 * If `true` in that event, then a raw pointer shall be stored; if `false`, then null is stored (but
514 * unless user code checks for this rather odd possibility, in practice subsequent undefined behavior
515 * is likely).
516 */
517template<typename Repository_type, bool CAN_STORE_RAW_PTR>
520{
521public:
522 // Types.
523
524 /// Our base type.
526
527 /// Short-hand for template parameter `Repository_type`; may be useful for generic programming.
528 using Repository = Repository_type;
529
530 /// Short-hand for template parameter `CAN_STORE_RAW_PTR`; may be useful for generic programming.
531 static constexpr bool S_CAN_STORE_RAW_PTR = CAN_STORE_RAW_PTR;
532
533 // Constructors/destructor.
534
535 /// Construct with `nullptr`.
537
538 /**
539 * Construct from vaddr.
540 *
541 * Corner case: If #S_CAN_STORE_RAW_PTR is `false`, and `p` is neither `nullptr` nor belongs to #Repository,
542 * then formally behavior is undefined. Practically:
543 * - If `Repository::from_address()` yields `pool_id = 0` in this situation, then we shall act as if
544 * `p == nullptr` (we become null).
545 * - If it yields UB in this situation, then we yield UB also.
546 *
547 * @param p
548 * See above.
549 */
550 Shm_pool_offset_ptr_data(const void* p);
551
552 /**
553 * Copy constructor.
554 *
555 * @param src
556 * Source object.
557 */
559
560 /**
561 * Copy constructor from object whose type is the opposite w/r/t whether it can store a raw pointer.
562 *
563 * Corner case: If #S_CAN_STORE_RAW_PTR is `false`, and `src.is_raw() == true`, then
564 * this ctor shall yield `this->to_bool() == false`. Or in regular words: if our type is such that we
565 * can only represent in-SHM addresses, and `src` stores a raw address, then we cannot (safely) represent
566 * the address in `src` and will represent null instead. (We are not aware of a use-case for such a
567 * conversion to be successful and prefer to deterministically result in null.)
568 *
569 * @param src
570 * Source object.
571 */
573
574 // Methods.
575
576 /**
577 * Assignment.
578 *
579 * @param src
580 * Source object.
581 * @return `*this`.
582 */
584
585 /**
586 * Returns whether the offset pointer holds a non-null, raw pointer; as opposed to an offset pointer or null.
587 *
588 * @return See above.
589 */
590 bool is_raw() const;
591
592 /**
593 * Retrieves the pointer to the object, if there is one stored; otherwise `nullptr`.
594 *
595 * ### Corner cases ###
596 * This class acts in particular ways depending on what representation is currently stored, and what the value is;
597 * one can get to odd situations via increment() in particular. First we describe what `*this`
598 * will do formally; then we will discuss how this might affect the public-facing containing fancy-pointer type.
599 *
600 * If null is actually stored, as via default ctor, ctor from `nullptr`, or assignment of either, then we
601 * return `nullptr` as required. Now assume this is not the case.
602 *
603 * If `S_CAN_STORE_RAW_PTR == false`:
604 * - Recall that internally a *handle* is stored: pool ID, offset into that pool.
605 * - If this pool does not exist, *and* `Repository::to_address()` detects this: we return `nullptr`.
606 * Implications:
607 * - If one dereferences it, behavior is obviously undefined. This is desirable and likely similar to
608 * what would occur with dereferencing a corrupt or (obviously) null regular pointer.
609 * - If one compares to it such as the `.end()` scenario below, they're likely to get some behavior
610 * they don't expect -- but do they expect, if the system/they allowed the pool to get unmapped
611 * while running algorithms on related data? Not our problem.
612 * - Even though we do this, formally it is UB. We are just trying to contain the entropy somewhat.
613 * - If this pool does not exist, *and* `Repository::to_address()` yields UB as a result:
614 * we too yield UB.
615 * - If this pool does exist, but the offset is out of bounds, we will return the out-of-bounds
616 * address based on the simple `base + offset` formula. If offset is negative, we'll return pre-pool
617 * vaddr; if offset is positive and equals or exceeds pool size, we'll return the post-pool vaddr.
618 * Reason: See #pool_offset_t doc header.
619 *
620 * If `S_CAN_STORE_RAW_PTR == true`:
621 * - If `is_raw()`: See above `!S_CAN_STORE_RAW_PTR` case. Same deal here.
622 * - Else: We return the stored address (in canonical form).
623 *
624 * In short: get() will *not* yield `nullptr` for various out-of-bounds situations. to_bool() conversion
625 * will act consistently with this.
626 *
627 * @return See above.
628 */
629 void* get() const;
630
631 /**
632 * Returns `false` if and only if `get() == nullptr` (assuming `*this` is valid; otherwise
633 * formally undefined behavior; but see below).
634 *
635 * ### Corner case ###
636 * The caveat, "assuming `*this` stores a valid pointer," refers to the the following case (which implies
637 * `*this` is already in bug-somewhere territory): If
638 * - `*this` refers to an offset, rather than raw, pointer (always the case if `S_CAN_STORE_RAW_PTR == false`), and
639 * - somehow `*this` refers to a pool never/no longer in #Repository, and
640 * - `Repository::to_address()` given such invalid args returns null rather than engaging in UB, then:
641 *
642 * `get()` would return null and therefore `get() == nullptr` would return `true`; whereas to_bool() as
643 * implemented -- for performance never actually invoking the (relatively expensive) `get()`/`to_address()` --
644 * will nevertheless return `false`. Again, this entire scenario is predicated on a situation that, in the
645 * first place, means something already went wrong. We are just pointing out to_bool(), for performance,
646 * does not actually call get(), and therefore "if and only if `get() == nullptr`" assumes no ill scenario.
647 *
648 * @return See above.
649 */
650 bool to_bool() const;
651
652 /**
653 * Returns `true` if and only if `get() == other.get()`, albeit with certain acceptable exceptions and a
654 * better perf profile in practice. The purpose is to provide a faster implemention of `==` and `!=` for
655 * Shm_pool_offset_ptr. (Flip the result of equals() to get `!=`.)
656 *
657 * Formal contract is as noted above; and the exceptions were we might not return `.get() == other.get()` are
658 * as follows. Assume `*this` is P1, `other` is P2.
659 * - If P1 and P2 are non-null (`.to_bool() == true`) and non-raw (`.is_raw() == false`; always the case
660 * if #S_CAN_STORE_RAW_PTR is `false`); *and*
661 * - P1's pool is different from P2's pool; *and*
662 * - the offset for P1 or P2 or each of them points to outside the aforementioned pool
663 * (hence offset is either negative or past pool's size); *then*:
664 * - we shall return `false` (not-equal), even though in reality `get() == other.get()` is possible.
665 * - We consider this a pathological case worth the perf gains realized.
666 *
667 * @param other
668 * Thing against which to compare.
669 * @return See above.
670 */
671 bool equals(Shm_pool_offset_ptr_data other) const;
672
673 /**
674 * Returns `true` if and only if `get() < other.get()`. The purpose is to provide a faster implemention of
675 * `<` and `>=` for Shm_pool_offset_ptr. (Flip the result of less_than() to get `>=`.)
676 *
677 * @param other
678 * Thing against which to compare.
679 * @return `get() < other.get()`.
680 */
681 bool less_than(Shm_pool_offset_ptr_data other) const;
682
683 /**
684 * Returns `true` if and only if `get() > other.get()`. The purpose is to provide a faster implemention of
685 * `>` and `<=` for Shm_pool_offset_ptr. (Flip the result of greater_than() to get `<=`.)
686 *
687 * @param other
688 * Thing against which to compare.
689 * @return `get() > other.get()`.
690 */
691 bool greater_than(Shm_pool_offset_ptr_data other) const;
692
693 /**
694 * Returns (essentially) `get()` minus `other.get()` in bytes; so (assuming no overflow issues) the value `N`
695 * such that `other.increment(N)` would make `other.get() == get()`.
696 *
697 * @note The return type is #diff_t which, while also signed, is narrower than `ptrdiff_t`. This method could
698 * be, in and of itself, changed to return `ptrdiff_t` which would avoid some overflow possibilities.
699 * That just wouldn't be all that useful, in context of how we're used: to implement the core of
700 * Shm_pool_offset_ptr pointer-minus-pointer op; as increment() implements the core of pointer-`+=`.
701 * (increment() takes `diff_t` which is an honest limitation given that #pool_offset_t is narrower than
702 * the full #rep_t. We don't have such a limitation, but we're on the same team.)
703 *
704 * @param other
705 * An object.
706 * @return See above.
707 */
709
710 /**
711 * Increments `*this` by a number of bytes (which can be positive, negative, or zero). As explained in
712 * get() and #pool_offset_t doc headers this is maximally permissive, including when essentially nonsensical
713 * get() return value might result. Summary of edge cases including the aforementioned ones but not limited to them:
714 * - If `!*this` legitimately (we are null due to being so assigned, not due to the pool referred-to within
715 * becoming invalid), this will:
716 * - (if #S_CAN_STORE_RAW_PTR is `true`) act similarly to native `+=` (become numerically = the bits in `bytes`);
717 * - (else) no-op. So don't do that. Really, though, it would be ill-advised to do it with a raw pointer too.
718 * - If `*this` is a legit offset pointer, meaning it refers to an existing pool, and incrementing the stored
719 * offset by `bytes` (which might make it smaller) places `*this` before or past the pool boundary: We do so.
720 * Naturally dereferencing get() would yield undefined behavior; but for example `increment(-bytes)` would
721 * get `*this` back to its original state which might be just fine.
722 * - If arithmetic overflow occurs:
723 * - This function will not invoke undefined behavior (crash or similar).
724 * - get() will not either.
725 * - However no guarantees are made as to the value get() would return numerically. Informally speaking an
726 * attempt is made to hew as close to native pointer behavior as possible, depending on is_raw(), but
727 * it is a best effort only. Informally it is generally ill-advised to rely on any particular behavior
728 * at that point.
729 *
730 * @param bytes
731 * See above.
732 */
733 void increment(diff_t bytes) noexcept;
734
735private:
736 // Types.
737
738 /// Short-hand from base.
740 /// Short-hand from base.
742 /// Short-hand from base.
744 /// Type of `m_rep`. @see #m_rep.
745 using Representation = union
746 {
747 /// The full raw bits.
748 rep_t m_rep;
749 /// The bits viewed in the offset-ptr form.
751 /// The bits viewed in the raw-ptr form.
753 };
754
755 // Methods.
756
757 /**
758 * Helper, surely inlined with any decent optimizer, that returns what get() would return if
759 * #m_rep equalled the supplied value, and its MSB were 0, but at least one other bit were 1. In plainer language:
760 * we know it's not null, and its MSB indicates a raw pointer; this returns the valid (canonical)
761 * pointer represented.
762 *
763 * Must not be compiled unless #S_CAN_STORE_RAW_PTR is `true`.
764 *
765 * @param rep
766 * Would-be #m_rep; does not equal zero; but MSB but be zero; or UB results.
767 * @return What get() would return given `m_rep == rep`.
768 */
769 static void* get_as_raw(Representation rep);
770
771 /**
772 * Helper a-la get_as_raw() but given only the pre-condition that `rep` is not zero (does not represent null).
773 *
774 * @param rep
775 * See above.
776 * @return See above.
777 */
779
780 // Friends.
781
782 /// Friend of this class (cross-`CAN_STORE_RAW_PTR` copy ctor needs access to `m_rep`).
783 friend class Shm_pool_offset_ptr_data<Repository_type, !S_CAN_STORE_RAW_PTR>;
784
785 /// Friend of this class.
786 template<typename Repository_type2, bool CAN_STORE_RAW_PTR2>
787 friend std::ostream& operator<<(std::ostream& os,
789
790 /**
791 * The raw bits. See our class doc header and #Base internals. The preferred method of interpreting it is:
792 * -# Check against simply being equal to 0 (all bits). If so => not-a-pointer (null).
793 * -# Access `m_rep.m_offset_ptr_rep.m_selector_offset_else_raw`; if
794 * 1 then: `Offset_ptr_rep::m_pool_id` specifies the pool in global #Repository singleton;
795 * `Offset_ptr_rep::m_pool_offset` is the offset off its base vaddr. Else:
796 * -# All but the MSB of #m_rep = the same bits in the canonical result get() must return (the raw
797 * `void*`). To compute the full canonical pointer (namely in get()):
798 * - The 2nd MSB (which happens to equal the following extended-sign bits) is in
799 * `m_rep.m_raw_ptr_rep.m_ext_sign_msb`.
800 * - get() must copy `m_rep.m_rep` but before returning this copy ensure its MSB equals `m_ext_sign_msb`.
801 * In other words if and only if `Raw_ptr_rep::m_ext_sign_msb` is 1, the MSB in the copy shall be set to 1.
802 *
803 * If `!S_CAN_STORE_RAW_PTR`, then (corruption/undefined behavior aside) in step 2 `m_selector_offset_else_raw == 1`
804 * always.
805 */
807}; // class Shm_pool_offset_ptr_data
808
809// Free functions: in *_fwd.hpp.
810
811// Template implementations.
812
814{
815 constexpr size_t SLACK_SZ = 4 * 1024;
816 constexpr unsigned int N_OFFSET_MAGNITUDE_BITS
817 = S_N_POOL_OFFSET_BITS - (std::numeric_limits<pool_offset_t>::is_signed ? 1 : 0);
818 return (size_t(1) << N_OFFSET_MAGNITUDE_BITS) - SLACK_SZ;
819}
820
821template<typename Repository_type, bool CAN_STORE_RAW_PTR>
823 m_rep{ .m_rep = 0 }
824{
825 // Not-a-pointer: All bits are zero. (See class doc header for explanation.)
826}
827
828template<typename Repository_type, bool CAN_STORE_RAW_PTR>
830{
831 // m_rep is uninitialized garbage.
832
833 if (!p)
834 {
835 m_rep.m_rep = 0;
836 return;
837 }
838 // else
839
840 pool_id_t pool_id_or_0;
841 pool_offset_t pool_offset;
842 Repository::from_address(p, pool_id_or_0, pool_offset); // @todo Would compiler let us target m_pool_* directly?
843
844 if (pool_id_or_0 == 0)
845 {
846 if constexpr(S_CAN_STORE_RAW_PTR)
847 {
848 /* This is slightly subtle; if one revisits the class doc header, one sees that, in order for
849 * raw-pointer get() to at most merely need to flip one bit and otherwise return the result as-is, we
850 * store as many bits of the canonical form as possible (all but the MSB); and only set the MSB to the
851 * required selector value (0). */
852 m_rep.m_rep = reinterpret_cast<rep_t>(p);
853
854#if 0 // Can be interesting to temporarily enable to ensure our assumptions about canonical form seem to hold.
855 // Perf: Enable only when perf does not matter.
856 {
857# ifndef NDEBUG
858 // Shift by 47 bits = 0... then 17 previously MSB.
859 const auto sign_bits = m_rep.m_rep >> (Raw_ptr_rep::S_N_VAL_BITS - 1);
860# endif
861 assert(((sign_bits == rep_t(0)) // First 17 bits were 0s. Or:
862 ||
863 // First 17 bits were 1s. (1111...1 + 1 = 10000...0 = 1 then 17 0s, which is 1 << 17.)
864 ((sign_bits + rep_t(1)) == (rep_t(1) << ((sizeof(rep_t) * 8) - Raw_ptr_rep::S_N_VAL_BITS + 1))))
865 && "A non-canonical raw pointer encountered.");
866 }
867#endif
868
869 // Set MSB to 0. Operate on bit-field for clarity and probably speed (see class doc header for discussion).
870 m_rep.m_raw_ptr_rep.m_selector_offset_else_raw = rep_t(0);
871 } // if constexpr(S_CAN_STORE_RAW_PTR)
872 else // if constexpr(!S_CAN_STORE_RAW_PTR)
873 {
874 /* Just leave it as null and pray for happiness (as promised).
875 * Reminder: Repository::from_address() is allowed to behave this way, in which case we do this;
876 * and it is allowed to yield UB; then we yield UB. */
877 m_rep.m_rep = 0;
878 }
879 } // if (!pool_id_or_0 == 0)
880 else // if (pool_id_or_0 != 0): Found in a SHM pool.
881 {
882 // Operate on bit-field for clarity and probably speed (see class doc header for discussion).
883 m_rep.m_offset_ptr_rep.m_selector_offset_else_raw = rep_t(1);
884 m_rep.m_offset_ptr_rep.m_pool_id = pool_id_or_0;
885 m_rep.m_offset_ptr_rep.m_pool_offset = pool_offset;
886 }
887} // Shm_pool_offset_ptr_data::Shm_pool_offset_ptr_data(const void* p)
888
889template<typename Repository_type, bool CAN_STORE_RAW_PTR>
891 (const Shm_pool_offset_ptr_data&) = default;
892
893template<typename Repository_type, bool CAN_STORE_RAW_PTR>
896{
897 /* The bit encoding for null (all zeros) and offset pointers (MSB=1, pool_id, pool_offset) is identical
898 * regardless of CAN_STORE_RAW_PTR. So we can copy the bits directly for those cases, avoiding the double
899 * lookup that `Shm_pool_offset_ptr_data(src.get())` would incur (resolve to vaddr via to_address(), then
900 * from_address() back to pool_id/offset).
901 *
902 * The only case needing special handling: src is a raw pointer (non-null, MSB=0), and we are offset-only
903 * (!S_CAN_STORE_RAW_PTR). See below. */
904
905 if constexpr(S_CAN_STORE_RAW_PTR)
906 {
907 // offset-only -> raw-allowed: src can only be null or offset (never raw). Both are valid as-is.
908 m_rep.m_rep = src.m_rep.m_rep;
909 }
910 else
911 {
912 // raw-allowed -> offset-only: src can be null, offset, or raw.
913 if (src.is_raw())
914 {
915 /* src is raw (non-null, selector=0). We cannot represent it. We shouldn't be converting from a
916 * raw-allowing, raw pointer to an offset-only pointer, as a successful conversion wouldn't be expected,
917 * and there isn't a use case to do this, that we know of anyway.
918 *
919 * Since we cannot represent it, we store null as advertised. (This is consistent with how the void* ctor
920 * handles not-in-a-pool addresses, if that could even be detected, when !S_CAN_STORE_RAW_PTR.)
921 *
922 * (One option -- which would mean changing our contract -- would be to essentially delegate to
923 * the void*-taking ctor, giving it src.get(). That is try Repository::from_address(); if it yields
924 * pool_id=0 then save null anyway; but otherwise yay, we found our representation as an offset-ptr after all.
925 * However that only makes our contract weirdly non-deterministic. This raises a question: why does
926 * the void*-taking ctor do what it does then? Answer: It is different, because it has only a raw ptr
927 * to begin with; it *must* do from_address() in the first place; from_address() is an outside force;
928 * if it happens to not explode, then we might as well also not-explode while formally promising and delivering
929 * UB. Here, on the other hand, we don't have to call any such thing: We have `src` already as a
930 * Shm_pool_offset_ptr_data, same class template as *this; and src.is_raw() is a misuse of the API.) */
931 m_rep.m_rep = 0;
932 }
933 else
934 {
935 m_rep.m_rep = src.m_rep.m_rep; // null or offset pointer: bits are valid as-is.
936 }
937 }
938} // Shm_pool_offset_ptr_data::Shm_pool_offset_ptr_data(cross-type copy ctor)
939
940template<typename Repository_type, bool CAN_STORE_RAW_PTR>
943 = default;
944
945template<typename Repository_type, bool CAN_STORE_RAW_PTR>
947{
948 // See the void* ctor and/or the class doc header; it should explain the following pretty well. Keeping cmnts light.
949
950 static_assert(S_CAN_STORE_RAW_PTR,
951 "This helper method is meaningless, unless it is possible to store raw vaddrs. "
952 "Granted, we are static, so it would still work algorithmically speaking; but "
953 "this way protects from trying to call us in unqualified form for the wrong type at least.");
954
955#if 0 // Avoid the perf hit even from an assert(). Could enable when debugging perhaps.
956 assert((rep.m_offset_ptr_rep.m_selector_offset_else_raw == rep_t(0))
957 && "Precondition to helper method = the selector bit <=> raw.");
958#endif
959
960 if (rep.m_raw_ptr_rep.m_ext_sign_msb != rep_t(0))
961 {
962 rep.m_raw_ptr_rep.m_selector_offset_else_raw = rep_t(1);
963 }
964 return reinterpret_cast<void*>(rep.m_rep);
965}
966
967template<typename Repository_type, bool CAN_STORE_RAW_PTR>
969{
970 /* See the void* ctor and/or the class doc header; it should explain the following pretty well. Keeping cmnts light.
971 *
972 * Reminder: Pre-condition is: rep=/=zero, meaning we're not a null. */
973
974 // Operate on bit-field for clarity and probably speed (see class doc header for discussion).
975 if constexpr(S_CAN_STORE_RAW_PTR)
976 {
977 if (rep.m_offset_ptr_rep.m_selector_offset_else_raw == rep_t(0))
978 {
979 return get_as_raw(rep);
980 } // if (rep.m_offset_ptr_rep.m_selector_offset_else_raw == rep_t(0))
981 // else if (rep.m_offset_ptr_rep.m_selector_offset_else_raw != rep_t(0)): Fall through.
982 } // if constexpr(S_CAN_STORE_RAW_PTR)
983 else // if constexpr(!S_CAN_STORE_RAW_PTR)
984 {
985#if 0 // Avoid the perf hit even from an assert(). Could enable when debugging perhaps.
986 assert((rep.m_offset_ptr_rep.m_selector_offset_else_raw == rep_t(1))
987 && "How'd we reach such a state in 1st place? Constructor shouldn't; increment() should not either.");
988#endif
989 }
990
991 // Per contract: if this yields null (pool not found), we return null. If it yields UB, we yield UB.
992 return Repository::to_address(rep.m_offset_ptr_rep.m_pool_id, rep.m_offset_ptr_rep.m_pool_offset);
993} // Shm_pool_offset_ptr_data::get_if_not_null()
994
995template<typename Repository_type, bool CAN_STORE_RAW_PTR>
997{
998 // We suspect null is common enough to where checking it before any bit/bit-sequence access = overall optimization.
999 return (m_rep.m_rep == rep_t(0)) ? static_cast<void*>(nullptr) : get_if_not_null(m_rep);
1000}
1001
1002template<typename Repository_type, bool CAN_STORE_RAW_PTR>
1004{
1005 if constexpr(S_CAN_STORE_RAW_PTR)
1006 {
1007 // Suppose MSB = 0; if all other bits = 0 then null; if at least one is 1 then raw. Otherwise neither.
1008 return (m_rep.m_offset_ptr_rep.m_selector_offset_else_raw == pool_offset_t(0))
1009 && (m_rep.m_rep != rep_t(0));
1010 }
1011 else
1012 {
1013 return false; // Cannot be raw.
1014 }
1015}
1016
1017template<typename Repository_type, bool CAN_STORE_RAW_PTR>
1019{
1020 return m_rep.m_rep != rep_t(0);
1021}
1022
1023template<typename Repository_type, bool CAN_STORE_RAW_PTR>
1025{
1026 constexpr bool NO_RAW = !S_CAN_STORE_RAW_PTR;
1027 constexpr bool RESULT_EQ = true; // Harder to have a brain-glitch this way, we've found.
1028 constexpr bool RESULT_NE = false;
1029
1030 /* Consult doc header first, then come back here. So: we could just `return get() == other.get()`, and we only
1031 * do not, because we want higher perf than that. However, as we reflected in doc header (which, to be fair,
1032 * in this case is worded how it is *because of* the reasoning in this impl, less so the reverse), our gold
1033 * standard for correctness is indeed `get() == other.get()`, *but* we are allowed to make exceptions when
1034 * doing so (1) would lead to higher performance and (2) is defensible in practical terms (so, something like
1035 * "in this logic branch we might not return `get == other.get()`, but it is OK because <practical reason>").
1036 * So we'll do that kind of reasoning below.
1037 *
1038 * Since get() behavior matters for the "gold standard" of our behavior, we may at points rely on its exact
1039 * impl; and that's fine; but remember one thing in particular: if we are not null (to_bool() == true), and
1040 * !is_raw(), then `get() == Repository::to_address(m_pool_id, m_pool_offset)`, which in turn very specifically
1041 * is:
1042 * - lookup base-vaddr by m_pool_id in (conceptual, actual impls may vary for perf reasons) map
1043 * [not found => undefined behavior (UB) => by our contract we don't care]
1044 * - add base-vaddr + m_pool_offset; return result
1045 * [result being before base-vaddr or past pool-end => *not* UB => we must handle it]
1046 *
1047 * The key question: How would we do better perf-wise than `get() == other.get()`? After all if we can't,
1048 * then might as well just return that. Answer: If x is not null and !x.is_raw() (incidentally always the case
1049 * if NO_RAW), then x.get() involves a map lookup of some kind. Depending on Repository impl (and they do
1050 * quite vary; as of this writing Borrower_shm_pool_collection_repository and Owner_shm_pool_repository are
1051 * the two possibilities in Flow-IPC proper, and certainly their impls are quite different), at a minimum
1052 * some map lookup must occur. Conceivably there can also be locking. In any case, the minimum case of a lock-free
1053 * hash-map lookup *times two* may not sound "that bad," but consider that generally people are used to
1054 * raw pointers, and with raw pointers there is *no* extra calculation; .get() would just yield the numeric
1055 * value of the pointer. A map-lookup is much slower. Moreover we have seen production high-load environments
1056 * in which .get()-due-to-ptr-equality-comparisons showed up as a processor hot spot. (In that particular case
1057 * it was due to a `for(a:b)` loop, `b` being an in-SHM vector; the comparison to b.end() meant many lookups.)
1058 * So it's serious business.
1059 *
1060 * If we can avoid .get() -- or more precisely Repository::to_address() firstly and get_as_raw() as a distant
1061 * second priority -- relying on only a few comparisons instead, then great.
1062 *
1063 * Spoiler alert: In the final analysis (by algorithm inspection, not necessarily empirically), the below
1064 * - reduces to a comparison of integers if !CAN_STORE_RAW_PTR; otherwise:
1065 * - reduces to same if they're indeed numerically equal (surely not uncommon); otherwise:
1066 * - reduces to a handful of 0/bit/equality comparisons in most other situations, by inducing a
1067 * likely-safe exception to the "gold standard";
1068 * - worst-case, in an atypical situation, is similar to `.get() == other.get()`. */
1069
1070 const auto& other_rep = other.m_rep;
1071
1072 if (m_rep.m_rep == other_rep.m_rep)
1073 {
1074 return RESULT_EQ; // Bitwise equal => get()s would be obviously also equal.
1075 }
1076 // else
1077
1078 if constexpr(NO_RAW)
1079 {
1080 return RESULT_NE;
1081 /* Let's prove that is correct. Analyzing possibilities:
1082 * - If we are null (!to_bool()), then other must have equal value for EQ, but it is different, so NE is right.
1083 * - Same but flipped.
1084 * - Hence neither is null; and since NO_RAW, m_pool_id/offset and other.m_pool_id/offset are all
1085 * valid and in-use values. So:
1086 * - If m_pool/offset and other.m_pool_id/offset *were* pairwise equal, then it would result in EQ.
1087 * But `m_rep != other_rep`, so that's not the case. So, result must be NE, unless...
1088 * - ...there is some way for m_pool/offset and other.m_pool_id/offset to *not* be pairwise equal, yet
1089 * for `get() == other.get()`. Is there? Let's contemplate the possibilities. Assume
1090 * they're not pairwise equal, and result is EQ: `get() == other.get()`.
1091 * - If `m_pool_id == other.m_pool_id`, then EQ can only result if `m_pool_offset == other.m_pool_offset`;
1092 * but we've established non-pairwise-equality. So `m_pool_id != other.m_pool_id`.
1093 * - If m_offset and other.m_pool_offset are both within-pool-bounds (not negative, not past respective
1094 * pool sizes), then get() and other.get() are within separate pools. Pools do not overlap.
1095 * Therefore one, or both, of `m_pool_offset`s are outside their respective pool bounds.
1096 * - The answer is yes. E.g., say m_pool_offset is 0, while other.m_pool_offset is the distance, or
1097 * negative distance (depending on whether our pool or `other` pool comes first by vaddr value),
1098 * between our base-vaddr and `other` base-vaddr -- even if that's billions of bytes apart. In that
1099 * case `get() == other.get()` -- EQ -- but we shall return NE.
1100 *
1101 * We consider this to be a reasonable exception to the "gold standard" (`get() == other.get()`) and thus
1102 * in our contract pointed it out. Why? Answer: It is a pathological case. In practice it's difficult to
1103 * conceive of a situation where this would naturally come about without trying to make it happen. It isn't
1104 * 100% inconceivable, but we document it and live with the unlikely bad consequences. It is worth
1105 * not having to do get() here. (Though, at least it would've already correctly returned EQ on bitwise
1106 * equality.) */
1107 } // if constexpr(NO_RAW)
1108 else // if constexpr(CAN_STORE_RAW_PTR)
1109 {
1110 /* The below could be written as follows, but we unwind (in admittedly relatively ugly fashion) ~everything to
1111 * do as little recalculating as possible, making it all very explicit. Overkill? Possibly? Maintenance risk?
1112 * A bit, probably. All in all though might be worthwhile given how paranoid about perf we are trying to be. */
1113#if 0
1114 return ((!to_bool()) || (!other.to_bool()) || (is_raw() == other.is_raw()))
1115 ? RESULT_NE;
1116 : (get() == other.get());
1117#else // The actual code then:
1118 if (m_rep.m_rep == rep_t(0)) { return RESULT_NE; } // `other` is not null.
1119 if (other_rep.m_rep == rep_t(0)) { return RESULT_NE; } // *this is not null.
1120 // else if (neither is null):
1121
1122 const bool is_raw = m_rep.m_offset_ptr_rep.m_selector_offset_else_raw == pool_offset_t(0);
1123 if (is_raw
1124 ==
1125 (other_rep.m_offset_ptr_rep.m_selector_offset_else_raw == pool_offset_t(0)))
1126 {
1127 return RESULT_NE;
1128 }
1129 // else if (neither is null) && (is_raw != other.is_raw())
1130
1131 return (is_raw ? (get_as_raw(m_rep)
1132 ==
1133 Repository::to_address(other_rep.m_offset_ptr_rep.m_pool_id,
1134 other_rep.m_offset_ptr_rep.m_pool_offset))
1135 : (get_as_raw(other_rep) // !is_raw => other.is_raw()
1136 ==
1137 Repository::to_address(m_rep.m_offset_ptr_rep.m_pool_id,
1138 m_rep.m_offset_ptr_rep.m_pool_offset)))
1139 ? RESULT_EQ : RESULT_NE;
1140#endif
1141
1142 /* Let's prove that is correct. Please refer to the `if 0`ed nicely-readable version which is equivalent to
1143 * the longer-but-hopefully-a-bit fast one just above. Analyzing possibilities:
1144 *
1145 * - If we are null, or `other` is null: See bullet points 1-2 in the proof cmnt in the NO_RAW case above. Same.
1146 * So assume neither is null. Hence we are raw or an offset-ptr, as it `other` one of the two.
1147 * - If `is_raw() == other.is_raw()`:
1148 * - If !is_raw(): See the proof above for the NO_RAW case (starting with bullet point 3). Same applies.
1149 * - If is_raw(): Then it's just a regular raw vaddr comparison (except MSB is unconditionally 0 for both,
1150 * but it carries no info, as it's part of the canonial form's filler bits); certainly if they're numerically
1151 * unequal then NE is correct. (This is stronger than the preceding bullet: There is no pathological way
1152 * for NE to be technically wrong.)
1153 * - So assume is_raw() and !other.is_raw() (the reverse could be the case, but by symmetry the same
1154 * reasoning below would apply equally well), and both are non-null.
1155 *
1156 * And in that case we use the (slow) "gold standard": `get() == other.get()`. (It is unwound a bit to avoid
1157 * redundant computing: since is_raw, we can directly use the get_as_raw() part of get(); and since
1158 * !other.is_raw() and non-null, we can directly use the to_address() part of other.get().)
1159 *
1160 * That is correct by definition. QED.
1161 *
1162 * ...but maybe we can do something faster and still okay? Well, is_raw(), so get() is just
1163 * equal to reinterpret_cast<void*>(m_rep) + potentially flip of MSB (get_as_raw()) -- quick enough;
1164 * but there's simply no way to deal with `other` other than calling to_address(other.m_pool_id/offset)
1165 * which involves one of the lookups we were trying to avoid. If we don't want to do that, then:
1166 * Our only other option is the ol' `return NE`. Would that be okay in that any
1167 * false NE would be pathological? Answer: Kind of. It's somewhat similar to the pathological case
1168 * outlined in the NO_RAW comment but 50% less so: One of the sides is a raw address, but the other one
1169 * represents the same vaddr but in a pool. Like maybe they created an offset-pointer to a pool-free
1170 * location and then allocated to create a pool at that "suspected" place and are now checking. It's "thin";
1171 * less thin than the NO_RAW scenario though.
1172 *
1173 * Here's our reasoning to play it safe: We just don't think the scenario of comparing an owner-side
1174 * (CAN_STORE_RAW_PTR) pointer to not-in-SHM versus a pointer to in-SHM is likely enough to need to
1175 * perf-optimize (at which point just doing the definitely-correct thing wins). Copying bytes between the
1176 * two, yes, totally plausible. Why compare them though? E.g., loop-comparing against .end() would be in
1177 * the same container so probably all in-SHM or all on stack/in heap. Or a list<> comparing node vs another
1178 * node... same deal.
1179 *
1180 * Plus at least it's only one to_address() (map lookup) as opposed to two.
1181 *
1182 * @todo Revisit; gather some stats, perhaps under load with diverse production code bases. */
1183 } // else if constexpr(CAN_STORE_RAW_PTR)
1184} // Shm_pool_offset_ptr_data::equals()
1185
1186template<typename Repository_type, bool CAN_STORE_RAW_PTR>
1188{
1189 constexpr bool RESULT_LT = true; // Harder to have a brain-glitch this way, we've found.
1190 constexpr bool RESULT_GE = false;
1191
1192 /* We could just `return get() < other.get()`, and we only do not, because we want
1193 * higher perf than that. However, as we reflected in doc header, our gold
1194 * standard for correctness is indeed `get() == other.get()`, *but* we are allowed to make exceptions when
1195 * doing so (1) would lead to higher performance and (2) is defensible in practical terms.
1196 * So we'll do that kind of reasoning below. However: it turns out that, unlike in equals(), we are able
1197 * to avoid any exceptions for the sake of perf here.
1198 *
1199 * Since get() behavior matters for the "gold standard" of our behavior, we may at points rely on its exact
1200 * impl; and that's fine; but remember one thing in particular: if we are not null (to_bool() == true), and
1201 * !is_raw(), then `get() == Repository::to_address(m_pool_id, m_pool_offset)`, which in turn very specifically
1202 * is:
1203 * - lookup base-vaddr by m_pool_id in (conceptual, actual impls may vary for perf reasons) map
1204 * [not found => undefined behavior (UB) => by our contract we don't care]
1205 * - add base-vaddr + m_pool_offset; return result
1206 * [result being before base-vaddr or past pool-end => *not* UB => we must handle it]
1207 *
1208 * The key question: How would we do better perf-wise than `get() == other.get()`? After all if we can't,
1209 * then might as well just return that. Answer: <similar to what we wrote in equals() in analogous spot>.
1210 *
1211 * If we can avoid .get() -- or more precisely Repository::to_address() firstly and get_as_raw() as a distant
1212 * second priority -- relying on only a few comparisons instead, then great.
1213 *
1214 * Spoiler alert: In the final analysis (by algorithm inspection, not necessarily empirically), the below is
1215 * pretty similar perf-wise to just `return get() < other.get()` except for one likely very-common situation:
1216 * both sides non-null, non-raw (100% of the time if !CAN_STORE_RAW_PTR, most of the time otherwise), and
1217 * in the same SHM-pool. In that very-common situation it reduces to a few 0 checks and a comparison of
1218 * two (in x86-64, 32-bit) integers. */
1219
1220 const auto& other_rep = other.m_rep;
1221 const bool other_is_null = other_rep.m_rep == rep_t(0);
1222
1223 /* First handle/eliminate situations where we are null (!to_bool()) and/or `other` is null. Granted
1224 * `<` comparisons between null and <anything> are probably fairly rare, so it'd be nice to not check for that
1225 * until we have to (all else being equal); but logically it seems hard to avoid doing it first. At least
1226 * a check-for-zero should be quite quick. */
1227
1228 if (m_rep.m_rep == rep_t(0))
1229 {
1230 return other_is_null ? RESULT_GE : RESULT_LT; // null >= null / null < non-null.
1231 }
1232 // else
1233 if (other_is_null) // && *this is not null
1234 {
1235 return RESULT_GE; // non-null >= null.
1236 }
1237 // else neither is null:
1238
1239 /* Next handle/eliminate situations where one or both is a raw pointer (MSB indicates rawness; the other bits
1240 * contain actual vaddr being encoded; MSB in vaddr would be copy of 2nd MSB and carries no real info).
1241 * Happily !CAN_STORE_RAW_PTR means by definition neither one is raw, so that block can be skipped. */
1242
1243 if constexpr(S_CAN_STORE_RAW_PTR)
1244 {
1245 const bool is_raw = m_rep.m_offset_ptr_rep.m_selector_offset_else_raw == pool_offset_t(0);
1246 if (is_raw != (other_rep.m_offset_ptr_rep.m_selector_offset_else_raw == pool_offset_t(0)))
1247 {
1248 /* One is raw; the other is not. No choice but to essentially execute .get() for both sides and compare.
1249 * However we can skip some redundancies based on information we do have and reduce .get() on one side
1250 * to get_as_raw() (possible bit flip but that's it) and the "slow" lookup on the other side. */
1251 return (is_raw ? (get_as_raw(m_rep)
1252 <
1253 Repository::to_address(other_rep.m_offset_ptr_rep.m_pool_id,
1254 other_rep.m_offset_ptr_rep.m_pool_offset))
1255 : (get_as_raw(other_rep) // !is_raw => other.is_raw()
1256 >
1257 Repository::to_address(m_rep.m_offset_ptr_rep.m_pool_id,
1258 m_rep.m_offset_ptr_rep.m_pool_offset)))
1259 ? RESULT_LT : RESULT_GE;
1260 }
1261 // else if (is_raw == other.is_raw()):
1262 if (is_raw)
1263 {
1264 /* Both are raw. That's promising, in that it is tempting to just return `m_rep.m_rep < other_rep.m_rep`
1265 * (compare the bits' numeric values). Unfortunately it is possible that the numeric value of actual
1266 * vaddr get() (by now, get_as_raw()) flips the MSB of .m_rep. If that were true for neither or both sides:
1267 * no problem; we could still just use `m_rep.m_rep < other_rep.m_rep`. It can be true for exactly one side
1268 * too though. Hence we need to do the bit-flip, if indeed 2nd MSB indicates this, for each side first
1269 * and then compare. It should be quick. */
1270 return (get_as_raw(m_rep) < get_as_raw(other_rep)) ? RESULT_LT : RESULT_GE;
1271 }
1272 // else if both are non-null and non-raw (both are offset pointers). Fall through:
1273 }
1274 else // if constexpr(!CAN_STORE_RAW_PTR)
1275 {
1276#if 0 // Avoid the perf hit even from an assert(). Could enable when debugging perhaps.
1277 assert((!is_raw()) && "Cannot be raw if !CAN_STORE_RAW_PTR.");
1278 assert((!other.is_raw()) && "`other` cannot be raw if !CAN_STORE_RAW_PTR.");
1279#endif
1280 }
1281
1282 // To recap: both are non-null and non-raw (both are offset pointers).
1283
1284 if (m_rep.m_offset_ptr_rep.m_pool_id == other_rep.m_offset_ptr_rep.m_pool_id)
1285 {
1286 /* This is probably quite common (certainly not universal though): things being compared tend to live near each
1287 * other, within the same vaddr area (extent, pool). So in many cases the whole thing amounts to:
1288 * two comparisons against zero, [if CAN_STORE_RAW_PTR: 2ish bit computations,] and `integer1 < integer2`. */
1289 return (m_rep.m_offset_ptr_rep.m_pool_offset < other_rep.m_offset_ptr_rep.m_pool_offset)
1290 ? RESULT_LT : RESULT_GE;
1291 }
1292 // else
1293
1294 return (Repository::to_address(m_rep.m_offset_ptr_rep.m_pool_id, m_rep.m_offset_ptr_rep.m_pool_offset)
1295 <
1296 Repository::to_address(other_rep.m_offset_ptr_rep.m_pool_id, other_rep.m_offset_ptr_rep.m_pool_offset))
1297 ? RESULT_LT : RESULT_GE;
1298} // Shm_pool_offset_ptr_data::less_than()
1299
1300template<typename Repository_type, bool CAN_STORE_RAW_PTR>
1301bool
1303{
1304 constexpr bool RESULT_GT = true; // Harder to have a brain-glitch this way, we've found.
1305 constexpr bool RESULT_LE = false;
1306
1307 /* This impl is similar to less_than(), so please grok that guy; then come back here. Keeping comments light.
1308 *
1309 * Code reuse (beyond get_as_raw() reuse) is not impossible (maybe a template param for whether we'll do
1310 * less-than or greater-than), but seems the resulting code would just be harder to understand, and the
1311 * gains for easier maintenance/reduced bugginess would be marginal. */
1312
1313 const auto& other_rep = other.m_rep;
1314 const bool other_is_null = other_rep.m_rep == rep_t(0);
1315
1316 if (m_rep.m_rep == rep_t(0))
1317 {
1318 return RESULT_LE; // null <= null / null <= non-null.
1319 }
1320 // else
1321 if (other_is_null) // && *this is not null
1322 {
1323 return RESULT_GT; // non-null > null.
1324 }
1325 // else neither is null:
1326
1327 if constexpr(S_CAN_STORE_RAW_PTR)
1328 {
1329 const bool is_raw = m_rep.m_offset_ptr_rep.m_selector_offset_else_raw == pool_offset_t(0);
1330 if (is_raw != (other_rep.m_offset_ptr_rep.m_selector_offset_else_raw == pool_offset_t(0)))
1331 {
1332 return (is_raw ? (get_as_raw(m_rep)
1333 >
1334 Repository::to_address(other_rep.m_offset_ptr_rep.m_pool_id,
1335 other_rep.m_offset_ptr_rep.m_pool_offset))
1336 : (get_as_raw(other_rep) // !is_raw => other.is_raw()
1337 <
1338 Repository::to_address(m_rep.m_offset_ptr_rep.m_pool_id,
1339 m_rep.m_offset_ptr_rep.m_pool_offset)))
1340 ? RESULT_GT : RESULT_LE;
1341 }
1342 // else if (is_raw == other.is_raw()):
1343 if (is_raw)
1344 {
1345 return (get_as_raw(m_rep) > get_as_raw(other_rep)) ? RESULT_GT : RESULT_LE;
1346 }
1347 // else Fall through:
1348 }
1349 else // if constexpr(!CAN_STORE_RAW_PTR)
1350 {
1351#if 0 // Avoid the perf hit even from an assert(). Could enable when debugging perhaps.
1352 assert((!is_raw()) && "Cannot be raw if !CAN_STORE_RAW_PTR.");
1353 assert((!other.is_raw()) && "`other` cannot be raw if !CAN_STORE_RAW_PTR.");
1354#endif
1355 }
1356
1357 // To recap: both are non-null and non-raw (both are offset pointers).
1358
1359 if (m_rep.m_offset_ptr_rep.m_pool_id == other_rep.m_offset_ptr_rep.m_pool_id)
1360 {
1361 return (m_rep.m_offset_ptr_rep.m_pool_offset > other_rep.m_offset_ptr_rep.m_pool_offset)
1362 ? RESULT_GT : RESULT_LE;
1363 }
1364 // else
1365
1366 return (Repository::to_address(m_rep.m_offset_ptr_rep.m_pool_id, m_rep.m_offset_ptr_rep.m_pool_offset)
1367 >
1368 Repository::to_address(other_rep.m_offset_ptr_rep.m_pool_id, other_rep.m_offset_ptr_rep.m_pool_offset))
1369 ? RESULT_GT : RESULT_LE;
1370} // Shm_pool_offset_ptr_data::greater_than()
1371
1372template<typename Repository_type, bool CAN_STORE_RAW_PTR>
1375{
1376 static_assert(std::is_signed_v<diff_t>, "If diff_t is unsigned, we cannot properly subtract pointers.");
1377
1378 using wide_offset_t = std::make_signed_t<rep_t>;
1379 using ret_t = diff_t; // Explicitly convert to this, as we return (for clarity at least).
1380
1381 /* The below is written as-if we want the distance-from result in units of sizeof(T). Currently this class
1382 * (notably our complementary increment()) conventionally deals with byte distances, so we just set
1383 * T=byte. Our caller can do the division by sizeof(T) as desired. The code below would support either
1384 * way without any perf penalty, so why not? (Just don't forget `/ sizeof(T)` when manually doing integer
1385 * arithemtic; we do avoid that below where possible.) */
1386 using T = uint8_t;
1387
1388 // Just syntactic sugar.
1389 constexpr auto as_t_ptr = [](const void* ptr) constexpr -> auto { return static_cast<const T*>(ptr); };
1390 constexpr const T* T_NULLPTR = nullptr;
1391
1392 /* We could just `return as_t_ptr(get()) - as_t_ptr(other.get())`, and we only do not, because we want
1393 * higher perf than that. For pithiness/to avoid redundancy we don't explain in full detail further except to say:
1394 *
1395 * This impl is similar in its strategy to less_than(), so please grok that guy; then come back here.
1396 * Keeping comments light accordingly.
1397 *
1398 * That said, as with less/greater_than(), there are pithier ways of getting the same answer while
1399 * still getting the benefit of the main perf-saving case (where *this and `other` are both storing
1400 * pool ID/offset pairs, and the pool IDs are the same, and we can thus return essentially offset1 minus offset2).
1401 * Nevertheless we go with the less/greater_than() approach, where we do a ~full unwind of the logical
1402 * possibilities explicitly, skipping any redundant checks/arithmetic. As with less/greater_than(),
1403 * it is perhaps questionable whether this is appreciably (or at all) faster (though it's probably not appreciably
1404 * slower at least) than the pithier approaches. Bottom line: If less/greater_than() do it like this,
1405 * then we should too. (If they are changed not to, then this should be as well. Also consider equals().)
1406 *
1407 * Orthgonally: Tactically: in the various cases below, if it is (all else being equal, perf-wise particularly)
1408 * possible to make the compiler itself generate code for `a - b`, where `a` and `b` are both `const T*`, then
1409 * it is both pithy and correct by definition. Better (certainly no worse) than reinterpret_cast<>ing all-over. */
1410
1411 if (m_rep.m_rep == rep_t(0))
1412 {
1413 /* Let compiler do whatever it does for `(uint8_t*)0 - (uint8_t*)p`. Perf-wise `other` hasn't been looked-at
1414 * yet, so other.get() is fair; and the only extra thing we've done is the this->m_rep=zero check. */
1415 return ret_t(T_NULLPTR - as_t_ptr(other.get()));
1416 }
1417 // else
1418 const auto& other_rep = other.m_rep;
1419 if (other_rep.m_rep == rep_t(0)) // && *this is not null
1420 {
1421 // Same deal for the reverse case, plus we already know *this is not null.
1422 return ret_t(as_t_ptr(get_if_not_null(m_rep)) - T_NULLPTR);
1423 }
1424 // else neither is null:
1425 if constexpr(S_CAN_STORE_RAW_PTR)
1426 {
1427 const bool is_raw = m_rep.m_offset_ptr_rep.m_selector_offset_else_raw == pool_offset_t(0);
1428 if (is_raw != (other_rep.m_offset_ptr_rep.m_selector_offset_else_raw == pool_offset_t(0)))
1429 {
1430 return ret_t(is_raw ? (as_t_ptr(get_as_raw(m_rep))
1431 -
1432 as_t_ptr(Repository::to_address(other_rep.m_offset_ptr_rep.m_pool_id,
1433 other_rep.m_offset_ptr_rep.m_pool_offset)))
1434 : (as_t_ptr(Repository::to_address(m_rep.m_offset_ptr_rep.m_pool_id,
1435 m_rep.m_offset_ptr_rep.m_pool_offset))
1436 - // !is_raw => other.is_raw()
1437 as_t_ptr(get_as_raw(other_rep))));
1438 }
1439 // else if (is_raw == other.is_raw()):
1440 if (is_raw)
1441 {
1442 return ret_t(as_t_ptr(get_as_raw(m_rep)) - as_t_ptr(get_as_raw(other_rep)));
1443 }
1444 // else if both are non-null and non-raw (both are offset pointers). Fall through:
1445 }
1446 else // if constexpr(!CAN_STORE_RAW_PTR)
1447 {
1448#if 0 // Avoid the perf hit even from an assert(). Could enable when debugging perhaps.
1449 assert((!is_raw()) && "Cannot be raw if !CAN_STORE_RAW_PTR.");
1450 assert((!other.is_raw()) && "`other` cannot be raw if !CAN_STORE_RAW_PTR.");
1451#endif
1452 }
1453
1454 // To recap: both are non-null and non-raw (both are offset pointers).
1455
1456 if (m_rep.m_offset_ptr_rep.m_pool_id == other_rep.m_offset_ptr_rep.m_pool_id)
1457 {
1458 return ret_t((wide_offset_t(m_rep.m_offset_ptr_rep.m_pool_offset)
1459 - wide_offset_t(other_rep.m_offset_ptr_rep.m_pool_offset))
1460 / wide_offset_t(sizeof(T))); // Signed division! (Unsigned sizeof would break negatives, sizeof>1.)
1461 /* (^-- Might as well convert (from narrower signed pool_offset_t) to the wider signed wide_offset_t to first get
1462 * a clean, non-*erflowing wide_offset_t result. We then still convert the result back to ret_t=diff_t -- narrow
1463 * again -- to return, because currently that's what Shm_pool_offset_ptr wants from us (for its ptr-minus-ptr op).
1464 * If we wanted to return a wider type, like the standard ptrdiff_t (currently = wide_offset_t), then ret_t
1465 * could be changed to alias to that, and this would still work -- now sans possible *erflow. Is there a perf
1466 * penalty to the converting? Probably not much given our target arch.) */
1467 }
1468 // else
1469
1470 return ret_t(as_t_ptr(Repository::to_address(m_rep.m_offset_ptr_rep.m_pool_id,
1471 m_rep.m_offset_ptr_rep.m_pool_offset))
1472 -
1473 as_t_ptr(Repository::to_address(other_rep.m_offset_ptr_rep.m_pool_id,
1474 other_rep.m_offset_ptr_rep.m_pool_offset)));
1475} // Shm_pool_offset_ptr_data::distance_from()
1476
1477template<typename Repository_type, bool CAN_STORE_RAW_PTR>
1479{
1480 static_assert(std::is_signed_v<diff_t>, "If diff_t is unsigned, we cannot really decrement pointers.");
1481
1482 // Operate on bit-field for clarity and probably speed (see class doc header for discussion).
1483 if constexpr(S_CAN_STORE_RAW_PTR)
1484 {
1485 if (m_rep.m_offset_ptr_rep.m_selector_offset_else_raw == 0)
1486 {
1487 /* Subtlety: m_rep may be 0, meaning we are not holding a raw pointer per se (by our classification)
1488 * but rather represent nullptr. However: this is allowed with native pointers (dereferencing the
1489 * result is another matter); for instance `uint8_t* x = 0; x += 4;` will make it hold address 0x4.
1490 * So we let it happen.
1491 *
1492 * Subtlety: One wonders if doing += or -= ops on native pointers keeps canonical-form rules in mind;
1493 * so if one overflows the lower 48 bits, perhaps x86-64 arch will avoid touching the nearby
1494 * extended-sign bits. Experimentation, backed by docs, shows that is not the case: It simply does
1495 * the += or -= op on the underlying uint64_t. Of course dereferencing the result is going to blow up
1496 * (processor exception => SEGV-type-thing), but that's beside the point. So we do the same thing.
1497 *
1498 * Related subtlety: Our += is identical to native pointer +=; the only possible difference occurs in that
1499 * we might flip ->m_selector_offset_else_raw to 1, if they're doing an op on the outside edges of the
1500 * range. This would turn us into an offset pointer; so a subsequent get() (which is not a deref yet!)
1501 * would behave unpredictably; it could return nullptr, or it could accidentally encode a real pool ID
1502 * and thus yield some real-ish address instead of the right thing (which, granted, itself is something
1503 * unreal). A native get() in that state would return the "right thing." So we have two choices
1504 * for what do after the `m_rep +=`. 1, we could force-clear the MSB (shift left, shift right).
1505 * This would yield, possibly, the "right thing" in subsequent get() (which flips the MSB if the
1506 * 2nd MSB is 1, which would probably be 1 in this scenario -- though not necessarily depending on
1507 * magnitude of `bytes`). Else, 2, we could do nothing. This would hit the aforementioned
1508 * non-deterministic behavior, wherein get() would take us to be an offset pointer. One can make
1509 * a case for either. I (ygoldfel) ultimately decided to do (2). The motivation: in the by-far-mainstream
1510 * case, where no one is doing anything funky, it has higher performance. The defense:
1511 * Performing overflowing pointer arithmetic is not disallowed (meaning it shouldn't lead to undefined
1512 * behavior) in and of itself; and reading a resulting pointer value afterwards shouldn't lead to
1513 * any exception or crash; but counting on any particular numeric value -- honestly I haven't tried to
1514 * absolutely confirm this formally in terms of the standard but just intuitively let's be real here --
1515 * is not in the cards. Our impl doesn't crash anything here, or in subsequent get(), but any code
1516 * counting on some kind of particular resulting address after overflowing a pointer... shouldn't. Nor
1517 * is it useful in any conceivable to me way. So for performance's sake this is defensible. */
1518 m_rep.m_rep += bytes;
1519 return;
1520 } // if (raw_ptr_rep->m_selector_offset_else_raw == 0)
1521 // else: Fall through:
1522 } // if constexpr(S_CAN_STORE_RAW_PTR)
1523 else
1524 {
1525 if (m_rep.m_rep == rep_t(0)) // Recall that !S_CAN_STORE_RAW_PTR.
1526 {
1527 /* As advertised remain null. We have no choice; we cannot express this any other way. We could assert(),
1528 * but again we promised to behave -- ill-advised or not we stick to the promise. */
1529 return;
1530 }
1531 // else
1532
1533#if 0 // Avoid the perf hit even from an assert(). Could enable when debugging perhaps.
1534 assert((m_rep.m_offset_ptr_rep.m_selector_offset_else_raw == rep_t(1))
1535 && "How'd we reach such a state in 1st place? Constructor shouldn't; increment() should not either.");
1536#endif
1537 }
1538
1539 m_rep.m_offset_ptr_rep.m_pool_offset += bytes; // Remember this may be negative (or 0).
1540 /* - This might have overflowed; similarly to the above "defense" we can state that this will cause to crash
1541 * here or in get(), and no guarantees can be made or should be expected as to the numeric value of get()
1542 * after such an operation.
1543 * - It might have gone out of bounds of a pool. That is it might be negative; or positive but exceeding
1544 * the pool size; this is discussed in #pool_offset_t doc header. The conclusion based on that explanation:
1545 * We should indeed let it happen.
1546 * - It might be entirely Kosher; so yay then. */
1547} // Shm_pool_offset_ptr_data::increment()
1548
1549template<typename Repository_type, bool CAN_STORE_RAW_PTR>
1550std::ostream& operator<<(std::ostream& os,
1552{
1554 using boost::io::ios_all_saver;
1555
1556 if (val.m_rep.m_rep == rep_t(0))
1557 {
1558 return os << "null";
1559 }
1560 // else
1561
1562 if constexpr(CAN_STORE_RAW_PTR)
1563 {
1564 if (!val.m_rep.m_offset_ptr_rep.m_selector_offset_else_raw)
1565 {
1566 ios_all_saver saver(os); // Revert std::hex/etc. soon.
1567 return os << "ext_sign_bit[" << val.m_rep.m_raw_ptr_rep.m_ext_sign_msb << "]... val_bits[0x"
1568 << std::hex << val.m_rep.m_raw_ptr_rep.m_val_bits << "]@" << val.get();
1569 // @todo Slight perf impact: val.get() has some redundancy to above selector bit check.
1570 }
1571 // else: Fall through:
1572 }
1573 return os << "pool_id[" << val.m_rep.m_offset_ptr_rep.m_pool_id
1574 << "]+[" << val.m_rep.m_offset_ptr_rep.m_pool_offset << "]@" << val.get();
1575} // operator<<(ostream, Shm_pool_offset_ptr_data)
1576
1577} // namespace ipc::shm::arena_lend::detail
1578
1579#if IPC_SHM_ARENA_LEND_DETAIL_GCC_COMPILER
1580# pragma GCC diagnostic pop // See above.
1581#endif
1582
1583#undef IPC_SHM_ARENA_LEND_DETAIL_GCC_COMPILER
Parameterization-independent aspects of Shm_pool_offset_ptr_data segregated in non-template base.
static constexpr size_t max_pool_size()
The max size of a SHM-pool such that any offset into it – including the one-past-the-last-byte offset...
static std::once_flag s_pool_id_shm_region_init_flag
Used with std::call_once() to ensure s_pool_id_shm_region_or_none is initialized no more than once.
int32_t diff_t
Analogous to difference_type in STL containers (but uses our naming conventions).
detail::pool_id_t pool_id_t
Pool ID type alias. Please see detail::pool_id_t doc header for discussion as to the chosen bit width...
static std::optional< boost::interprocess::named_mutex > s_pool_id_mutex_or_none
Initialized no more than once (in this process) by generate_pool_id(), a handle to a named-mutex prot...
static bipc::mapped_region s_pool_id_shm_region_or_none
Initialized no more than once (in this process) by generate_pool_id(), a handle to tiny SHM region st...
uint64_t rep_t
The unsigned integer type used to store Shm_pool_offset_ptr_data pointer payloads.
static pool_id_t generate_pool_id()
Returns a heretofore-unused pool ID so as to be used to identify (in a Shm_pool_offset_ptr_data::Repo...
static bipc::shared_memory_object s_pool_id_shm_obj_or_none
The SHM-object (pool) mapped by s_pool_id_shm_region_or_none.
Shm_pool_offset_ptr_data & operator=(const Shm_pool_offset_ptr_data &src)
Assignment.
Offset_ptr_rep m_offset_ptr_rep
The bits viewed in the offset-ptr form.
bool less_than(Shm_pool_offset_ptr_data other) const
Returns true if and only if get() < other.get().
bool greater_than(Shm_pool_offset_ptr_data other) const
Returns true if and only if get() > other.get().
Raw_ptr_rep m_raw_ptr_rep
The bits viewed in the raw-ptr form.
diff_t distance_from(Shm_pool_offset_ptr_data other) const
Returns (essentially) get() minus other.get() in bytes; so (assuming no overflow issues) the value N ...
void increment(diff_t bytes) noexcept
Increments *this by a number of bytes (which can be positive, negative, or zero).
Repository_type Repository
Short-hand for template parameter Repository_type; may be useful for generic programming.
bool equals(Shm_pool_offset_ptr_data other) const
Returns true if and only if get() == other.get(), albeit with certain acceptable exceptions and a bet...
void * get() const
Retrieves the pointer to the object, if there is one stored; otherwise nullptr.
bool to_bool() const
Returns false if and only if get() == nullptr (assuming *this is valid; otherwise formally undefined ...
static constexpr bool S_CAN_STORE_RAW_PTR
Short-hand for template parameter CAN_STORE_RAW_PTR; may be useful for generic programming.
Shm_pool_offset_ptr_data(const Shm_pool_offset_ptr_data &src)
Copy constructor.
friend std::ostream & operator<<(std::ostream &os, Shm_pool_offset_ptr_data< Repository_type2, CAN_STORE_RAW_PTR2 > val)
Friend of this class.
static void * get_as_raw(Representation rep)
Helper, surely inlined with any decent optimizer, that returns what get() would return if m_rep equal...
static void * get_if_not_null(Representation rep)
Helper a-la get_as_raw() but given only the pre-condition that rep is not zero (does not represent nu...
bool is_raw() const
Returns whether the offset pointer holds a non-null, raw pointer; as opposed to an offset pointer or ...
Segregated private stuff for ipc::shm::arena_lend.
uint32_t pool_id_t
Pool ID type.
std::ostream & operator<<(std::ostream &os, Shm_pool_offset_ptr_data< Repository_type, CAN_STORE_RAW_PTR > val)
Prints string representation of the given Shm_pool_offset_ptr_data to the given ostream.
A bit-field overlaid over any rep_t, this represents a pointer with the selector MSB indicating a poo...
pool_id_t m_pool_id
If and only if m_selector_offset_else_raw: The pool ID.
pool_offset_t m_pool_offset
If and only if m_selector_offset_else_raw: The offset within pool IDed by m_pool_id.
rep_t m_selector_offset_else_raw
Either 0 (raw or null pointer; do not use the other bit members) or 1 (offset pointer).
A bit-field overlaid over any rep_t, this represents a pointer with the selector MSB indicating a ra-...
rep_t m_ext_sign_bits_except_msb
If !m_selector_offset_else_raw, and the entire value is not all-zero-bits: m_ext_sign_msb repeated.
rep_t m_selector_offset_else_raw
Either 0 (raw or null pointer; if all other bits are also 0, then do not use the other bit members) o...
rep_t m_ext_sign_msb
If !m_selector_offset_else_raw, and the entire value is not all-zero-bits: The most significant,...
rep_t m_val_bits
If !m_selector_offset_else_raw, and the entire value is not all-zero-bits: m_ext_sign_msb repeated ye...