The Pedigree Project 0.1
slam-regressions.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/core/SlamAllocator.h"
11#include "pedigree/kernel/process/Scheduler.h"
12#include "pedigree/kernel/process/Thread.h"
13#include "pedigree/kernel/processor/Processor.h"
14
15namespace {
16constexpr size_t ObjectSize = 128;
17constexpr size_t ObjectCount = SLAB_MINIMUM_SIZE / ObjectSize;
18
19void prepareAllocation(SlamCache& cache, uintptr_t object) {
20 SlamAllocator::AllocHeader* header = reinterpret_cast<SlamAllocator::AllocHeader*>(object);
21 header->cache = &cache;
22#if OVERRUN_CHECK
23 header->magic = VIGILANT_MAGIC;
24 SlamAllocator::AllocFooter* footer = reinterpret_cast<SlamAllocator::AllocFooter*>(
25 object + cache.objectSize() - sizeof(SlamAllocator::AllocFooter));
26 footer->magic = VIGILANT_MAGIC;
27#endif
28}
29
30struct RemoteFreeContext {
31 RemoteFreeContext(SlamCache& cache, uintptr_t* objects, size_t count, size_t allocatingProcessor)
32 : cache(cache),
33 objects(objects),
34 count(count),
35 allocatingProcessor(allocatingProcessor),
36 processor(static_cast<size_t>(-1)) {}
37
38 SlamCache& cache;
39 uintptr_t* objects;
40 size_t count;
41 size_t allocatingProcessor;
42 Atomic<size_t> processor;
43};
44
45int freeRemotely(void* parameter) {
46 RemoteFreeContext* context = reinterpret_cast<RemoteFreeContext*>(parameter);
47 const size_t processor = Processor::index();
48 if (processor == context->allocatingProcessor ||
49 !context->processor.compareAndSwap(static_cast<size_t>(-1), processor)) {
50 return 0;
51 }
52 for (size_t i = 0; i < context->count; ++i) {
53 context->cache.free(context->objects[i]);
54 }
55 return 0;
56}
57
58bool runRemoteFree(RemoteFreeContext& context) {
59 const size_t processorCount = Processor::getCount();
60 Thread* workers[64] = {};
61 size_t started = 0;
62 for (; started < processorCount; ++started) {
63 workers[started] = new Thread(Scheduler::instance().getKernelProcess(), freeRemotely, &context,
64 nullptr, false, false, true);
65 workers[started]->setName("QEMU SLAM remote freer");
66 if (!workers[started]->start()) {
67 break;
68 }
69 }
70
71 bool joined = true;
72 for (size_t i = 0; i < started; ++i) {
73 joined = workers[i]->joinForCompletion() && joined;
74 }
75 return started == processorCount && joined &&
76 context.processor.value() != static_cast<size_t>(-1);
77}
78} // namespace
79
80bool runSlamAllocatorConcurrencyRegression() {
81 NOTICE("QEMU-CONCURRENCY-TEST: BEGIN slam-cross-cpu-recovery-smp");
82
83 const size_t processorCount = Processor::getCount();
84 if (processorCount < 2 || processorCount > 64) {
85 ERROR("QEMU SLAM regression requires between two and 64 processors");
86 return false;
87 }
88
89 SlamAllocator& allocator = SlamAllocator::instance();
90 SlamCache cache;
91 cache.initialise(&allocator, ObjectSize);
92
93 uintptr_t objects[ObjectCount] = {};
94 // The slab header consumes object slots; fill exactly one slab.
95 const size_t objectCount = cache.slabObjectCount();
96 if (!objectCount || objectCount > ObjectCount) {
97 return false;
98 }
99 for (size_t i = 0; i < objectCount; ++i) {
100 objects[i] = cache.allocate();
101 prepareAllocation(cache, objects[i]);
102 }
103
104 const size_t allocatingProcessor = Processor::index();
105 RemoteFreeContext firstFree(cache, objects, objectCount, allocatingProcessor);
106 if (!runRemoteFree(firstFree) || firstFree.processor == allocatingProcessor) {
107 ERROR(
108 "QEMU-CONCURRENCY-TEST: FAIL slam-cross-cpu-recovery-smp: "
109 "free worker did not complete on a remote CPU");
110 return false;
111 }
112
113 uintptr_t reused = cache.allocate();
114 if ((reused & ~(SLAB_MINIMUM_SIZE - 1)) != (objects[0] & ~(SLAB_MINIMUM_SIZE - 1))) {
115 ERROR(
116 "QEMU-CONCURRENCY-TEST: FAIL slam-cross-cpu-recovery-smp: "
117 "remote free list was stranded");
118 return false;
119 }
120 prepareAllocation(cache, reused);
121
122 RemoteFreeContext finalFree(cache, &reused, 1, allocatingProcessor);
123 if (!runRemoteFree(finalFree) || cache.recovery(1) != 1) {
124 ERROR(
125 "QEMU-CONCURRENCY-TEST: FAIL slam-cross-cpu-recovery-smp: "
126 "cross-CPU sub-page slab was not reclaimed");
127 return false;
128 }
129
130 NOTICE("QEMU-CONCURRENCY-TEST: slam cpus=" << Dec << allocatingProcessor << "/"
131 << static_cast<size_t>(firstFree.processor) << "/"
132 << static_cast<size_t>(finalFree.processor));
133 NOTICE("QEMU-CONCURRENCY-TEST: PASS slam-cross-cpu-recovery-smp");
134 return true;
135}
static size_t getCount()
static size_t index()
static Scheduler & instance()
Definition Scheduler.h:96
void initialise(SlamAllocator *parent, size_t objectSize)
size_t recovery(size_t maxSlabs)
uintptr_t allocate()
bool joinForCompletion()
Definition Thread.cc:2750
@ Dec
Definition Log.h:126