ZXFoundation™ 26h2
Loading...
Searching...
No Matches
table.cxxm
1/// SPDX-License-Identifier: Apache-2.0
2/// @file zxfoundation/scoms/scoms_table.cxxm
3/// @brief SCOMS — scoms_table<T, N>: unified, generation-counted,
4/// reference-counted kernel object table.
5
6export module zxfoundation.scoms.table;
7import zxfoundation.base.types;
8import zxfoundation.base.typestate;
9import zxfoundation.scoms.kobject.types;
10import zxfoundation.scoms.kobject.base;
11import zxfoundation.scoms.kobject.ref;
12import zxfoundation.scoms.metadata.core;
13import zxfoundation.sync.qspinlock.core;
14import zxfoundation.sync.lockable;
15import arch.s390x.cpu.irq;
16import lib.error;
17import lib.stl;
18
19#define __zxmacro_want_symalias
20#include macro_extensions
21
22auto allocate(u64) noexcept -> void* symalias_reference(kmalloc);
23auto free(void*) noexcept -> void symalias_reference(kfree);
24
25export {
26
27namespace zxfoundation::scoms {
28
29 using base::lifecycle_state;
30 using std::atomic;
31 using std::memory_order;
32
33 /// @brief Generic, generation-counted kernel object table.
34 /// @tparam T Concrete kernel object type. Must have a `kobject ko`
35 /// field at offset 0.
36 /// @tparam N Maximum number of objects (static capacity).
37 template <typename T, u32 N>
38 class scoms_table {
39 static_assert(__builtin_offsetof(T, ko) == 0,
40 "T::ko must be at offset 0 for SCOMS scoms_table");
41
42 static constexpr u32 BLOCK_SIZE = 256U;
43 static constexpr u32 BLOCK_COUNT = (N + BLOCK_SIZE - 1U) / BLOCK_SIZE;
44 T** m_slot_blocks[BLOCK_COUNT]{};
45 u32* m_next_blocks[BLOCK_COUNT]{};
46 u32* m_generation_blocks[BLOCK_COUNT]{};
47 u32 m_free_head{0};
48 atomic<u32> m_count{0};
49 sync::qspinlock::qspinlock m_table_lock{};
50 bool m_initialized{false};
51
52 /// @brief The kobject::kobject_type that this table manages.
53 kobject::kobject_type m_type{kobject::kobject_type::none};
54 inline static scoms_table* s_instance{nullptr};
55
56 static auto reclaim_object(kobject::bobject& header) noexcept -> void {
57 if (!s_instance) __builtin_trap();
58 auto& object = *reinterpret_cast<T*>(&header);
59 s_instance->reclaim_retired(object);
60 }
61
62 auto reclaim_retired(T& object) noexcept -> void {
63 const u32 id = object.ko.id;
64 if (id >= N) __builtin_trap();
65 const sync::irq_lock_guard guard{m_table_lock};
66 if (object_ptr(id) != nullptr) __builtin_trap();
67 object_core::complete_reclaim(object.ko);
68 object.ko.id = kobject::KOBJECT_ID_INVALID;
69 object.~T();
70 free(&object);
71 next_ptr(id) = m_free_head;
72 m_free_head = id;
73 }
74
75 [[nodiscard]] auto slot_ptr(u32 id) noexcept -> T*& {
76 return m_slot_blocks[id / BLOCK_SIZE][id % BLOCK_SIZE];
77 }
78
79 [[nodiscard]] auto object_ptr(u32 id) noexcept -> T* {
80 auto* block = m_slot_blocks[id / BLOCK_SIZE];
81 return block ? block[id % BLOCK_SIZE] : nullptr;
82 }
83
84 [[nodiscard]] auto next_ptr(u32 id) noexcept -> u32& {
85 return m_next_blocks[id / BLOCK_SIZE][id % BLOCK_SIZE];
86 }
87
88 [[nodiscard]] auto generation_ptr(u32 id) noexcept -> u32& {
89 return m_generation_blocks[id / BLOCK_SIZE][id % BLOCK_SIZE];
90 }
91
92 [[nodiscard]] auto ensure_slot_block(u32 block) noexcept -> bool {
93 if (m_next_blocks[block]) return true;
94 auto* next = static_cast<u32*>(allocate(
95 sizeof(u32) * BLOCK_SIZE));
96 auto* generation = static_cast<u32*>(allocate(
97 sizeof(u32) * BLOCK_SIZE));
98 if (!next || !generation) return false;
99 for (u32 i = 0; i < BLOCK_SIZE; ++i) {
100 next[i] = ~0U;
101 generation[i] = 0U;
102 }
103 m_next_blocks[block] = next;
104 m_generation_blocks[block] = generation;
105 return true;
106 }
107
108 [[nodiscard]] auto ensure_object_block(u32 block) noexcept -> bool {
109 if (m_slot_blocks[block]) return true;
110 auto* slots = static_cast<T**>(allocate(
111 sizeof(T*) * BLOCK_SIZE));
112 if (!slots) return false;
113 for (u32 i = 0; i < BLOCK_SIZE; ++i) slots[i] = nullptr;
114 m_slot_blocks[block] = slots;
115 return true;
116 }
117
118 public:
119 /// @brief Initialize the table.
120 /// @param type The kobject::kobject_type for objects in this table.
121 auto init(kobject::kobject_type type) noexcept -> void {
122 s_instance = this;
123 install_bobject_finalizer(object_core::finalize_last_put);
124 m_type = type;
125
126 // Build the free-list.
127 for (u32 i = 0; i < N; ++i) {
128 if (!ensure_slot_block(i / BLOCK_SIZE)) return;
129 next_ptr(i) = (i + 1 < N) ? (i + 1) : ~0U;
130 }
131
132 m_free_head = 0;
133 m_count.store(0, memory_order::relaxed);
134 m_initialized = true;
135 }
136
137 /// @brief Allocate a slot, returning an embryo ref.
138 /// @return An embryo ref on success, or kernel_error if the
139 /// table is full.
140 [[nodiscard]] auto alloc(kobject::kobject_type requested_type = kobject::kobject_type::none) noexcept
141 -> std::expected<kobject::robject<T, lifecycle_state::embryo>, lib::kernel_error>
142 {
143 if (!m_initialized) [[unlikely]]
144 return std::unexpected(
145 lib::kernel_error::from_generic(lib::generic_error::uninitialized));
146
147 const sync::irq_lock_guard guard{m_table_lock};
148
149 if (m_free_head == ~0U)
150 return std::unexpected(
151 lib::kernel_error::from_memory(lib::memory_error::out_of_memory));
152
153 const u32 slot = m_free_head;
154 m_free_head = next_ptr(slot);
155 next_ptr(slot) = ~0U;
156
157 if (!ensure_object_block(slot / BLOCK_SIZE)) {
158 next_ptr(slot) = m_free_head;
159 m_free_head = slot;
160 return std::unexpected(lib::kernel_error::from_memory(
161 lib::memory_error::out_of_memory));
162 }
163 auto* obj_ptr = static_cast<T*>(allocate(sizeof(T)));
164 if (!obj_ptr) {
165 next_ptr(slot) = m_free_head;
166 m_free_head = slot;
167 return std::unexpected(lib::kernel_error::from_memory(
168 lib::memory_error::out_of_memory));
169 }
170 std::construct_at(obj_ptr);
171 m_slot_blocks[slot / BLOCK_SIZE][slot % BLOCK_SIZE] = obj_ptr;
172 T& obj = *obj_ptr;
173 obj.ko.generation = ++generation_ptr(slot); // ABA prevention.
174 const auto object_type = requested_type == kobject::kobject_type::none
175 ? m_type : requested_type;
176 obj.ko.initialize(slot, object_type);
177 auto metadata_result = object_core::register_object(obj.ko);
178 if (!metadata_result) {
179 next_ptr(slot) = m_free_head;
180 m_free_head = slot;
181 obj.~T();
182 free(obj_ptr);
183 m_slot_blocks[slot / BLOCK_SIZE][slot % BLOCK_SIZE] = nullptr;
184 return std::unexpected(metadata_result.error());
185 }
186 auto reclaimer_result = object_core::set_reclaimer(obj.ko, reclaim_object);
187 if (!reclaimer_result) {
188 discard_value object_core::unregister_object(obj.ko);
189 next_ptr(slot) = m_free_head;
190 m_free_head = slot;
191 obj.~T();
192 free(obj_ptr);
193 m_slot_blocks[slot / BLOCK_SIZE][slot % BLOCK_SIZE] = nullptr;
194 return std::unexpected(reclaimer_result.error());
195 }
196
197 discard_value m_count.fetch_add(1, memory_order::relaxed);
198
199 return kobject::robject<T, lifecycle_state::embryo>{&obj};
200 }
201
202 /// @brief Allocate a specific slot index, returning an embryo ref.
203 /// @param[in] id Slot index to claim. Must be < N and currently free.
204 [[nodiscard]] auto alloc_at(u32 id) noexcept
205 -> std::expected<kobject::robject<T, lifecycle_state::embryo>, lib::kernel_error>
206 {
207 if (!m_initialized) [[unlikely]]
208 return std::unexpected(
209 lib::kernel_error::from_generic(lib::generic_error::uninitialized));
210
211 if (id >= N)
212 return std::unexpected(
213 lib::kernel_error::from_generic(lib::generic_error::invalid_arg));
214
215 const sync::irq_lock_guard guard{m_table_lock};
216
217 if (object_ptr(id) != nullptr)
218 return std::unexpected(
219 lib::kernel_error::from_locking(
220 lib::locking_error::already_locked));
221
222 if (m_free_head == ~0U)
223 return std::unexpected(
224 lib::kernel_error::from_memory(lib::memory_error::out_of_memory));
225
226 if (m_free_head == id) {
227 m_free_head = next_ptr(id);
228 } else {
229 u32 prev = m_free_head;
230 while (prev != ~0U && next_ptr(prev) != id)
231 prev = next_ptr(prev);
232 if (prev == ~0U) {
233 return std::unexpected(
234 lib::kernel_error::from_locking(
235 lib::locking_error::already_locked));
236 }
237 next_ptr(prev) = next_ptr(id);
238 }
239 next_ptr(id) = ~0U;
240
241 if (!ensure_object_block(id / BLOCK_SIZE)) {
242 next_ptr(id) = m_free_head;
243 m_free_head = id;
244 return std::unexpected(lib::kernel_error::from_memory(
245 lib::memory_error::out_of_memory));
246 }
247 auto* obj_ptr = static_cast<T*>(allocate(sizeof(T)));
248 if (!obj_ptr) {
249 next_ptr(id) = m_free_head;
250 m_free_head = id;
251 return std::unexpected(lib::kernel_error::from_memory(
252 lib::memory_error::out_of_memory));
253 }
254 new (obj_ptr) T{};
255 m_slot_blocks[id / BLOCK_SIZE][id % BLOCK_SIZE] = obj_ptr;
256 T& obj = *obj_ptr;
257 obj.ko.generation = ++generation_ptr(id);
258 obj.ko.initialize(id, m_type);
259 auto metadata_result = object_core::register_object(obj.ko);
260 if (!metadata_result) {
261 next_ptr(id) = m_free_head;
262 m_free_head = id;
263 obj.~T();
264 free(obj_ptr);
265 m_slot_blocks[id / BLOCK_SIZE][id % BLOCK_SIZE] = nullptr;
266 return std::unexpected(metadata_result.error());
267 }
268 auto reclaimer_result = object_core::set_reclaimer(obj.ko, reclaim_object);
269 if (!reclaimer_result) {
270 discard_value object_core::unregister_object(obj.ko);
271 next_ptr(id) = m_free_head;
272 m_free_head = id;
273 obj.~T();
274 free(obj_ptr);
275 m_slot_blocks[id / BLOCK_SIZE][id % BLOCK_SIZE] = nullptr;
276 return std::unexpected(reclaimer_result.error());
277 }
278
279 discard_value m_count.fetch_add(1, memory_order::relaxed);
280
281 return kobject::robject<T, lifecycle_state::embryo>{&obj};
282 }
283
284 /// @brief Look up by slot index.
285 [[nodiscard]] auto find_raw(u32 id) noexcept -> T* {
286 if (id >= N) return nullptr;
287 auto* obj = object_ptr(id);
288 if (!obj || obj->ko.id == kobject::KOBJECT_ID_INVALID) return nullptr;
289 return obj;
290 }
291
292 [[nodiscard]] auto find_raw(u32 id) const noexcept -> const T* {
293 if (id >= N) return nullptr;
294 auto* obj = const_cast<scoms_table*>(this)->object_ptr(id);
295 if (!obj || obj->ko.id == kobject::KOBJECT_ID_INVALID) return nullptr;
296 return obj;
297 }
298
299 /// @brief Look up by slot index + generation.
300 [[nodiscard]] auto find_by_gen(u32 id, u32 gen) noexcept -> T* {
301 if (id >= N) return nullptr;
302 auto* obj = object_ptr(id);
303 if (!obj || obj->ko.id == kobject::KOBJECT_ID_INVALID) return nullptr;
304 if (obj->ko.generation != gen) return nullptr;
305 return obj;
306 }
307
308 /// @brief Look up by slot index and acquire a typed ref.
309 /// @return A ref if the slot is valid, or a null ref.
310 template <lifecycle_state State = lifecycle_state::active>
311 [[nodiscard]] auto acquire_ref(u32 id) noexcept
312 -> kobject::robject<T, State>
313 {
314 const sync::irq_lock_guard guard{m_table_lock};
315 T* p = object_ptr(id);
316 if (!p) return {};
317 if (p->ko.state != State) return {};
318 return kobject::robject<T, State>{p};
319 }
320
321 /// @brief Release a slot back to the free-list.
322 /// @param id Slot index to release.
323 /// @return Success, or kernel_error if the slot is invalid.
324 [[nodiscard]] auto release(u32 id) noexcept
325 -> std::expected<void, lib::kernel_error>
326 {
327 if (id >= N)
328 return std::unexpected(
329 lib::kernel_error::from_generic(lib::generic_error::invalid_arg));
330
331 T* retired = nullptr;
332 {
333 const sync::irq_lock_guard guard{m_table_lock};
334 auto* obj_ptr = object_ptr(id);
335 if (!obj_ptr) return std::unexpected(
336 lib::kernel_error::from_generic(lib::generic_error::not_found));
337 T& obj = *obj_ptr;
338 if (obj.ko.state == lifecycle_state::embryo ||
339 obj.ko.state == lifecycle_state::active ||
340 obj.ko.state == lifecycle_state::suspended) {
341 if (!obj.ko.transition(lifecycle_state::dying)) {
342 return std::unexpected(lib::kernel_error::from_generic(
343 lib::generic_error::invalid_arg));
344 }
345 }
346 if (obj.ko.state != lifecycle_state::dying) {
347 return std::unexpected(lib::kernel_error::from_generic(
348 lib::generic_error::invalid_arg));
349 }
350 auto metadata_result = object_core::unregister_object(obj.ko);
351 if (!metadata_result) return std::unexpected(metadata_result.error());
352 discard_value obj.ko.transition(lifecycle_state::destroyed);
353 m_slot_blocks[id / BLOCK_SIZE][id % BLOCK_SIZE] = nullptr;
354 discard_value m_count.fetch_sub(1, memory_order::relaxed);
355 retired = &obj;
356 }
357 retired->ko.put();
358 return {};
359 }
360
361 /// @brief Destroy all objects matching a predicate.
362 /// @param pred Predicate: bool(const T&).
363 /// @param cleanup Optional cleanup: void(T&). Called before
364 /// the slot is released.
365 /// @return Number of objects destroyed.
366 template <typename Pred, typename Cleanup>
367 auto destroy_if(Pred&& pred, Cleanup&& cleanup) noexcept -> u32 {
368 u32 destroyed = 0;
369
370 for (u32 i = 0; i < N; ++i) {
371 auto ref = acquire_ref<lifecycle_state::active>(i);
372 if (!ref || !pred(*ref)) continue;
373 cleanup(*ref);
374 if (release(i)) ++destroyed;
375 }
376
377 return destroyed;
378 }
379
380 /// @brief Destroy all objects matching a predicate (no cleanup).
381 template <typename Pred>
382 auto destroy_if(Pred&& pred) noexcept -> u32 {
383 return destroy_if(
384 static_cast<Pred&&>(pred),
385 [](T&) noexcept {});
386 }
387
388 /// @brief Number of currently allocated (non-free) slots.
389 [[nodiscard]] auto count() const noexcept -> u32 {
390 return m_count.load(memory_order::relaxed);
391 }
392
393 /// @brief Maximum capacity.
394 [[nodiscard]] static constexpr auto capacity() noexcept -> u32 {
395 return N;
396 }
397
398 /// @brief Is the table initialized?
399 [[nodiscard]] auto initialized() const noexcept -> bool {
400 return m_initialized;
401 }
402
403 /// @brief Invoke @p fn for every live (non-free) object in the table.
404 /// @tparam Fn Callable of signature @c bool(T&) noexcept.
405 /// @param[in] fn Visitor. Return @c true to continue, @c false to stop.
406 /// @return Number of objects visited (including the one that stopped
407 /// the walk, if any).
408 template <typename Fn>
409 auto for_each(Fn&& fn) noexcept -> u32 {
410 u32 visited = 0;
411 for (u32 block = 0; block < BLOCK_COUNT; ++block) {
412 auto* slots = m_slot_blocks[block];
413 if (!slots) continue;
414 for (u32 i = 0; i < BLOCK_SIZE; ++i) {
415 auto* obj = slots[i];
416 if (!obj) continue;
417 auto s = obj->ko.state;
418 if (s != lifecycle_state::embryo &&
419 s != lifecycle_state::active &&
420 s != lifecycle_state::suspended &&
421 s != lifecycle_state::dying) continue;
422 ++visited;
423 if (!fn(*obj)) return visited;
424 }
425 }
426 return visited;
427 }
428
429 /// @brief Direct slot access (no validity check — internal use only).
430 [[nodiscard]] auto slot(u32 id) noexcept -> T& {
431 return *slot_ptr(id);
432 }
433 [[nodiscard]] auto slot(u32 id) const noexcept -> const T& {
434 return *slot_ptr(id);
435 }
436 };
437
438} // namespace zxfoundation::scoms
439
440} // end export