The Pedigree Project 0.1
Cache-discard.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/LockGuard.h"
9#include "pedigree/kernel/TargetInfo.h"
10#include "pedigree/kernel/process/TerminationDeferral.h"
11#include "pedigree/kernel/utilities/Cache.h"
12#include "pedigree/kernel/utilities/assert.h"
13
14#if THREADS
15#include "pedigree/kernel/process/Thread.h"
16#include "pedigree/kernel/processor/Processor.h"
17#include "pedigree/kernel/processor/ProcessorInformation.h"
18#endif
19
20static constexpr size_t CachePageSize = TargetInfo::getPageSize();
21
23 CachePage* page;
24 size_t references;
25};
26
27Cache::PreparedDiscard::PreparedDiscard(Cache& cache)
28 : m_TerminationDeferral(), m_Cache(cache), m_Entries(), m_Count(0), m_Committed(false) {}
29
30Cache::PreparedDiscard::~PreparedDiscard() {
31 if (m_Committed) {
32 return;
33 }
34 for (size_t i = 0; i < m_Count; ++i) {
35 CachePage* page = m_Entries.get()[i].page;
36 {
37 LockGuard<Spinlock> guard(m_Cache.m_Lock);
38 assert(page->evictionState == CachePage::EvictionState::Draining);
39 page->evictionState = CachePage::EvictionState::None;
40 }
41#if THREADS
42 m_Cache.m_EvictionWaiters.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(page));
43#endif
44 }
45}
46
48 if (m_Committed || !callback)
49 return false;
50 {
51 LockGuard<Spinlock> guard(m_Cache.m_Lock);
52 for (size_t i = 0; i < m_Count; ++i) {
53 CachePage* page = m_Entries.get()[i].page;
54 if (m_Entries.get()[i].references || page->refcnt != 1 || page->writebackPins ||
55 page->callbackActive || page->evictionState != CachePage::EvictionState::Draining)
56 return false;
57 }
58 }
59 bool succeeded = true;
60 for (size_t i = 0; i < m_Count; ++i) {
61 CachePage* page = m_Entries.get()[i].page;
62 {
63 LockGuard<Spinlock> guard(m_Cache.m_Lock);
64 page->callbackActive = true;
65#if THREADS
66 page->callbackOwner = Processor::information().getCurrentThread();
67#endif
68 // A later device-cache flush may fail even after this write succeeds.
69 // Rollback must leave every submitted page eligible for another write.
70 m_Cache.recordMutation(page);
71 }
72 const bool written = callback(page->key, page->location, context);
73 if (!written)
74 succeeded = false;
75 {
76 LockGuard<Spinlock> guard(m_Cache.m_Lock);
77 page->writebackFailed = page->writebackFailed || !written;
78 page->callbackActive = false;
79#if THREADS
80 page->callbackOwner = nullptr;
81#endif
82 m_Cache.updateWritebackIndex(page);
83 }
84#if THREADS
85 m_Cache.m_EvictionWaiters.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(page));
86#endif
87 }
88 return succeeded;
89}
90
91void Cache::PreparedDiscard::commit() {
92 assert(!m_Committed);
93 for (size_t i = 0; i < m_Count; ++i) {
94 CachePage* page = m_Entries.get()[i].page;
95 {
96 LockGuard<Spinlock> guard(m_Cache.m_Lock);
97 assert(page->evictionState == CachePage::EvictionState::Draining);
98 assert(page->refcnt == 1 && !page->writebackPins);
99 page->evictionState = CachePage::EvictionState::Retiring;
100 }
101 // The bytes are intentionally discarded. Eviction callbacks retire only
102 // external indexes; running writeback could recreate truncated storage.
103 const bool retired = m_Cache.finishRetirement(page, m_Cache.m_Callback, m_Cache.m_CallbackMeta);
104 assert(retired);
105 }
106 m_Committed = true;
107}
108
109Cache::DiscardStatus Cache::prepareDiscardFrom(uintptr_t cutoff, const DiscardReference* references,
110 size_t count,
112 result.reset();
113 if ((count && !references) || cutoff % CachePageSize) {
114 return DiscardStatus::Invalid;
115 }
116 for (size_t i = 0; i < count; ++i) {
117 if (references[i].key % CachePageSize || references[i].references == ~size_t(0) ||
118 (i && references[i - 1].key >= references[i].key)) {
119 return DiscardStatus::Invalid;
120 }
121 }
122#if THREADS
123 TerminationDeferral terminationDeferral;
124#endif
126 if (!plan) {
127 return DiscardStatus::NoMemory;
128 }
129#if THREADS
130 if (!m_ManagerOperations.tryAcquire(plan.get()->m_Lease)) {
131 return DiscardStatus::Closed;
132 }
133#endif
134 constexpr size_t MaximumPages = 65536;
135 size_t pages = 0;
136 {
138 if (static_cast<size_t>(m_ShutdownState)) {
139 return DiscardStatus::Closed;
140 }
141 uintptr_t key = 0;
142 CachePage* page = nullptr;
143 uintptr_t cursor = cutoff;
144 while (m_Pages.lowerBound(cursor, key, page)) {
145 if (key == ~uintptr_t(0) || ++pages > MaximumPages) {
146 return DiscardStatus::NoMemory;
147 }
148 cursor = key + 1;
149 }
150 }
151 if (pages) {
152 plan.get()->m_Entries = UniqueArray<PreparedDiscard::Entry>::allocate(pages);
153 if (!plan.get()->m_Entries) {
154 return DiscardStatus::NoMemory;
155 }
156 }
157
158 DiscardStatus status = DiscardStatus::Ready;
159 {
161 uintptr_t cursor = cutoff;
162 uintptr_t key = 0;
163 CachePage* page = nullptr;
164 size_t referenceIndex = 0;
165 while (referenceIndex < count && references[referenceIndex].key < cutoff) {
166 ++referenceIndex;
167 }
168 while (m_Pages.lowerBound(cursor, key, page)) {
169 if (plan.get()->m_Count == pages || key == ~uintptr_t(0)) {
170 status = DiscardStatus::Busy;
171 break;
172 }
173 cursor = key + 1;
174 while (referenceIndex < count && references[referenceIndex].key < key) {
175 if (references[referenceIndex++].references) {
176 status = DiscardStatus::Invalid;
177 break;
178 }
179 }
180 if (status != DiscardStatus::Ready) {
181 break;
182 }
183 size_t expected = 0;
184 if (referenceIndex < count && references[referenceIndex].key == key) {
185 expected = references[referenceIndex++].references;
186 }
187 if (page->evictionState != CachePage::EvictionState::None ||
188 page->status == CachePage::Editing || page->writebackPins > page->refcnt ||
189 page->refcnt - page->writebackPins != 1 + expected) {
190 status = DiscardStatus::Busy;
191 break;
192 }
193 page->evictionState = CachePage::EvictionState::Draining;
194 plan.get()->m_Entries.get()[plan.get()->m_Count++] = {page, expected};
195 }
196 while (status == DiscardStatus::Ready && referenceIndex < count) {
197 if (references[referenceIndex++].references) {
198 status = DiscardStatus::Invalid;
199 }
200 }
201 }
202 if (status != DiscardStatus::Ready) {
203 return status;
204 }
205
206 for (size_t i = 0; i < plan.get()->m_Count; ++i) {
207 const auto& entry = plan.get()->m_Entries.get()[i];
208#if THREADS
209 while (true) {
210 auto waitGuard = m_EvictionWaiters.acquire();
211 {
213 if (!entry.page->writebackPins) {
214 assert(entry.page->refcnt == 1 + entry.references);
215 break;
216 }
217 }
218 const auto reason = waitGuard.waitForCompletion(WaitQueue::Channel(entry.page),
219 Thread::CallbackDrain, entry.page->key);
220 if (reason == WaitQueue::WakeReason::Unwinding ||
221 reason == WaitQueue::WakeReason::Terminating) {
222 FATAL("Prepared cache discard interrupted during callback drain");
223 }
224 }
225#else
226 if (entry.page->writebackPins) {
227 return DiscardStatus::Busy;
228 }
229#endif
230 }
231 result = pedigree_std::move(plan);
232 return DiscardStatus::Ready;
233}
234
235void Cache::releaseWriteback(uintptr_t key) {
236 CachePage* page = nullptr;
237 bool shouldEvict = false;
238 {
240 page = m_Pages.lookup(key);
241 assert(page && page->writebackPins && page->refcnt);
242 --page->writebackPins;
243 --page->refcnt;
244 shouldEvict = !page->refcnt;
245 }
246#if THREADS
247 m_EvictionWaiters.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(page));
248#endif
249 if (shouldEvict) {
250 CacheManager::instance().addCacheRequest(this, true, CacheConstants::PleaseEvict, key);
251 }
252}
uint64_t addCacheRequest(Cache *cache, bool asynchronous, CacheConstants::CallbackCause cause, uintptr_t key, uintptr_t location=0, bool transferredPin=false, bool onlyIfDirty=false, bool batch=false)
Definition Cache.cc:421
MUST_USE_RESULT bool writeback(retirement_writeback_t callback, void *context)
Definition Cache.h:207
OperationBarrier m_ManagerOperations
Definition Cache.h:733
bool(* retirement_writeback_t)(uintptr_t key, uintptr_t page, void *meta)
Definition Cache.h:292
Tree< uintptr_t, CachePage * > m_Pages
Definition Cache.h:703
WaitQueue m_EvictionWaiters
Definition Cache.h:730
writeback_t m_Callback
Definition Cache.h:742
bool finishRetirement(CachePage *page, writeback_t callback, void *callbackMeta)
Definition Cache.cc:1156
DiscardStatus prepareDiscardFrom(uintptr_t cutoff, const DiscardReference *references, size_t count, UniquePointer< PreparedDiscard > &result)
Atomic< size_t > m_ShutdownState
Definition Cache.h:757
Spinlock m_Lock
Definition Cache.h:726
void * m_CallbackMeta
Definition Cache.h:751
MUST_USE_RESULT bool tryAcquire(Lease &lease)
static ProcessorInformation & information()
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40
static UniquePointer< T > adopt(T *pointer)
Definition Pointers.h:101
size_t refcnt
Definition Cache.h:221
uintptr_t key
Key for this page.
Definition Cache.h:214
uintptr_t location
The location of this page in memory.
Definition Cache.h:217