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