The Pedigree Project 0.1
Cache.cc
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include "pedigree/kernel/LockGuard.h"
21#include "pedigree/kernel/Log.h"
22#include "pedigree/kernel/Metrics.h"
23#include "pedigree/kernel/TargetInfo.h"
24#include "pedigree/kernel/machine/Machine.h"
25#include "pedigree/kernel/machine/Timer.h"
26#include "pedigree/kernel/process/MemoryPressureManager.h"
27#include "pedigree/kernel/process/TerminationDeferral.h"
28#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
29#include "pedigree/kernel/processor/VirtualAddressSpace.h"
30#include "pedigree/kernel/utilities/Cache.h"
31#include "pedigree/kernel/utilities/Iterator.h"
32#include "pedigree/kernel/utilities/Vector.h"
33#include "pedigree/kernel/utilities/assert.h"
34#include "pedigree/kernel/utilities/utility.h"
35
36#if !STANDALONE_CACHE
37#include "pedigree/kernel/process/Scheduler.h"
38#include "pedigree/kernel/process/Thread.h"
39#include "pedigree/kernel/processor/Processor.h"
40#include "pedigree/kernel/processor/ProcessorInformation.h"
41#endif
42
43#include "pedigree/kernel/utilities/smhasher/MurmurHash3.h"
44
45class Process;
46
47static constexpr size_t CachePageSize = TargetInfo::getPageSize();
48
49// Don't allocate cache space in reverse, but DO re-use cache pages.
50// This gives us wins because we don't need to reallocate page tables for
51// evicted pages. Without reuse, we end up needing to clean up old page tables
52// eventually.
55static bool g_AllocatorInited = false;
56
57CacheManager* CacheManager::m_Instance = nullptr;
58
59#if THREADS
60static int trimTrampoline(void* p) {
61 CacheManager::instance().trimThread();
62 return 0;
63}
64#endif
65
66CacheManager::CacheManager()
67 : RequestQueue(MakeConstantString("CacheManager")),
68 m_Caches(),
69 m_NextCacheId(1),
70 m_TimerClock(),
71 m_TrimDelta(0),
72#if THREADS
73 m_CachesLock(),
74 m_pTrimThread(0),
75 m_TrimWaiters(),
76 m_bTrimRequested(false),
77#endif
78 m_bActive(false),
79 m_pTimer(nullptr),
80 m_TerminalState(0) {
81}
82
83CacheManager::~CacheManager() {
84 stopPeriodicWork();
85
86#if THREADS
87 {
89 if (m_Caches.begin() != m_Caches.end()) {
90 FATAL("CacheManager destroyed while Cache objects remain registered");
91 }
92 }
93#else
94 if (m_Caches.begin() != m_Caches.end()) {
95 FATAL("CacheManager destroyed while Cache objects remain registered");
96 }
97#endif
98
100}
101
102void CacheManager::stopPeriodicWork() {
103#if !STANDALONE_CACHE
104 if (m_pTimer) {
105 if (!m_pTimer->unregisterHandler(this)) {
106 FATAL("CacheManager could not drain its timer callback");
107 }
108 m_pTimer = nullptr;
109 }
110#endif
111
112#if THREADS
113 {
114 auto guard = m_TrimWaiters.acquire();
115 m_bActive = false;
116 guard.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(this));
117 }
118 if (m_pTrimThread) {
119 m_pTrimThread->joinForCompletion();
120 m_pTrimThread = nullptr;
121 }
122#else
123 m_bActive = false;
124#endif
125}
126
128 const size_t state = m_TerminalState;
129 if (state >= 2)
130 return state == 2;
131 if (!m_TerminalState.compareAndSwap(0, 1)) {
132 FATAL("Concurrent CacheManager terminal shutdown is not permitted");
133 return false;
134 }
135 TerminationDeferral lifetime;
136 stopPeriodicWork();
137 // Cancel queued writebacks and join active callbacks before flushing directly.
138 // Lower storage queues and interrupts still service those synchronous writes.
140
141#if THREADS
142 const uint64_t maximumId = cacheGenerationWatermark();
143#else
144 const uint64_t maximumId = m_NextCacheId - 1;
145#endif
146 size_t remainingPasses = 0;
147 {
148#if THREADS
150#endif
151 remainingPasses = m_Caches.count() + 1;
152 }
153 while (remainingPasses--) {
154 bool succeeded = true;
155 bool dirty = false;
156 for (size_t scan = 0; scan < 2; ++scan) {
157 uint64_t afterId = 0;
158 Cache* cache = nullptr;
159 uint64_t cacheId = 0;
160 while (true) {
161#if THREADS
163 if (!acquireNextCache(afterId, maximumId, cache, cacheId, lease))
164#else
165 if (!findNextCache(afterId, maximumId, cache, cacheId))
166#endif
167 break;
168 afterId = cacheId;
169 if (!scan) {
170 succeeded = cache->syncAll() && succeeded;
171 } else {
172 LockGuard<Spinlock> guard(cache->m_Lock);
173 if (cache->m_Callback) {
174 for (auto page = cache->m_Pages.begin(); page != cache->m_Pages.end(); ++page)
175 dirty |= page.value()->status == Cache::CachePage::Editing ||
176 cache->needsWriteback(page.value());
177 }
178 }
179 }
180 }
181 if (!succeeded || !dirty) {
182 m_TerminalState = succeeded ? 2 : 3;
183 return succeeded;
184 }
185 // An upper cache can dirty a lower cache already visited in this pass.
186 // A finite dependency chain settles within one pass per retained cache.
187 }
188 ERROR("CacheManager: terminal writeback did not settle");
189 m_TerminalState = 3;
190 return false;
191}
192
194#if !STANDALONE_CACHE
195 Timer* t = Machine::instance().getTimer();
196 if (t && t->registerHandler(this)) {
197 m_pTimer = t;
198 const Time::Timestamp interval = CACHE_WRITEBACK_PERIOD * Time::Multiplier::Millisecond;
199 if (t->supportsDeadlines() && !t->armHandler(this, Time::getTicks() + interval)) {
200 FATAL("CacheManager could not arm its timer callback");
201 }
202 } else {
203 FATAL("CacheManager could not register its timer callback");
204 }
205#endif
206
207 // Call out to the base class initialise() so the RequestQueue goes live.
209
210#if THREADS
211 // Create our main trim thread.
212 Process* pParent = Processor::information().getCurrentThread()->getParent();
213 {
214 auto guard = m_TrimWaiters.acquire();
215 m_bActive = true;
216 m_bTrimRequested = true;
217 }
218 m_pTrimThread = new Thread(pParent, trimTrampoline, 0);
219 m_pTrimThread->setName("CacheManager trim thread");
220#endif
221}
222
223void CacheManager::registerCache(Cache* pCache) {
224#if THREADS
226#endif
227 if (static_cast<size_t>(m_TerminalState)) {
228 FATAL("Cache registered after terminal CacheManager shutdown began");
229 }
230 if (!m_NextCacheId) {
231 FATAL("CacheManager exhausted its stable cache identity space");
232 }
233 pCache->m_ManagerId = m_NextCacheId++;
234#if THREADS
235 {
236 auto timerGuard = m_TrimWaiters.acquire();
237 pCache->m_ManagerTimerStamp = m_TimerClock;
238 }
239#else
240 pCache->m_ManagerTimerStamp = m_TimerClock;
241#endif
242 m_Caches.insert(pCache->m_ManagerId, pCache);
243}
244
245void CacheManager::unregisterCache(Cache* pCache) {
246#if THREADS
247 {
249#endif
250 if (!pCache->m_ManagerId || m_Caches.lookup(pCache->m_ManagerId) != pCache) {
251 FATAL("CacheManager could not unregister an unknown Cache");
252 }
253 m_Caches.remove(pCache->m_ManagerId);
254 pCache->m_ManagerId = 0;
255#if THREADS
256 }
258#endif
259}
260
261bool CacheManager::trimAll(size_t count) {
262 if (static_cast<size_t>(m_TerminalState))
263 return false;
264 size_t totalEvicted = 0;
265#if THREADS
266 uint64_t afterId = 0;
267 const uint64_t maximumId = cacheGenerationWatermark();
268 while (count) {
269 Cache* cache = nullptr;
270 uint64_t cacheId = 0;
271 OperationBarrier::Lease cacheLease;
272 if (!acquireNextCache(afterId, maximumId, cache, cacheId, cacheLease)) {
273 break;
274 }
275
276 afterId = cacheId;
277 size_t evicted = cache->trim(count);
278 totalEvicted += evicted;
279 count -= evicted;
280 }
281#else
282 uint64_t afterId = 0;
283 const uint64_t maximumId = m_NextCacheId - 1;
284 Cache* cache = nullptr;
285 uint64_t cacheId = 0;
286 while (count && findNextCache(afterId, maximumId, cache, cacheId)) {
287 afterId = cacheId;
288 size_t evicted = cache->trim(count);
289 totalEvicted += evicted;
290 count -= evicted;
291 }
292#endif
293
294 return totalEvicted != 0;
295}
296
297void CacheManager::timer(uint64_t delta) {
298 if (static_cast<size_t>(m_TerminalState))
299 return;
300#if !STANDALONE_CACHE
301 const Time::Timestamp interval = CACHE_WRITEBACK_PERIOD * Time::Multiplier::Millisecond;
302 if (m_pTimer && m_pTimer->supportsDeadlines() &&
303 !m_pTimer->armHandler(this, Time::getTicks() + interval)) {
304 FATAL("CacheManager could not rearm its timer callback");
305 }
306#endif
307 bool memoryPressure = false;
308#if THREADS
309 // Keep the pressure check at timer cadence without waking an idle worker.
310 // Sample before taking the waiter lock, as physical allocators can trim caches.
311 memoryPressure =
312 PhysicalMemoryManager::instance().freePageCount() <= MemoryPressureManager::getLowWatermark();
313#endif
314 timerTick(delta, memoryPressure);
315}
316
317void CacheManager::timerTick(uint64_t delta, bool memoryPressure) {
318#if THREADS
319 auto guard = m_TrimWaiters.acquire();
320#endif
321 m_TimerClock.advance(delta);
322 const uint64_t maximum = ~static_cast<uint64_t>(0);
323 m_TrimDelta = delta > (maximum - m_TrimDelta) ? maximum : m_TrimDelta + delta;
324#if THREADS
325 if (!m_bTrimRequested && (memoryPressure || m_TrimDelta >= CACHE_WRITEBACK_PERIOD * 1000000ULL)) {
326 // A running worker consumes this predicate before it can sleep again.
327 m_bTrimRequested = true;
328 guard.wakeOne(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(this));
329 }
330#else
331 (void)memoryPressure;
332 TimerStamp stamp;
333 if (!takeTimerStamp(stamp))
334 return;
335 uint64_t afterId = 0;
336 const uint64_t maximumId = m_NextCacheId - 1;
337 Cache* cache = nullptr;
338 uint64_t cacheId = 0;
339 while (findNextCache(afterId, maximumId, cache, cacheId, true)) {
340 afterId = cacheId;
341 dispatchTimer(cache, stamp);
342 }
343#endif
344}
345
346void CacheManager::TimerStamp::advance(uint64_t delta) {
347 const uint64_t previous = elapsed;
348 elapsed += delta;
349 if (elapsed < previous)
350 ++wraps;
351}
352
353uint64_t CacheManager::TimerStamp::since(const TimerStamp& previous) const {
354 if (wraps < previous.wraps || (wraps == previous.wraps && elapsed < previous.elapsed))
355 return 0;
356 if (wraps == previous.wraps || (wraps - previous.wraps == 1 && elapsed < previous.elapsed))
357 return elapsed - previous.elapsed;
358 return ~uint64_t{0};
359}
360
362 if (m_TrimDelta < CACHE_WRITEBACK_PERIOD * 1000000ULL)
363 return false;
364 // Keep subperiod deltas until a scan is due, then deliver the whole elapsed
365 // interval once, including time accumulated while the worker was running.
366 m_TrimDelta = 0;
367 stamp = m_TimerClock;
368 return true;
369}
370
371void CacheManager::dispatchTimer(Cache* cache, const TimerStamp& stamp) {
372 const uint64_t delta = stamp.since(cache->m_ManagerTimerStamp);
373 if (!delta)
374 return;
375 cache->m_ManagerTimerStamp = stamp;
376 cache->timer(delta);
377}
378
379bool CacheManager::findNextCache(uint64_t afterId, uint64_t maximumId, Cache*& cache,
380 uint64_t& cacheId, bool timersOnly) {
381 while (afterId < maximumId && m_Caches.lowerBound(afterId + 1, cacheId, cache) &&
382 cacheId <= maximumId) {
383 if (!timersOnly || cache->needsPeriodicTimer())
384 return true;
385 // Retain the old timer stamp so late callback installation receives elapsed time.
386 afterId = cacheId;
387 }
388 cache = nullptr;
389 cacheId = 0;
390 return false;
391}
392
393#if THREADS
394bool CacheManager::acquireCache(Cache* cache, uint64_t& generation,
397 for (auto it = m_Caches.begin(); it != m_Caches.end(); ++it) {
398 if (it.value() == cache) {
399 if (cache->m_ManagerOperations.tryAcquire(lease)) {
400 generation = cache->m_ManagerId;
401 return true;
402 }
403 generation = 0;
404 return false;
405 }
406 }
407 generation = 0;
408 lease = OperationBarrier::Lease();
409 return false;
410}
411
412bool CacheManager::acquireNextCache(uint64_t afterId, uint64_t maximumId, Cache*& cache,
413 uint64_t& cacheId, OperationBarrier::Lease& lease,
414 bool timersOnly) {
416 if (!findNextCache(afterId, maximumId, cache, cacheId, timersOnly)) {
417 lease = OperationBarrier::Lease();
418 return false;
419 }
420
421 if (!cache->m_ManagerOperations.tryAcquire(lease)) {
422 FATAL("CacheManager found a closing Cache still registered");
423 }
424 return true;
425}
426
431#endif
432
433uint64_t CacheManager::addCacheRequest(Cache* cache, bool asynchronous,
434 CacheConstants::CallbackCause cause, uintptr_t key,
435 uintptr_t location, bool transferredPin, bool batch) {
436 if (static_cast<size_t>(m_TerminalState)) {
437 if (batch)
438 cache->releaseBackgroundWriteback(reinterpret_cast<Cache::BackgroundWriteback*>(key));
439 if (transferredPin)
440 cache->releaseWriteback(key);
441 return 0;
442 }
443#if THREADS
444 // RequestQueue rejects these contexts before taking payload ownership.
445 // In particular, last-reference cancellation can request another eviction.
447 if (batch)
448 cache->releaseBackgroundWriteback(reinterpret_cast<Cache::BackgroundWriteback*>(key));
449 if (transferredPin)
450 cache->releaseWriteback(key);
451 return 0;
452 }
453 uint64_t generation = 0;
454 OperationBarrier::Lease cacheLease;
455 if (!acquireCache(cache, generation, cacheLease)) {
456 if (batch)
457 cache->releaseBackgroundWriteback(reinterpret_cast<Cache::BackgroundWriteback*>(key));
458 if (transferredPin) {
459 cache->releaseWriteback(key);
460 }
461 return 0;
462 }
463
464 CacheRequest* request = new CacheRequest(cache, pedigree_std::move(cacheLease));
465 if (!request) {
466 if (batch)
467 cache->releaseBackgroundWriteback(reinterpret_cast<Cache::BackgroundWriteback*>(key));
468 if (transferredPin) {
469 cache->releaseWriteback(key);
470 }
471 return 0;
472 }
473 const uint64_t requestToken = reinterpret_cast<uint64_t>(request);
474#else
475 const uint64_t generation = 0;
476 const uint64_t requestToken = 0;
477#endif
478
479 // A batch is identified by p7 and owns its p3 payload.
480 if (asynchronous) {
481 return addAsyncRequest(1, reinterpret_cast<uint64_t>(cache), cause, key, location,
482 transferredPin ? 1 : 0, generation, batch ? 2 : 0, requestToken);
483 }
484
485 return addRequest(1, RequestQueue::NewRequest, reinterpret_cast<uint64_t>(cache), cause, key,
486 location, transferredPin ? 1 : 0, generation, batch ? 2 : 0, requestToken);
487}
488
489uint64_t CacheManager::executeRequest(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4,
490 uint64_t p5, uint64_t p6, uint64_t p7, uint64_t p8) {
491#if THREADS
492 CacheRequest* request = reinterpret_cast<CacheRequest*>(p8);
493 if (!request || !request->cache || request->cache != reinterpret_cast<Cache*>(p1)) {
494 FATAL("CacheManager received a request without lifetime ownership");
495 return 0;
496 }
497 Cache* pCache = request->cache;
498#else
499 Cache* pCache = reinterpret_cast<Cache*>(p1);
500 if (!pCache)
501 return 0;
502
503 bool cacheFound = false;
504 for (auto it = m_Caches.begin(); it != m_Caches.end(); ++it) {
505 if (it.value() == pCache) {
506 cacheFound = true;
507 break;
508 }
509 }
510 if (!cacheFound) {
511 return 0;
512 }
513#endif
514
515 uint64_t result = pCache->executeRequest(p1, p2, p3, p4, p5, p6, p7, 0);
516#if THREADS
517 delete request;
518#endif
519 return result;
520}
521
523#if THREADS
524 CacheRequest* cacheRequest = reinterpret_cast<CacheRequest*>(request.p8);
525 if (!cacheRequest) {
526 FATAL("CacheManager cancelled a request without lifetime ownership");
527 return;
528 }
529 if (request.p7 == 2)
530 cacheRequest->cache->releaseBackgroundWriteback(
531 reinterpret_cast<Cache::BackgroundWriteback*>(request.p3));
532 if (request.p5) {
533 cacheRequest->cache->releaseWriteback(request.p3);
534 }
535 delete cacheRequest;
536#else
537 if (request.p1 && request.p7 == 2)
538 reinterpret_cast<Cache*>(request.p1)
539 ->releaseBackgroundWriteback(reinterpret_cast<Cache::BackgroundWriteback*>(request.p3));
540 if (request.p1 && request.p5) {
541 Cache* cache = reinterpret_cast<Cache*>(request.p1);
542 cache->releaseWriteback(request.p3);
543 }
544#endif
545}
546
547#if THREADS
548void CacheManager::trimThread() {
549 while (true) {
550 TimerStamp stamp;
551 bool timerDue = false;
552 {
553 auto guard = m_TrimWaiters.acquire();
554 if (!m_bActive) {
555 return;
556 }
557 if (!m_bTrimRequested) {
558 const WaitQueue::WakeReason reason = guard.wait(
559 WaitQueue::Channel(this), Thread::CallbackDrain, reinterpret_cast<uintptr_t>(this));
560 if (reason == WaitQueue::WakeReason::Unwinding ||
561 reason == WaitQueue::WakeReason::Terminating) {
562 return;
563 }
564 continue;
565 }
566 m_bTrimRequested = false;
567 timerDue = takeTimerStamp(stamp);
568 }
569
570 // Ask caches to trim if we're heading towards memory usage problems.
572 size_t lowMark = MemoryPressureManager::getLowWatermark();
573 if (UNLIKELY(currFree <= lowMark)) {
574 // Start trimming. Trim more the closer to the high watermark we
575 // get.
576 NOTICE_NOLOCK(
577 "trimThread: free page count nears high watermark, "
578 "automatically trimming");
579 // Increase as the amount of memory decreases beyond the low
580 // watermark.
581 size_t trimCount = (lowMark - currFree) + 1;
582 trimAll(trimCount);
583 }
584
585 if (timerDue) {
586 uint64_t afterId = 0;
587 const uint64_t maximumId = cacheGenerationWatermark();
588 while (true) {
589 Cache* cache = nullptr;
590 uint64_t cacheId = 0;
591 OperationBarrier::Lease cacheLease;
592 if (!acquireNextCache(afterId, maximumId, cache, cacheId, cacheLease, true)) {
593 break;
594 }
595
596 afterId = cacheId;
597 dispatchTimer(cache, stamp);
598 }
599 }
600 }
601}
602#endif
603
604Cache::Cache(size_t pageConstraints)
605 : m_Pages(),
606 m_WritebackPages(),
607 m_DirtyTracking(DirtyTracking::Checksum),
608 // Each inode owns a Cache. Keep first-page metadata small; a saturated
609 // filter still falls back to the authoritative page tree.
610 m_PageFilter(4096, 4),
611 m_pLruHead(0),
612 m_pLruTail(0),
613 m_Lock(false),
614#if THREADS
615 m_EvictionWaiters(),
616 m_ManagerOperations(),
617#endif
618 m_ManagerId(0),
619 m_ManagerTimerStamp(),
620 m_PeriodicTimerEnabled(false),
621 m_Callback(0),
622 m_BackgroundWriteback(nullptr),
623 m_Nanoseconds(0),
624 m_WritebackEpoch(0),
625 m_CallbackMeta(nullptr),
626 m_bInCritical(0),
627 m_ShutdownState(0),
628 m_PageConstraints(pageConstraints)
629#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
630 ,
631 m_WritebackAdmissionHook(nullptr),
632 m_WritebackAdmissionHookMeta(nullptr)
633#endif
634{
635 {
636 LockGuard<Spinlock> allocatorGuard(m_AllocatorLock);
637 if (!g_AllocatorInited) {
638#if STANDALONE_CACHE
639 uintptr_t start = 0;
640 uintptr_t end = 0;
641 discover_range(start, end);
642#else
645#endif
646 m_Allocator.free(start, end - start);
647 g_AllocatorInited = true;
648 }
649 }
650
651 // Allocate any necessary iterators now, so that they're available
652 // immediately and we consume their memory early.
653 m_Pages.begin();
654 m_Pages.end();
655
656 CacheManager::instance().registerCache(this);
657}
658
659Cache::~Cache() {
660 shutdown();
661}
662
663bool Cache::shutdown(ShutdownMode mode) {
664 const size_t state = m_ShutdownState;
665 if (state == 2 || state == 3) {
666 return state == 2;
667 }
668 const bool deferred = state == 4;
669 if (deferred && mode == ShutdownMode::DiscardDeferred) {
670 return true;
671 }
672 if (!m_ShutdownState.compareAndSwap(deferred ? 4 : 0, deferred ? 5 : 1)) {
673 FATAL("Concurrent Cache shutdown is not permitted");
674 return false;
675 }
676
677 // Removing registration closes queue-time admission. Every request already
678 // published owns a manager-operation lease, so this waits for queued and
679 // active callbacks before storage is touched.
680 if (!deferred) {
681 CacheManager::instance().unregisterCache(this);
682 }
683 size_t discardedDirtyPages = 0;
684 const bool waitForPins = mode != ShutdownMode::DiscardDeferred;
685 const bool discard = deferred || mode != ShutdownMode::WriteBack;
686 const bool succeeded =
687 empty(discard ? EvictionMode::DiscardDirty : EvictionMode::DiscardBaseReference,
688 &discardedDirtyPages, waitForPins);
689 if (discardedDirtyPages) {
690 WARNING("Cache: discarded " << Dec << discardedDirtyPages
691 << " dirty pages after backing device removal");
692 }
693 if (!succeeded) {
694 ERROR("Cache: backend teardown left unwritten pages resident");
695 }
696 m_ShutdownState = succeeded ? (waitForPins ? 2 : 4) : 3;
697 return succeeded;
698}
699
700bool Cache::ensureUsable(const char* operation) const {
701 const size_t state = m_ShutdownState;
702 if (state < 2) {
703 return true;
704 }
705 if (state == 4 || state == 5) {
706 return false;
707 }
708
709 FATAL("Cache::" << operation << " called after terminal shutdown");
710 return false;
711}
712
713void Cache::waitForPageEviction(uintptr_t key) {
714#if THREADS
715 while (true) {
716 CachePage* page = nullptr;
717 auto waitGuard = m_EvictionWaiters.acquire();
718 {
720 if (m_PageFilter.contains(key)) {
721 page = m_Pages.lookup(key);
722 }
723 if (!page || page->evictionState == CachePage::EvictionState::None) {
724 return;
725 }
726 }
727
728 const WaitQueue::WakeReason reason =
729 waitGuard.waitForCompletion(WaitQueue::Channel(page), Thread::CallbackDrain, key);
730 (void)reason;
731 }
732#else
733 (void)key;
734#endif
735}
736
737uintptr_t Cache::lookup(uintptr_t key) {
738 if (!ensureUsable("lookup")) {
739 return 0;
740 }
741
743
744 // Check against the bloom filter first, before we hit the tree.
745 if (!m_PageFilter.contains(key)) {
746 Metrics::increment(Metrics::CacheLookupMiss);
747 return 0;
748 }
749
750 CachePage* pPage = m_Pages.lookup(key);
751 if (!pPage) {
752 Metrics::increment(Metrics::CacheLookupMiss);
753 return 0;
754 }
755 if (pPage->evictionState == CachePage::EvictionState::Draining ||
756 pPage->evictionState == CachePage::EvictionState::Retiring) {
757 Metrics::increment(Metrics::CacheLookupMiss);
758 return 0;
759 }
760
761 uintptr_t ptr = pPage->location;
762 pPage->refcnt++;
763 promotePage(pPage);
764 Metrics::increment(Metrics::CacheLookupHit);
765
766 return ptr;
767}
768
769size_t Cache::read(uintptr_t offset, size_t length, uintptr_t buffer,
770 bool (*prepare)(uintptr_t, size_t)) {
771 if (!length || length - 1 > ~uintptr_t(0) - offset || !ensureUsable("read")) {
772 return 0;
773 }
774#if THREADS
775 TerminationDeferral terminationDeferral;
776#endif
777 constexpr size_t MaxPages = 32;
778 CachePage* pages[MaxPages];
779 bool evict[MaxPages];
780 const size_t within = offset % CachePageSize;
781 const uintptr_t first = offset - within;
782 const size_t limit = MaxPages * CachePageSize - within;
783 if (length > limit) {
784 length = limit;
785 }
786 const size_t wanted = (within + length - 1) / CachePageSize + 1;
787 size_t count = 0;
788 {
790 for (; count < wanted; ++count) {
791 // Hits need the tree anyway; hashing a Bloom filter adds no useful work.
792 CachePage* page = m_Pages.lookup(first + count * CachePageSize);
793 if (!page || page->status == CachePage::Editing ||
794 page->evictionState == CachePage::EvictionState::Draining ||
795 page->evictionState == CachePage::EvictionState::Retiring || page->refcnt == ~size_t(0)) {
796 break;
797 }
798 ++page->refcnt;
799 promotePage(page);
800 pages[count] = page;
801 }
802 }
803 if (!count) {
804 return 0;
805 }
806 const size_t available = count * CachePageSize - within;
807 size_t copied = length < available ? length : available;
808 if (prepare && !prepare(buffer, copied)) {
809 copied = 0;
810 }
811 if (buffer && copied) {
812 size_t remaining = copied;
813 for (size_t i = 0; i < count; ++i) {
814 const size_t start = i ? 0 : within;
815 const size_t bytes = remaining < CachePageSize - start ? remaining : CachePageSize - start;
816 ForwardMemoryCopy(reinterpret_cast<void*>(buffer),
817 reinterpret_cast<void*>(pages[i]->location + start), bytes);
818 buffer += bytes;
819 remaining -= bytes;
820 }
821 }
822 {
824 for (size_t i = 0; i < count; ++i) {
825 assert(pages[i]->refcnt);
826 --pages[i]->refcnt;
827 evict[i] = !pages[i]->refcnt;
828 }
829 }
830 for (size_t i = 0; i < count; ++i) {
831#if THREADS
832 m_EvictionWaiters.wakeAllIfWaiting(WaitQueue::WakeReason::Signalled,
833 WaitQueue::Channel(pages[i]));
834#endif
835 if (evict[i]) {
836 CacheManager::instance().addCacheRequest(this, true, CacheConstants::PleaseEvict,
837 first + i * CachePageSize);
838 }
839 }
840 Metrics::add(Metrics::CacheReadBytes, copied);
841 return copied;
842}
843
844bool Cache::lookupStable(uintptr_t key, uintptr_t& location, bool wait) {
845 location = 0;
846 if (!ensureUsable("lookupStable"))
847 return false;
848#if THREADS
849 TerminationDeferral terminationDeferral;
850 OperationBarrier::Lease operation;
851 if (!m_ManagerOperations.tryAcquire(operation))
852 return false;
853 Thread* currentThread = Processor::information().getCurrentThread();
854 const bool canWait = wait && currentThread && !CacheManager::instance().callbackContext();
855#else
856 (void)wait;
857#endif
858 while (true) {
859#if THREADS
860 auto waitGuard = m_EvictionWaiters.acquire();
861#endif
862 CachePage* page = nullptr;
863 {
865 page = m_PageFilter.contains(key) ? m_Pages.lookup(key) : nullptr;
866 if (!page)
867 return true;
868 if (page->status == CachePage::Editing ||
869 page->evictionState == CachePage::EvictionState::Draining || page->refcnt == ~size_t{0})
870 return false;
871 if (!page->callbackActive && page->evictionState == CachePage::EvictionState::None) {
872 ++page->refcnt;
873 location = page->location;
874 promotePage(page);
875 return true;
876 }
877#if THREADS
878 if (!canWait || page->callbackOwner == currentThread)
879#endif
880 return false;
881 }
882#if THREADS
883 const auto reason =
884 waitGuard.waitForCompletion(WaitQueue::Channel(page), Thread::CallbackDrain, key);
885 (void)reason;
886#endif
887 }
888}
889
890uintptr_t Cache::insert(uintptr_t key, bool* alreadyExisted) {
891 if (!ensureUsable("insert")) {
892 return 0;
893 }
894
895 // Eviction callbacks may block and re-enter this Cache, so memory-pressure
896 // work cannot run under the insertion lock.
897 lruEvict();
898
899 while (true) {
902
903 // We check the bloom filter to avoid hitting the tree, which is useful
904 // as this is quite a hot path at times.
905 CachePage* pPage = 0;
906 bool triedLookup = false;
907 if (m_PageFilter.contains(key)) {
908 pPage = m_Pages.lookup(key);
909 if (pPage && pPage->evictionState != CachePage::EvictionState::None) {
910 continue;
911 }
912 if (pPage) {
913 if (alreadyExisted) {
914 *alreadyExisted = true;
915 }
916 return pPage->location;
917 }
918
919 triedLookup = true;
920 }
921
922 if (alreadyExisted) {
923 *alreadyExisted = false;
924 }
925
926 // sanity check
928 if ((!triedLookup) && m_Pages.lookup(key)) {
929 FATAL("Cache: bloom filter lied!");
930 }
931
933 uintptr_t location = 0;
934 bool succeeded = m_Allocator.allocate(CachePageSize, location);
936
937 if (!succeeded) {
938 FATAL("Cache: out of address space [have " << m_Pages.count() << " items].");
939 return 0;
940 }
941
942 if (!map(location)) {
943 FATAL("Map failed in Cache::insert())");
944 }
945
946 pPage = new CachePage;
947 ByteSet(pPage, 0, sizeof(CachePage));
948 pPage->key = key;
949 pPage->location = location;
950 pPage->refcnt = 1;
951 pPage->checksum[0] = 0;
952 pPage->checksum[1] = 0;
953 pPage->status = CachePage::Editing;
954 m_Pages.insert(key, pPage);
955 updateWritebackIndex(pPage);
956 m_PageFilter.add(key);
957 linkPage(pPage);
958
959 return location;
960 }
961}
962
963uintptr_t Cache::insert(uintptr_t key, size_t size, bool* alreadyExisted) {
964 if (!ensureUsable("insert")) {
965 return 0;
966 }
967
968 if (size % CachePageSize) {
969 WARNING("Cache::insert called with a size that isn't page-aligned");
970 if (alreadyExisted) {
971 *alreadyExisted = false;
972 }
973 return 0;
974 }
975
976 size_t nPages = size / CachePageSize;
977 if (!nPages) {
978 return 0;
979 }
980
981 // Retire at most one old page for each page this insertion may map.
982 // lruEvict owns the Cache lock and drops it around backing-store I/O.
983 for (size_t page = 0; page < nPages; ++page) {
984 lruEvict();
985 }
986
987 while (true) {
988 for (size_t page = 0; page < nPages; ++page) {
989 waitForPageEviction(key + (page * CachePageSize));
990 }
991
993 bool evictionPending = false;
994 for (size_t page = 0; page < nPages; ++page) {
995 CachePage* pageEntry = m_Pages.lookup(key + (page * CachePageSize));
996 if (pageEntry && pageEntry->evictionState != CachePage::EvictionState::None) {
997 evictionPending = true;
998 break;
999 }
1000 }
1001 if (evictionPending) {
1002 continue;
1003 }
1004
1005 // A range insertion must either reuse one complete contiguous extent
1006 // or create a wholly new one. Allocating around an interior overlap
1007 // loses the allocator chunk for the skipped page and publishes a
1008 // partially initialized range.
1009 CachePage* pPage = 0;
1010 CachePage* firstPage = 0;
1011 size_t existingPages = 0;
1012 bool contiguousExtent = true;
1013 for (size_t page = 0; page < nPages; ++page) {
1014 pPage = m_Pages.lookup(key + (page * CachePageSize));
1015 if (pPage) {
1016 ++existingPages;
1017 if (!firstPage) {
1018 firstPage = pPage;
1019 }
1020 if (page == 0 && pPage->location != firstPage->location) {
1021 contiguousExtent = false;
1022 } else if (page > 0 && pPage->location != firstPage->location + (page * CachePageSize)) {
1023 contiguousExtent = false;
1024 }
1025 }
1026 }
1027 if (existingPages) {
1028 if (existingPages != nPages || !firstPage || firstPage->key != key || !contiguousExtent) {
1029 if (alreadyExisted) {
1030 *alreadyExisted = false;
1031 }
1032 return 0;
1033 }
1034
1035 if (alreadyExisted) {
1036 *alreadyExisted = true;
1037 }
1038 return firstPage->location;
1039 }
1040
1041 if (alreadyExisted) {
1042 *alreadyExisted = false;
1043 }
1044
1045 // Nope, so let's allocate this block
1047 uintptr_t location;
1048 bool succeeded = m_Allocator.allocate(size, location);
1050
1051 if (!succeeded) {
1052 ERROR("Cache: can't allocate " << Dec << size << Hex << " bytes.");
1053 return 0;
1054 }
1055
1056 uintptr_t returnLocation = location;
1057 for (size_t page = 0; page < nPages; page++) {
1058 if (!map(location)) {
1059 FATAL("Map failed in Cache::insert())");
1060 }
1061
1062 pPage = new CachePage;
1063 ByteSet(pPage, 0, sizeof(CachePage));
1064 pPage->key = key + (page * CachePageSize);
1065 pPage->location = location;
1066
1067 // Cache pages retain one base reference while published.
1068 pPage->refcnt = 1;
1069 pPage->evictionState = CachePage::EvictionState::None;
1070 pPage->checksum[0] = 0;
1071 pPage->checksum[1] = 0;
1072 pPage->status = CachePage::Editing;
1073
1074 m_Pages.insert(key + (page * CachePageSize), pPage);
1075 updateWritebackIndex(pPage);
1076 m_PageFilter.add(key + (page * CachePageSize));
1077 linkPage(pPage);
1078
1079 location += CachePageSize;
1080 }
1081
1082 return returnLocation;
1083 }
1084}
1085
1086bool Cache::map(uintptr_t virt) const {
1087#if STANDALONE_CACHE
1088 // Will be part of the already-OK region in the allocator.
1089 return true;
1090#else
1092 return Processor::information().getVirtualAddressSpace().map(
1093 phys, reinterpret_cast<void*>(virt),
1095#endif
1096}
1097
1098bool Cache::exists(uintptr_t key, size_t length) {
1099 if (!ensureUsable("exists")) {
1100 return false;
1101 }
1102
1104
1105 bool result = true;
1106 for (size_t i = 0; i < length; i += CachePageSize) {
1107 if (!m_PageFilter.contains(key + i)) {
1108 result = false;
1109 break;
1110 }
1111
1112 CachePage* pPage = m_Pages.lookup(key + i);
1113 if (!pPage || pPage->evictionState == CachePage::EvictionState::Retiring) {
1114 result = false;
1115 break;
1116 }
1117 }
1118
1119 return result;
1120}
1121
1122bool Cache::evict(uintptr_t key) {
1123 if (!ensureUsable("evict")) {
1124 return false;
1125 }
1126 return evict(key, EvictionMode::Ordinary);
1127}
1128
1129bool Cache::discardEditing(uintptr_t key) {
1130 if (!ensureUsable("discardEditing")) {
1131 return false;
1132 }
1133 return evict(key, EvictionMode::DiscardEditing);
1134}
1135
1136bool Cache::evict(uintptr_t key, EvictionMode mode, size_t* discardedDirtyPages) {
1137 CachePage* page = nullptr;
1138 writeback_t callback = nullptr;
1139 void* callbackMeta = nullptr;
1140 uintptr_t location = 0;
1141 bool dirty = false;
1142 bool discardedDirty = false;
1143 uint64_t submittedGeneration = 0;
1144 uint64_t submittedChecksum[2] = {};
1145 bool submittedChecksumTracking = false;
1146
1147 {
1149 if (m_PageFilter.contains(key)) {
1150 page = m_Pages.lookup(key);
1151 }
1152 if (!page) {
1153 NOTICE("Cache::evict didn't evict " << key << " as it didn't actually exist");
1154 return false;
1155 }
1156 if (page->evictionState != CachePage::EvictionState::None) {
1157 return false;
1158 }
1159 // Ordinary eviction must leave a page mapped while its inserter fills it.
1160 if (mode == EvictionMode::Ordinary && page->status == CachePage::Editing) {
1161 return false;
1162 }
1163
1164 callback = m_Callback;
1166
1167 if (mode == EvictionMode::DiscardEditing) {
1168 if (page->status != CachePage::Editing || page->refcnt != 1) {
1169 return false;
1170 }
1171 page->evictionState = CachePage::EvictionState::Retiring;
1172 } else if (mode == EvictionMode::DiscardDirty) {
1173 if (page->refcnt > 1) {
1174 return false;
1175 }
1176 discardedDirty = callback && page->status != CachePage::Editing && needsWriteback(page);
1177 page->evictionState = CachePage::EvictionState::Retiring;
1178 } else {
1179 // Callback-backed pages retain a base reference. Other caches must
1180 // be entirely unpinned before eviction.
1181 const size_t permittedReferences =
1182 (callback || mode == EvictionMode::DiscardBaseReference) ? 1 : 0;
1183 if (page->refcnt > permittedReferences) {
1184 return false;
1185 }
1186
1187 page->evictionState = CachePage::EvictionState::WriteBack;
1188 dirty = callback && needsWriteback(page);
1189 submittedGeneration = page->mutationGeneration;
1190 submittedChecksumTracking = dirty && tracksChecksum(page);
1191 page->callbackActive = dirty;
1192#if THREADS
1193 page->callbackOwner = dirty ? Processor::information().getCurrentThread() : nullptr;
1194#endif
1195 }
1196
1197 location = page->location;
1198 }
1199
1200 // Backing-store I/O can block and may re-enter this Cache.
1201 if (submittedChecksumTracking)
1202 checksum(reinterpret_cast<const void*>(location), CachePageSize, submittedChecksum);
1203 if (dirty) {
1204 Metrics::increment(Metrics::CacheWritebackPages);
1205 }
1206 if (dirty && !callback(CacheConstants::WriteBack, key, location, callbackMeta)) {
1207 Metrics::increment(Metrics::CacheWritebackFailures);
1208 {
1210 page->writebackFailed = true;
1211 page->callbackActive = false;
1212#if THREADS
1213 page->callbackOwner = nullptr;
1214#endif
1215 page->evictionState = CachePage::EvictionState::None;
1216 updateWritebackIndex(page);
1217 }
1218#if THREADS
1219 m_EvictionWaiters.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(page));
1220#endif
1221 return false;
1222 }
1223
1224 if (mode != EvictionMode::DiscardEditing && mode != EvictionMode::DiscardDirty) {
1225 bool pinnedAgain = false;
1226 {
1228 CachePage* current = nullptr;
1229 if (m_PageFilter.contains(key)) {
1230 current = m_Pages.lookup(key);
1231 }
1232 if (current != page) {
1233 FATAL("Cache page changed identity during eviction");
1234 return false;
1235 }
1236
1237 page->callbackActive = false;
1238#if THREADS
1239 page->callbackOwner = nullptr;
1240#endif
1241 if (dirty) {
1242 page->writebackFailed = false;
1243 page->writtenGeneration = submittedGeneration;
1244 if (submittedChecksumTracking) {
1245 page->checksum[0] = submittedChecksum[0];
1246 page->checksum[1] = submittedChecksum[1];
1247 }
1248 if (page->status == CachePage::ChecksumChanging)
1249 page->status = CachePage::ChecksumStable;
1250 }
1251 // A callback or concurrent lookup may have pinned the page while
1252 // the cache lock was dropped. In that case, restore ordinary
1253 // admission.
1254 const size_t permittedReferences =
1255 (callback || mode == EvictionMode::DiscardBaseReference) ? 1 : 0;
1256 if (page->refcnt > permittedReferences ||
1257 (callback && (page->mutationGeneration != page->writtenGeneration ||
1258 (dirty && needsWriteback(page))))) {
1259 page->evictionState = CachePage::EvictionState::None;
1260 pinnedAgain = true;
1261 } else {
1262 page->evictionState = CachePage::EvictionState::Retiring;
1263 }
1264 updateWritebackIndex(page);
1265 }
1266
1267 if (pinnedAgain) {
1268#if THREADS
1269 m_EvictionWaiters.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(page));
1270#endif
1271 return false;
1272 }
1273 }
1274
1275 const bool retired = finishRetirement(page, callback, callbackMeta);
1276 if (retired && discardedDirty && discardedDirtyPages) {
1277 ++*discardedDirtyPages;
1278 }
1279 return retired;
1280}
1281
1282bool Cache::finishRetirement(CachePage* page, writeback_t callback, void* callbackMeta) {
1283 const uintptr_t key = page->key;
1284 const uintptr_t location = page->location;
1285
1286 // Same-key insertions wait while the external cache index is invalidated.
1287 if (callback) {
1288#if THREADS
1289 {
1291 page->callbackOwner = Processor::information().getCurrentThread();
1292 }
1293#endif
1294 callback(CacheConstants::Eviction, key, location, callbackMeta);
1295 }
1296
1297 {
1299 CachePage* current = nullptr;
1300 if (m_PageFilter.contains(key)) {
1301 current = m_Pages.lookup(key);
1302 }
1303 if (current != page || page->evictionState != CachePage::EvictionState::Retiring) {
1304 FATAL("Cache page changed identity during retirement");
1305 return false;
1306 }
1307 m_Pages.remove(key);
1308 if (page->writebackIndexed)
1309 m_WritebackPages.remove(key);
1310 unlinkPage(page);
1311 }
1312
1313#if THREADS
1314 m_EvictionWaiters.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(page));
1315#endif
1316
1317#if !STANDALONE_CACHE
1318 VirtualAddressSpace& va = Processor::information().getVirtualAddressSpace();
1319 void* mappedLocation = reinterpret_cast<void*>(location);
1320 physical_uintptr_t physicalLocation = 0;
1321 size_t flags = 0;
1322 va.getMapping(mappedLocation, physicalLocation, flags);
1323 va.unmap(mappedLocation);
1324 PhysicalMemoryManager::instance().freePage(physicalLocation);
1325#endif
1326
1327 {
1328 LockGuard<Spinlock> allocatorGuard(m_AllocatorLock);
1329 m_Allocator.free(location, CachePageSize);
1330 }
1331 delete page;
1332 Metrics::increment(Metrics::CacheEvictedPages);
1333 return true;
1334}
1335
1336bool Cache::retireWriteback(uintptr_t key, retirement_writeback_t callback, void* meta) {
1337 if (!ensureUsable("retireWriteback")) {
1338 return false;
1339 }
1340
1341#if THREADS
1342 TerminationDeferral terminationDeferral;
1343#endif
1344 CachePage* page = nullptr;
1345 CachePage::Status status = CachePage::Editing;
1346 writeback_t evictionCallback = nullptr;
1347 void* evictionCallbackMeta = nullptr;
1348 {
1350 if (m_PageFilter.contains(key)) {
1351 page = m_Pages.lookup(key);
1352 }
1353 if (!page) {
1354 return true;
1355 }
1356 if (!callback || page->evictionState != CachePage::EvictionState::None ||
1357 page->status == CachePage::Editing) {
1358 return false;
1359 }
1360
1361 page->evictionState = CachePage::EvictionState::Draining;
1362 status = page->status;
1363 evictionCallback = m_Callback;
1364 evictionCallbackMeta = m_CallbackMeta;
1365 }
1366
1367#if THREADS
1368 while (true) {
1369 bool ready = false;
1370 bool invalidated = false;
1371 bool reopened = false;
1372 auto waitGuard = m_EvictionWaiters.acquire();
1373 waitGuard.prepareToWait();
1374 {
1376 CachePage* current = nullptr;
1377 if (m_PageFilter.contains(key)) {
1378 current = m_Pages.lookup(key);
1379 }
1380 if (current != page || page->evictionState != CachePage::EvictionState::Draining ||
1381 page->status != status) {
1382 invalidated = true;
1383 if (current == page && page->evictionState == CachePage::EvictionState::Draining) {
1384 page->evictionState = CachePage::EvictionState::None;
1385 reopened = true;
1386 }
1387 } else {
1388 ready = page->refcnt == 1;
1389 }
1390 }
1391
1392 if (reopened) {
1393 waitGuard.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(page));
1394 }
1395 if (invalidated) {
1396 return false;
1397 }
1398 if (ready) {
1399 break;
1400 }
1401
1402 const WaitQueue::WakeReason reason =
1403 waitGuard.waitForCompletion(WaitQueue::Channel(page), Thread::CallbackDrain, key);
1404 (void)reason;
1405 }
1406#else
1407 {
1409 if (page->refcnt != 1) {
1410 page->evictionState = CachePage::EvictionState::None;
1411 return false;
1412 }
1413 }
1414#endif
1415
1416 {
1418 page->callbackActive = true;
1419#if THREADS
1420 page->callbackOwner = Processor::information().getCurrentThread();
1421#endif
1422 }
1423 Metrics::increment(Metrics::CacheWritebackPages);
1424 const bool writebackSucceeded = callback(key, page->location, meta);
1425 if (!writebackSucceeded) {
1426 Metrics::increment(Metrics::CacheWritebackFailures);
1427 }
1428 bool retire = false;
1429 bool wake = false;
1430 {
1432 page->callbackActive = false;
1433#if THREADS
1434 page->callbackOwner = nullptr;
1435#endif
1436 CachePage* current = nullptr;
1437 if (m_PageFilter.contains(key)) {
1438 current = m_Pages.lookup(key);
1439 }
1440 if (writebackSucceeded && current == page &&
1441 page->evictionState == CachePage::EvictionState::Draining && page->refcnt == 1 &&
1442 page->status == status) {
1443 page->evictionState = CachePage::EvictionState::Retiring;
1444 retire = true;
1445 } else if (current == page && page->evictionState == CachePage::EvictionState::Draining) {
1446 page->writebackFailed = page->writebackFailed || !writebackSucceeded;
1447 page->evictionState = CachePage::EvictionState::None;
1448 updateWritebackIndex(page);
1449 wake = true;
1450 }
1451 }
1452
1453 if (!retire) {
1454#if THREADS
1455 if (wake) {
1456 m_EvictionWaiters.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(page));
1457 }
1458#else
1459 (void)wake;
1460#endif
1461 return false;
1462 }
1463
1464 return finishRetirement(page, evictionCallback, evictionCallbackMeta);
1465}
1466
1468 if (!ensureUsable("empty")) {
1469 return false;
1470 }
1471 return empty(EvictionMode::DiscardBaseReference, nullptr);
1472}
1473
1474bool Cache::empty(EvictionMode mode, size_t* discardedDirtyPages, bool waitForPins) {
1475 while (true) {
1476 uintptr_t key = 0;
1477 if (!waitForPins) {
1478 bool found = false;
1479 {
1481 for (auto it = m_Pages.begin(); it != m_Pages.end(); ++it) {
1482 CachePage* page = it.value();
1483 if (page->evictionState == CachePage::EvictionState::None && page->refcnt <= 1) {
1484 key = it.key();
1485 found = true;
1486 break;
1487 }
1488 }
1489 }
1490 if (!found) {
1491 return true;
1492 }
1493 } else {
1494#if THREADS
1495 CachePage* waitPage = nullptr;
1496 {
1497 auto waitGuard = m_EvictionWaiters.acquire();
1498 waitGuard.prepareToWait();
1499 {
1502 if (it == m_Pages.end()) {
1503 return true;
1504 }
1505
1506 key = it.key();
1507 CachePage* page = it.value();
1508 if (page->evictionState != CachePage::EvictionState::None || page->refcnt > 1) {
1509 waitPage = page;
1510 }
1511 }
1512
1513 if (waitPage) {
1514 const WaitQueue::WakeReason reason =
1515 waitGuard.waitForCompletion(WaitQueue::Channel(waitPage), Thread::CallbackDrain, key);
1516 (void)reason;
1517 continue;
1518 }
1519 }
1520#else
1521 {
1524 if (it == m_Pages.end()) {
1525 return true;
1526 }
1527 key = it.key();
1528 }
1529#endif
1530 }
1531
1532 // Another caller can win the eviction race after the predicate check.
1533 // Restarting discovers either its in-progress state or the next page.
1534 if (!evict(key, mode, discardedDirtyPages)) {
1536 CachePage* page = m_Pages.lookup(key);
1537 if (mode != EvictionMode::DiscardDirty && page && page->writebackFailed) {
1538 return false;
1539 }
1540 }
1541 }
1542}
1543
1544bool Cache::pin(uintptr_t key) {
1545 if (!ensureUsable("pin")) {
1546 return false;
1547 }
1548
1550
1551 if (!m_PageFilter.contains(key)) {
1552 return false;
1553 }
1554
1555 CachePage* pPage = m_Pages.lookup(key);
1556 if (!pPage) {
1557 return false;
1558 }
1559 if (pPage->evictionState == CachePage::EvictionState::Draining ||
1560 pPage->evictionState == CachePage::EvictionState::Retiring) {
1561 return false;
1562 }
1563
1564 pPage->refcnt++;
1565 promotePage(pPage);
1566
1567 return true;
1568}
1569
1570void Cache::release(uintptr_t key) {
1571 const size_t state = m_ShutdownState;
1572 const bool deferred = state == 4 || state == 5;
1573 if (!deferred && !ensureUsable("release")) {
1574 return;
1575 }
1576
1577 bool shouldEvict = false;
1578 CachePage* releasedPage = nullptr;
1579 {
1581
1582 if (!m_PageFilter.contains(key)) {
1583 return;
1584 }
1585
1586 CachePage* pPage = m_Pages.lookup(key);
1587 if (!pPage || pPage->evictionState == CachePage::EvictionState::Retiring) {
1588 return;
1589 }
1590
1591 assert(pPage->refcnt);
1592 pPage->refcnt--;
1593 releasedPage = pPage;
1594 shouldEvict = !pPage->refcnt;
1595 }
1596
1597#if THREADS
1598 m_EvictionWaiters.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(releasedPage));
1599#endif
1600
1601 // Thread creation can reschedule, so it must happen after dropping the
1602 // cache lock. Eviction rechecks the refcount if the page is pinned again.
1603 if (shouldEvict && !static_cast<size_t>(m_ShutdownState)) {
1604 CacheManager::instance().addCacheRequest(this, true, CacheConstants::PleaseEvict, key);
1605 }
1606}
1607
1608size_t Cache::trim(size_t count) {
1609 if (!ensureUsable("trim")) {
1610 return 0;
1611 }
1612
1613 if (!count)
1614 return 0;
1615
1616 size_t nPages = 0;
1617
1618 // Attempt an LRU compact.
1619 size_t n = 0;
1620 while ((nPages < count) && ((n = lruEvict(true)) > 0)) {
1621 nPages += n;
1622 }
1623
1624 return nPages;
1625}
1626
1627bool Cache::sync(uintptr_t key, bool async) {
1628 if (!ensureUsable("sync")) {
1629 return false;
1630 }
1631
1632#if THREADS
1633 TerminationDeferral terminationDeferral;
1634#endif
1635 if (!m_Callback)
1636 return true;
1637
1638 uintptr_t location = 0;
1639#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1640 writeback_admission_hook_t admissionHook = nullptr;
1641 void* admissionHookMeta = nullptr;
1642#endif
1643 {
1645
1646 if (!m_PageFilter.contains(key)) {
1647 return true;
1648 }
1649
1650 CachePage* pPage = m_Pages.lookup(key);
1651 if (!pPage || pPage->evictionState == CachePage::EvictionState::Draining ||
1652 pPage->evictionState == CachePage::EvictionState::Retiring) {
1653 return false;
1654 }
1655
1656 if (pPage->status != CachePage::Editing && !pPage->callbackActive &&
1657 pPage->evictionState == CachePage::EvictionState::None && !needsWriteback(pPage)) {
1658 return true;
1659 }
1660
1661 ++pPage->refcnt;
1662 ++pPage->writebackPins;
1663 location = pPage->location;
1664 promotePage(pPage);
1665#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1666 admissionHook = m_WritebackAdmissionHook;
1667 admissionHookMeta = m_WritebackAdmissionHookMeta;
1668#endif
1669 }
1670
1671#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
1672 if (admissionHook) {
1673 admissionHook(this, key, admissionHookMeta);
1674 }
1675#endif
1676
1677 if (async) {
1678 return CacheManager::instance().addCacheRequest(this, true, CacheConstants::WriteBack, key,
1679 location, true) != 0;
1680 } else {
1681 uint64_t result = CacheManager::instance().addCacheRequest(
1682 this, false, CacheConstants::WriteBack, key, location, true);
1683 return result == 2;
1684 }
1685}
1686
1688 return syncAll(nullptr, nullptr);
1689}
1690
1691bool Cache::syncAll(writeback_batch_t callback, void* metadata) {
1692#if THREADS
1693 TerminationDeferral terminationDeferral;
1694 OperationBarrier::Lease operation;
1695 if (!m_ManagerOperations.tryAcquire(operation)) {
1696 return false;
1697 }
1698 Thread* currentThread = Processor::information().getCurrentThread();
1699 const bool canWait = currentThread && !CacheManager::instance().callbackContext();
1700#endif
1701 struct Entry {
1702 uintptr_t key;
1703 uintptr_t location;
1704 bool pinned;
1705 };
1706 Vector<Entry> entries;
1707 Vector<uintptr_t> keys;
1708 auto& candidates = m_DirtyTracking == DirtyTracking::Explicit ? m_WritebackPages : m_Pages;
1709 bool snapshotted = false;
1710 // Allocation stays outside the cache lock; bounded retries avoid chasing
1711 // an indefinitely growing cache while holding the object's lifetime.
1712 for (size_t attempt = 0; attempt < 4 && !snapshotted; ++attempt) {
1713 size_t count = 0;
1714 {
1716 if (static_cast<size_t>(m_ShutdownState)) {
1717 return false;
1718 }
1719 if (!m_Callback) {
1720 return true;
1721 }
1722 count = candidates.count();
1723 }
1724 if (!entries.tryReserve(count) || (callback && !keys.tryReserve(count))) {
1725 return false;
1726 }
1727 {
1729 if (candidates.count() > entries.size() || (callback && candidates.count() > keys.size())) {
1730 continue;
1731 }
1732 for (auto it = candidates.begin(); it != candidates.end(); ++it) {
1733#if THREADS
1734 if ((it.value()->callbackOwner && it.value()->callbackOwner == currentThread) ||
1735 (!currentThread && it.value()->callbackActive)) {
1736#else
1737 if (it.value()->callbackActive) {
1738#endif
1739 return false;
1740 }
1741 CachePage* page = it.value();
1742 if (page->evictionState != CachePage::EvictionState::Draining &&
1743 page->evictionState != CachePage::EvictionState::Retiring &&
1744 (page->refcnt == ~size_t{0} || page->writebackPins == ~size_t{0})) {
1745 return false;
1746 }
1747 }
1748 for (auto it = candidates.begin(); it != candidates.end(); ++it) {
1749 CachePage* page = it.value();
1750 const bool pinned = page->evictionState != CachePage::EvictionState::Draining &&
1751 page->evictionState != CachePage::EvictionState::Retiring;
1752 if (pinned) {
1753 ++page->refcnt;
1754 ++page->writebackPins;
1755 }
1756 const Entry entry = {page->key, page->location, pinned};
1757 entries.pushBack(entry);
1758 }
1759 snapshotted = true;
1760 }
1761 }
1762 if (!snapshotted) {
1763 return false;
1764 }
1765
1766 bool succeeded = true;
1767 for (size_t i = 0; i < entries.count(); ++i) {
1768 Entry& entry = entries[i];
1769 // Draining pages cannot be pinned: their retirement waits for pins to
1770 // disappear. Join that operation, then retry if a failed page remains.
1771 while (!entry.pinned) {
1772#if THREADS
1773 auto waitGuard = m_EvictionWaiters.acquire();
1774#endif
1775 CachePage* page = nullptr;
1776 bool busy = false;
1777 {
1779 page = m_Pages.lookup(entry.key);
1780 if (!page) {
1781 break;
1782 }
1783 busy = page->evictionState == CachePage::EvictionState::Draining ||
1784 page->evictionState == CachePage::EvictionState::Retiring;
1785 if (!busy) {
1786 if (page->refcnt == ~size_t{0} || page->writebackPins == ~size_t{0}) {
1787 succeeded = false;
1788 break;
1789 }
1790 ++page->refcnt;
1791 ++page->writebackPins;
1792 entry.location = page->location;
1793 entry.pinned = true;
1794 }
1795#if THREADS
1796 else if (!canWait || page->callbackOwner == currentThread) {
1797 succeeded = false;
1798 break;
1799 }
1800#endif
1801 }
1802 if (busy) {
1803#if THREADS
1804 const WaitQueue::WakeReason reason =
1805 waitGuard.waitForCompletion(WaitQueue::Channel(page), Thread::CallbackDrain, entry.key);
1806 (void)reason;
1807#else
1808 succeeded = false;
1809 break;
1810#endif
1811 }
1812 }
1813 if (entry.pinned) {
1814 if (callback) {
1815 keys.pushBack(entry.key);
1816 } else {
1817 const bool written = writebackPage(entry.key, entry.location, true);
1818 succeeded = written && succeeded;
1819 releaseWriteback(entry.key);
1820 }
1821 entry.pinned = false;
1822 }
1823 }
1824 if (callback && keys.count()) {
1825 // Keep the entire snapshot pinned until the callback's durability barrier.
1826 succeeded = syncBatchInternal(&keys[0], keys.count(), callback, metadata, true) && succeeded;
1827 for (uintptr_t key : keys) {
1828 releaseWriteback(key);
1829 }
1830 }
1831 return succeeded;
1832}
1833
1834bool Cache::syncBatch(const uintptr_t* keys, size_t count, writeback_batch_t callback,
1835 void* metadata) {
1836 return syncBatchInternal(keys, count, callback, metadata, false);
1837}
1838
1839bool Cache::DirectWritebackLease::acquire(Cache& cache, uintptr_t key, uintptr_t location) {
1840 if (m_Cache || !cache.ensureUsable("DirectWritebackLease")) {
1841 return false;
1842 }
1843 {
1844 LockGuard<Spinlock> guard(cache.m_Lock);
1845 CachePage* page = cache.m_Pages.lookup(key);
1846 if (!page || page->location != location || !page->callbackActive ||
1847 page->status == CachePage::Editing || page->mutableLoans || page->externallyWritable ||
1848 page->directWriteback) {
1849 return false;
1850 }
1851 page->directWriteback = true;
1852 m_Cache = &cache;
1853 m_Page = page;
1854 }
1855
1856#if !STANDALONE_CACHE
1857 physical_uintptr_t physical = 0;
1858 size_t flags = 0;
1859 auto& addressSpace = VirtualAddressSpace::getKernelAddressSpace();
1860 if (addressSpace.getMapping(reinterpret_cast<void*>(location), physical, flags) && physical &&
1861 !(physical & (CachePageSize - 1)) &&
1866 m_Physical = physical;
1867 return true;
1868 }
1869#endif
1870 release();
1871 return false;
1872}
1873
1874void Cache::DirectWritebackLease::release() {
1875 if (!m_Cache) {
1876 return;
1877 }
1878 Cache* cache = m_Cache;
1879 CachePage* page = m_Page;
1880 {
1881 LockGuard<Spinlock> guard(cache->m_Lock);
1882 assert(page->directWriteback);
1883 page->directWriteback = false;
1884 }
1885 m_Cache = nullptr;
1886 m_Page = nullptr;
1887 m_Physical = 0;
1888#if THREADS
1889 cache->m_EvictionWaiters.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(page));
1890#endif
1891}
1892
1893// syncAll owns snapshot pins before retirement can start draining these pages.
1894bool Cache::syncBatchInternal(const uintptr_t* keys, size_t count, writeback_batch_t callback,
1895 void* metadata, bool snapshot) {
1896 if (!ensureUsable("syncBatch") || (!snapshot && count > MaxWritebackPages) ||
1897 (count && (!keys || !callback))) {
1898 return false;
1899 }
1900 if (!count)
1901 return true;
1902 // Snapshot keys come from the cache's unique-key tree.
1903 for (size_t i = 0; !snapshot && i < count; ++i) {
1904 for (size_t j = 0; j < i; ++j) {
1905 if (keys[i] == keys[j])
1906 return false;
1907 }
1908 }
1909#if THREADS
1910 TerminationDeferral terminationDeferral;
1911 OperationBarrier::Lease operation;
1912 if (!m_ManagerOperations.tryAcquire(operation))
1913 return false;
1914 Thread* currentThread = Processor::information().getCurrentThread();
1915 const bool canWait = currentThread && !CacheManager::instance().callbackContext();
1916#endif
1917 struct Submission {
1918 CachePage* page;
1919 uint64_t checksum[2];
1920 uint64_t generation;
1921 bool checksumTracking;
1922 };
1923 Submission smallSubmissions[MaxWritebackPages] = {};
1924 WritebackPage smallWrites[MaxWritebackPages] = {};
1925 UniqueArray<Submission> submissionsOwner;
1926 UniqueArray<WritebackPage> writesOwner;
1927 Submission* submissions = smallSubmissions;
1928 WritebackPage* writes = smallWrites;
1929 if (count > MaxWritebackPages) {
1930 submissionsOwner = UniqueArray<Submission>::allocate(count);
1931 writesOwner = UniqueArray<WritebackPage>::allocate(count);
1932 if (!submissionsOwner || !writesOwner) {
1933 return false;
1934 }
1935 submissions = submissionsOwner.get();
1936 writes = writesOwner.get();
1937 }
1938 size_t writeCount = 0;
1939 while (true) {
1940#if THREADS
1941 auto waitGuard = m_EvictionWaiters.acquire();
1942#endif
1943 CachePage* busy = nullptr;
1944 {
1946 if (static_cast<size_t>(m_ShutdownState) || !m_Callback)
1947 return false;
1948 // Claim all pages together: two overlapping batches must never each own
1949 // a prefix while waiting for the other batch's remaining callbacks.
1950 for (size_t i = 0; i < count; ++i) {
1951 CachePage* page = m_Pages.lookup(keys[i]);
1952 if (!page || page->status == CachePage::Editing ||
1953 (page->evictionState == CachePage::EvictionState::Draining && !snapshot) ||
1954 page->evictionState == CachePage::EvictionState::Retiring ||
1955 page->refcnt == ~size_t{0} || page->writebackPins == ~size_t{0}) {
1956 return false;
1957 }
1958 if (page->callbackActive || page->evictionState == CachePage::EvictionState::WriteBack) {
1959#if THREADS
1960 if (!canWait || page->callbackOwner == currentThread)
1961 return false;
1962#else
1963 return false;
1964#endif
1965 busy = page;
1966 }
1967 submissions[i].page = page;
1968 }
1969 if (!busy) {
1970 writeCount = 0;
1971 for (size_t i = 0; i < count; ++i) {
1972 CachePage* page = submissions[i].page;
1973 if (!needsWriteback(page)) {
1974 continue;
1975 }
1976 ++page->refcnt;
1977 ++page->writebackPins;
1978 page->callbackActive = true;
1979#if THREADS
1980 page->callbackOwner = currentThread;
1981#endif
1982 submissions[writeCount].page = page;
1983 writes[writeCount] = {keys[i], page->location};
1984 submissions[writeCount].generation = page->mutationGeneration;
1985 submissions[writeCount].checksumTracking = tracksChecksum(page);
1986 ++writeCount;
1987 updateWritebackIndex(page);
1988 promotePage(page);
1989 }
1990 }
1991 }
1992 if (!busy)
1993 break;
1994#if THREADS
1995 const auto reason = waitGuard.waitForCompletion(WaitQueue::Channel(busy), Thread::CallbackDrain,
1996 reinterpret_cast<uintptr_t>(busy));
1997 (void)reason;
1998#endif
1999 }
2000 if (!writeCount)
2001 return true;
2002 for (size_t i = 0; i < writeCount; ++i) {
2003 if (submissions[i].checksumTracking)
2004 checksum(reinterpret_cast<const void*>(writes[i].location), CachePageSize,
2005 submissions[i].checksum);
2006 }
2007 Metrics::add(Metrics::CacheWritebackPages, writeCount);
2008 const bool succeeded = callback(writes, writeCount, metadata);
2009 if (!succeeded) {
2010 Metrics::add(Metrics::CacheWritebackFailures, writeCount);
2011 }
2012 {
2014 for (size_t i = 0; i < writeCount; ++i) {
2015 CachePage* page = submissions[i].page;
2016 page->writebackFailed = !succeeded;
2017 if (succeeded) {
2018 page->writtenGeneration = submissions[i].generation;
2019 if (submissions[i].checksumTracking) {
2020 page->checksum[0] = submissions[i].checksum[0];
2021 page->checksum[1] = submissions[i].checksum[1];
2022 }
2023 if (page->status == CachePage::ChecksumChanging)
2024 page->status = CachePage::ChecksumStable;
2025 }
2026 page->callbackActive = false;
2027#if THREADS
2028 page->callbackOwner = nullptr;
2029#endif
2030 updateWritebackIndex(page);
2031 }
2032 }
2033 for (size_t i = 0; i < writeCount; ++i) {
2034#if THREADS
2035 m_EvictionWaiters.wakeAll(WaitQueue::WakeReason::Signalled,
2036 WaitQueue::Channel(submissions[i].page));
2037#endif
2038 releaseWriteback(writes[i].key);
2039 }
2040 return succeeded;
2041}
2042
2043bool Cache::writebackPage(uintptr_t key, uintptr_t location, bool wait) {
2044 CachePage* page = nullptr;
2045 writeback_t callback = nullptr;
2046 void* callbackMeta = nullptr;
2047 uint64_t submittedGeneration = 0;
2048 bool submittedChecksumTracking = false;
2049#if THREADS
2050 Thread* currentThread = Processor::information().getCurrentThread();
2051 const bool canWait = wait && currentThread && !CacheManager::instance().callbackContext();
2052#else
2053 (void)wait;
2054#endif
2055 while (true) {
2056#if THREADS
2057 auto waitGuard = m_EvictionWaiters.acquire();
2058#endif
2059 {
2061 page = m_Pages.lookup(key);
2062 if (!page || page->location != location || !m_Callback) {
2063 return false;
2064 }
2065 if (page->status == CachePage::Editing) {
2066 return false;
2067 }
2068 if (!page->callbackActive && page->evictionState != CachePage::EvictionState::WriteBack) {
2069 // A durable batch can supersede a timer request already in the queue.
2070 if (!needsWriteback(page)) {
2071 return true;
2072 }
2073 // A previously admitted writeback pin is allowed to finish while a
2074 // retirement waits in Draining for precisely these pins to disappear.
2075 page->callbackActive = true;
2076#if THREADS
2077 page->callbackOwner = currentThread;
2078#endif
2079 callback = m_Callback;
2081 submittedGeneration = page->mutationGeneration;
2082 submittedChecksumTracking = tracksChecksum(page);
2083 updateWritebackIndex(page);
2084 break;
2085 }
2086#if THREADS
2087 if (!canWait || page->callbackOwner == currentThread) {
2088#endif
2089 return false;
2090#if THREADS
2091 }
2092#endif
2093 }
2094#if THREADS
2095 const WaitQueue::WakeReason reason =
2096 waitGuard.waitForCompletion(WaitQueue::Channel(page), Thread::CallbackDrain, key);
2097 (void)reason;
2098#endif
2099 }
2100
2101 uint64_t submittedChecksum[2] = {};
2102 if (submittedChecksumTracking)
2103 checksum(reinterpret_cast<const void*>(location), CachePageSize, submittedChecksum);
2104 Metrics::increment(Metrics::CacheWritebackPages);
2105 const bool succeeded = callback(CacheConstants::WriteBack, key, location, callbackMeta);
2106 if (!succeeded) {
2107 Metrics::increment(Metrics::CacheWritebackFailures);
2108 }
2109 {
2111 page->writebackFailed = !succeeded;
2112 if (succeeded) {
2113 page->writtenGeneration = submittedGeneration;
2114 if (submittedChecksumTracking) {
2115 page->checksum[0] = submittedChecksum[0];
2116 page->checksum[1] = submittedChecksum[1];
2117 }
2118 // Otherwise the stable-checksum scan schedules this completed write again.
2119 if (page->status == CachePage::ChecksumChanging) {
2120 page->status = CachePage::ChecksumStable;
2121 }
2122 }
2123 page->callbackActive = false;
2124#if THREADS
2125 page->callbackOwner = nullptr;
2126#endif
2127 updateWritebackIndex(page);
2128 }
2129#if THREADS
2130 m_EvictionWaiters.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(page));
2131#endif
2132 return succeeded;
2133}
2134
2135void Cache::markDirty(uintptr_t key) {
2136 if (!ensureUsable("markDirty")) {
2137 return;
2138 }
2140 CachePage* page = m_Pages.lookup(key);
2141 if (page) {
2142 recordMutation(page);
2143 }
2144}
2145
2147 if (!ensureUsable("markExternallyWritable"))
2148 return;
2150 CachePage* page = m_Pages.lookup(key);
2151 if (!page || page->externallyWritable)
2152 return;
2153 const bool tracked = tracksChecksum(page);
2154 page->externallyWritable = true;
2155 if (!tracked)
2156 calculateChecksum(page);
2157 updateWritebackIndex(page);
2158}
2159
2160bool Cache::beginMutableLoan(uintptr_t key) {
2161 if (!ensureUsable("beginMutableLoan")) {
2162 return false;
2163 }
2164 while (true) {
2165#if THREADS
2166 auto waitGuard = m_EvictionWaiters.acquire();
2167#endif
2168 CachePage* page = nullptr;
2169 {
2171 page = m_Pages.lookup(key);
2172 if (!page || page->evictionState == CachePage::EvictionState::Retiring ||
2173 page->mutableLoans == ~size_t{0}) {
2174 return false;
2175 }
2176 if (!page->directWriteback) {
2177 const bool tracked = tracksChecksum(page);
2178 ++page->mutableLoans;
2179 if (!tracked) {
2180 calculateChecksum(page);
2181 // An existing writeback must not settle modifications made by this loan.
2182 if (page->callbackActive) {
2183 recordMutation(page);
2184 }
2185 }
2186 updateWritebackIndex(page);
2187 return true;
2188 }
2189#if THREADS
2190 Thread* current = Processor::information().getCurrentThread();
2191 if (!current || page->callbackOwner == current) {
2192 return false;
2193 }
2194#else
2195 return false;
2196#endif
2197 }
2198#if THREADS
2199 const auto reason =
2200 waitGuard.waitForCompletion(WaitQueue::Channel(page), Thread::CallbackDrain, key);
2201 (void)reason;
2202#endif
2203 }
2204}
2205
2206void Cache::endMutableLoan(uintptr_t key) {
2207 const size_t state = m_ShutdownState;
2208 if (state != 4 && state != 5 && !ensureUsable("endMutableLoan")) {
2209 return;
2210 }
2212 CachePage* page = m_Pages.lookup(key);
2213 assert(page && page->mutableLoans);
2214 if (!page || !page->mutableLoans)
2215 return;
2216
2217 if (page->mutableLoans == 1) {
2218 // An active callback may publish its older checksum after tracking ends.
2219 // A newer mutation generation keeps that completion from losing changes.
2220 if (page->callbackActive ||
2221 (!page->writebackFailed && page->mutationGeneration == page->writtenGeneration &&
2222 !verifyChecksum(page)))
2223 recordMutation(page);
2224 }
2225 --page->mutableLoans;
2226 updateWritebackIndex(page);
2227}
2228
2229void Cache::triggerChecksum(uintptr_t key) {
2230 if (!ensureUsable("triggerChecksum")) {
2231 return;
2232 }
2233
2235
2236 if (!m_PageFilter.contains(key)) {
2237 return;
2238 }
2239
2240 CachePage* pPage = m_Pages.lookup(key);
2241 if (!pPage) {
2242 return;
2243 }
2244
2245 calculateChecksum(pPage);
2246}
2247
2248void Cache::timer(uint64_t delta) {
2249 if (!ensureUsable("timer")) {
2250 return;
2251 }
2252
2253#if THREADS
2254 TerminationDeferral terminationDeferral;
2255#endif
2256 {
2258 const uint64_t maximum = ~static_cast<uint64_t>(0);
2259 m_Nanoseconds = delta > (maximum - m_Nanoseconds) ? maximum : m_Nanoseconds + delta;
2260 if (LIKELY(m_Nanoseconds < (CACHE_WRITEBACK_PERIOD * 1000000ULL))) {
2261 return;
2262 }
2263 if (UNLIKELY(m_Callback == 0)) {
2264 return;
2265 }
2266 if (UNLIKELY(m_bInCritical == 1)) {
2267 // Missed - don't repeatedly scan while the cache is changing.
2268 m_Nanoseconds = 0;
2269 return;
2270 }
2271 m_Nanoseconds = 0;
2272 ++m_WritebackEpoch;
2273 }
2274
2275 // Bound interrupt-disabled work to one page, including clean prefixes. A
2276 // copied key also lets callbacks mutate the tree without invalidating a scan.
2277 BackgroundWriteback* batch = nullptr;
2278 auto submitBatch = [&] {
2279 if (batch) {
2280 CacheManager::instance().addCacheRequest(this, true, CacheConstants::WriteBack,
2281 reinterpret_cast<uintptr_t>(batch), 0, false, true);
2282 batch = nullptr;
2283 }
2284 };
2285 uintptr_t nextKey = 0;
2286 auto& candidates = m_DirtyTracking == DirtyTracking::Explicit ? m_WritebackPages : m_Pages;
2287 bool finished = false;
2288 while (!finished) {
2289 bool queueWriteback = false;
2290 uintptr_t key = 0;
2291 uintptr_t location = 0;
2292#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
2293 writeback_admission_hook_t admissionHook = nullptr;
2294 void* admissionHookMeta = nullptr;
2295#endif
2296 {
2298 if (!m_Callback || m_bInCritical == 1) {
2299 break;
2300 }
2301
2302 CachePage* page = nullptr;
2303 if (!candidates.lowerBound(nextKey, key, page)) {
2304 break;
2305 }
2306 finished = key == ~uintptr_t{0};
2307 if (!finished) {
2308 nextKey = key + 1;
2309 }
2310 if (page->writebackEpoch == m_WritebackEpoch) {
2311 continue;
2312 }
2313 page->writebackEpoch = m_WritebackEpoch;
2314 if (page->evictionState != CachePage::EvictionState::None) {
2315 continue;
2316 }
2317 // A queued write owns the retry until it completes. Rescanning it can
2318 // otherwise enqueue another write on every timer tick.
2319 if (page->writebackPins) {
2320 continue;
2321 }
2322 if (page->status == CachePage::Editing) {
2323 continue;
2324 }
2325 if (page->status == CachePage::EditTransition) {
2326 promotePage(page);
2327 page->status = CachePage::ChecksumStable;
2328 continue;
2329 }
2330 if (page->writebackFailed || page->mutationGeneration != page->writtenGeneration) {
2331 // A stable checksum cannot make an unsuccessful backend write clean.
2332 } else if (!tracksChecksum(page)) {
2333 continue;
2334 } else if (page->status == CachePage::ChecksumChanging) {
2335 if (!verifyChecksum(page, true)) {
2336 continue;
2337 }
2338 page->status = CachePage::ChecksumStable;
2339 } else if (page->status == CachePage::ChecksumStable) {
2340 if (!verifyChecksum(page, true)) {
2341 page->status = CachePage::ChecksumChanging;
2342 recordMutation(page);
2343 }
2344 continue;
2345 } else {
2346 ERROR("Unknown page status!");
2347 continue;
2348 }
2349
2350 promotePage(page);
2351 ++page->refcnt;
2352 ++page->writebackPins;
2353 location = page->location;
2354 queueWriteback = true;
2355#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
2356 admissionHook = m_WritebackAdmissionHook;
2357 admissionHookMeta = m_WritebackAdmissionHookMeta;
2358#endif
2359 }
2360
2361 if (!queueWriteback) {
2362 continue;
2363 }
2364
2365#if defined(CACHE_TRACE_WRITEBACK) && CACHE_TRACE_WRITEBACK
2366 NOTICE("Cache " << Hex << reinterpret_cast<uintptr_t>(this) << ": queue writeback key=" << key
2367 << ", page=" << location);
2368#endif
2369#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
2370 if (admissionHook) {
2371 admissionHook(this, key, admissionHookMeta);
2372 }
2373#endif
2374 if (m_BackgroundWriteback) {
2375 if (!batch)
2376 batch = new BackgroundWriteback;
2377 if (batch) {
2378 batch->keys[batch->count++] = key;
2379 if (batch->count == MaxWritebackPages)
2380 submitBatch();
2381 continue;
2382 }
2383 }
2384 CacheManager::instance().addCacheRequest(this, true, CacheConstants::WriteBack, key, location,
2385 true);
2386 }
2387 submitBatch();
2388}
2389
2390void Cache::setCallback(Cache::writeback_t newCallback, void* meta) {
2391 if (static_cast<size_t>(m_ShutdownState) != 0) {
2392 FATAL("Cache callback installation requires an active Cache");
2393 return;
2394 }
2395
2397 if (!newCallback) {
2398 FATAL("Cache callbacks cannot be cleared after publication");
2399 return;
2400 }
2401 if (m_Callback) {
2402 FATAL("Cache callbacks are immutable after installation");
2403 return;
2404 }
2405 if (m_Pages.count()) {
2406 FATAL("Cache callbacks must be installed before inserting pages");
2407 return;
2408 }
2409 m_Callback = newCallback;
2410 m_CallbackMeta = meta;
2411 __atomic_store_n(&m_PeriodicTimerEnabled, true, __ATOMIC_RELEASE);
2412}
2413
2414void Cache::setBackgroundWriteback(writeback_batch_t callback) {
2416 if (static_cast<size_t>(m_ShutdownState) || !m_Callback || m_Pages.count() ||
2417 m_BackgroundWriteback || !callback) {
2418 FATAL("Background writeback must be installed before publishing cache pages");
2419 return;
2420 }
2421 m_BackgroundWriteback = callback;
2422}
2423
2424void Cache::releaseBackgroundWriteback(BackgroundWriteback* batch) {
2425 for (size_t i = 0; i < batch->count; ++i)
2426 releaseWriteback(batch->keys[i]);
2427 delete batch;
2428}
2429
2430void Cache::setDirtyTracking(DirtyTracking tracking) {
2431 if (!ensureUsable("setDirtyTracking"))
2432 return;
2434 if (m_Pages.count()) {
2435 FATAL("Cache dirty tracking must be selected before inserting pages");
2436 return;
2437 }
2438 m_DirtyTracking = tracking;
2439}
2440
2441#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
2442void Cache::setWritebackAdmissionHookForTest(writeback_admission_hook_t hook, void* meta) {
2444 m_WritebackAdmissionHook = hook;
2445 m_WritebackAdmissionHookMeta = meta;
2446}
2447#endif
2448
2449uint64_t Cache::executeRequest(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4, uint64_t p5,
2450 uint64_t p6, uint64_t p7, uint64_t p8) {
2451 if (p7 == 2) {
2452 auto* batch = reinterpret_cast<BackgroundWriteback*>(p3);
2453 const bool succeeded =
2454 syncBatchInternal(batch->keys, batch->count, m_BackgroundWriteback, m_CallbackMeta, true);
2455 releaseBackgroundWriteback(batch);
2456 return succeeded ? 2 : 0;
2457 }
2458
2459 // Eviction request?
2460 if (static_cast<CacheConstants::CallbackCause>(p2) == CacheConstants::PleaseEvict) {
2461 evict(p3);
2462 return 1;
2463 }
2464
2465 // sync() transfers a pin to its request before dropping the cache lock.
2466 // Timer-driven requests acquire their pin here.
2467 if (!p5) {
2469 CachePage* page = m_Pages.lookup(p3);
2470 if (!page || page->evictionState == CachePage::EvictionState::Draining ||
2471 page->evictionState == CachePage::EvictionState::Retiring) {
2472 return 0;
2473 }
2474 ++page->refcnt;
2475 ++page->writebackPins;
2476 }
2477
2478 // Never block the shared worker behind a direct callback which may itself
2479 // submit work to CacheManager. A rejected request retains dirty data.
2480 const bool succeeded = writebackPage(p3, p4, false);
2481
2482 // Unpin page, writeback complete
2483 releaseWriteback(p3);
2484
2485 return succeeded ? 2 : 0;
2486}
2487
2488size_t Cache::lruEvict(bool force) {
2489#if STANDALONE_CACHE
2490 return 0;
2491#else
2492 // Do we have memory pressure - do we need to do an LRU eviction?
2493 if (!force && PhysicalMemoryManager::instance().freePageCount() >=
2494 MemoryPressureManager::getLowWatermark()) {
2495 return 0;
2496 }
2497
2498 uintptr_t key = 0;
2499 {
2501 if (!(m_pLruHead && m_pLruTail)) {
2502 return 0;
2503 }
2504 key = m_pLruTail->key;
2505 }
2506
2507 if (evict(key)) {
2508 return 1;
2509 }
2510
2511 {
2513 CachePage* page = nullptr;
2514 if (m_PageFilter.contains(key)) {
2515 page = m_Pages.lookup(key);
2516 }
2517 if (page && page->evictionState == CachePage::EvictionState::None) {
2518 // Avoid repeatedly selecting a pinned page under pressure.
2519 promotePage(page);
2520 }
2521 }
2522
2523 return 0;
2524#endif
2525}
2526
2528 pPage->pPrev = 0;
2529 pPage->pNext = m_pLruHead;
2530 if (m_pLruHead)
2531 m_pLruHead->pPrev = pPage;
2532 m_pLruHead = pPage;
2533 if (!m_pLruTail)
2534 m_pLruTail = m_pLruHead;
2535}
2536
2538 unlinkPage(pPage);
2539 linkPage(pPage);
2540}
2541
2543 if (pPage->pPrev)
2544 pPage->pPrev->pNext = pPage->pNext;
2545 if (pPage->pNext)
2546 pPage->pNext->pPrev = pPage->pPrev;
2547 if (pPage == m_pLruTail)
2548 m_pLruTail = pPage->pPrev;
2549 if (pPage == m_pLruHead)
2550 m_pLruHead = pPage->pNext;
2551}
2552
2554 if (!tracksChecksum(pPage))
2555 return;
2556 void* buffer = reinterpret_cast<void*>(pPage->location);
2557 checksum(buffer, CachePageSize, pPage->checksum);
2558}
2559
2560bool Cache::verifyChecksum(CachePage* pPage, bool replace) {
2561 if (!tracksChecksum(pPage))
2562 return true;
2563 void* buffer = reinterpret_cast<void*>(pPage->location);
2564
2565 uint64_t new_checksum[2];
2566 checksum(buffer, CachePageSize, new_checksum);
2567
2568 bool result = pPage->checkZeroChecksum() || pPage->checkChecksum(new_checksum);
2569 if (replace) {
2570 pPage->checksum[0] = new_checksum[0];
2571 pPage->checksum[1] = new_checksum[1];
2572 }
2573
2574 return result;
2575}
2576
2577bool Cache::tracksChecksum(const CachePage* page) const {
2578 return m_DirtyTracking == DirtyTracking::Checksum || page->externallyWritable ||
2579 page->mutableLoans;
2580}
2581
2582bool Cache::needsWriteback(CachePage* page) {
2583 return page->writebackFailed || page->mutationGeneration != page->writtenGeneration ||
2584 !verifyChecksum(page);
2585}
2586
2587void Cache::updateWritebackIndex(CachePage* page) {
2588 if (m_DirtyTracking != DirtyTracking::Explicit)
2589 return;
2590 const bool candidate = page->externallyWritable || page->mutableLoans || page->writebackFailed ||
2591 page->mutationGeneration != page->writtenGeneration ||
2592 page->status == CachePage::Editing || page->callbackActive;
2593 if (candidate && !page->writebackIndexed) {
2594 m_WritebackPages.insert(page->key, page);
2595 page->writebackIndexed = true;
2596 } else if (!candidate && page->writebackIndexed) {
2597 m_WritebackPages.remove(page->key);
2598 page->writebackIndexed = false;
2599 }
2600}
2601
2602void Cache::recordMutation(CachePage* page) {
2603 ++page->mutationGeneration;
2604 if (page->mutationGeneration == page->writtenGeneration)
2605 ++page->mutationGeneration;
2606 updateWritebackIndex(page);
2607}
2608
2609void Cache::checksum(const void* data, size_t len, uint64_t out[2]) {
2610 MurmurHash3_x64_128(data, len, 0, out);
2611}
2612
2613void Cache::markEditing(uintptr_t key, size_t length) {
2614 if (!ensureUsable("markEditing")) {
2615 return;
2616 }
2617
2618 if (length && (length % CachePageSize)) {
2619 WARNING("Cache::markEditing called with a length that isn't page-aligned");
2620 return;
2621 }
2622
2623 if (!length) {
2624 length = CachePageSize;
2625 }
2626
2628
2629 size_t nPages = length / CachePageSize;
2630
2631 for (size_t page = 0; page < nPages; page++) {
2632 if (!m_PageFilter.contains(key + (page * CachePageSize))) {
2633 continue;
2634 }
2635
2636 CachePage* pPage = m_Pages.lookup(key + (page * CachePageSize));
2637 if (!pPage) {
2638 continue;
2639 }
2640
2641 pPage->status = CachePage::Editing;
2642 updateWritebackIndex(pPage);
2643 }
2644}
2645
2646void Cache::markNoLongerEditing(uintptr_t key, size_t length) {
2647 if (!ensureUsable("markNoLongerEditing")) {
2648 return;
2649 }
2650
2651 if (length && (length % CachePageSize)) {
2652 WARNING("Cache::markNoLongerEditing called with a length that isn't page-aligned");
2653 return;
2654 }
2655
2656 if (!length) {
2657 length = CachePageSize;
2658 }
2659
2661
2662 size_t nPages = length / CachePageSize;
2663
2664 for (size_t page = 0; page < nPages; page++) {
2665 if (!m_PageFilter.contains(key + (page * CachePageSize))) {
2666 continue;
2667 }
2668
2669 CachePage* pPage = m_Pages.lookup(key + (page * CachePageSize));
2670 if (!pPage) {
2671 continue;
2672 }
2673
2674 pPage->status = tracksChecksum(pPage) ? CachePage::EditTransition : CachePage::ChecksumStable;
2675
2676 // We have to checksum here as a write could happen between now and the
2677 // actual handling of the EditTransition, which would lead to some pages
2678 // potentially failing to complete a writeback (not good).
2679 calculateChecksum(pPage);
2680 updateWritebackIndex(pPage);
2681 }
2682}
2683
2684CachePageGuard::CachePageGuard(Cache& cache, uintptr_t location)
2685 : m_Cache(cache), m_Location(location) {}
2686
2687CachePageGuard::~CachePageGuard() {
2688 m_Cache.release(m_Location);
2689}
2690
2691bool Cache::CachePage::checkChecksum(uint64_t other[2]) const {
2692 return checksum[0] == other[0] && checksum[1] == other[1];
2693}
2694
2696 return checksum[0] == 0 && checksum[1] == 0;
2697}
uint64_t cacheGenerationWatermark()
Definition Cache.cc:427
bool acquireNextCache(uint64_t afterId, uint64_t maximumId, Cache *&cache, uint64_t &cacheId, OperationBarrier::Lease &lease, bool timersOnly=false)
Definition Cache.cc:412
Mutex m_CachesLock
Definition Cache.h:191
bool acquireCache(Cache *cache, uint64_t &generation, OperationBarrier::Lease &lease)
Definition Cache.cc:394
uint64_t m_NextCacheId
Definition Cache.h:185
MUST_USE_RESULT bool shutdown()
Definition Cache.cc:127
void initialise()
Definition Cache.cc:193
bool takeTimerStamp(TimerStamp &stamp)
Definition Cache.cc:361
Atomic< size_t > m_TerminalState
Definition Cache.h:203
bool m_bActive
Definition Cache.h:199
virtual void cancelRequest(const Request &request)
Definition Cache.cc:522
bool trimAll(size_t count=1)
Definition Cache.cc:261
virtual void timer(uint64_t delta)
Definition Cache.cc:297
uint64_t addCacheRequest(Cache *cache, bool asynchronous, CacheConstants::CallbackCause cause, uintptr_t key, uintptr_t location=0, bool transferredPin=false, bool batch=false)
Definition Cache.cc:433
virtual uint64_t executeRequest(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4, uint64_t p5, uint64_t p6, uint64_t p7, uint64_t p8)
Definition Cache.cc:489
bool findNextCache(uint64_t afterId, uint64_t maximumId, Cache *&cache, uint64_t &cacheId, bool timersOnly=false)
Definition Cache.cc:379
Definition Cache.h:207
void linkPage(CachePage *pPage)
Definition Cache.cc:2527
bool exists(uintptr_t key, size_t length)
Definition Cache.cc:1098
OperationBarrier m_ManagerOperations
Definition Cache.h:776
size_t lruEvict(bool force=false)
Definition Cache.cc:2488
MUST_USE_RESULT bool discardEditing(uintptr_t key)
Definition Cache.cc:1129
void promotePage(CachePage *pPage)
Definition Cache.cc:2537
size_t read(uintptr_t offset, size_t length, uintptr_t buffer, bool(*prepare)(uintptr_t, size_t)=nullptr)
Definition Cache.cc:769
void setBackgroundWriteback(writeback_batch_t callback)
Definition Cache.cc:2414
static Spinlock m_AllocatorLock
Definition Cache.h:766
MUST_USE_RESULT bool retireWriteback(uintptr_t key, retirement_writeback_t callback, void *meta)
Definition Cache.cc:1336
virtual void timer(uint64_t delta)
Definition Cache.cc:2248
bool shutdown(ShutdownMode mode=ShutdownMode::WriteBack)
Definition Cache.cc:663
bool writebackPage(uintptr_t key, uintptr_t location, bool wait)
Definition Cache.cc:2043
MUST_USE_RESULT bool syncAll()
Definition Cache.cc:1687
void setCallback(writeback_t newCallback, void *meta)
Definition Cache.cc:2390
bool evict(uintptr_t key)
Definition Cache.cc:1122
void release(uintptr_t key)
Definition Cache.cc:1570
void unlinkPage(CachePage *pPage)
Definition Cache.cc:2542
MUST_USE_RESULT bool lookupStable(uintptr_t key, uintptr_t &location, bool wait=false)
Definition Cache.cc:844
Tree< uintptr_t, CachePage * > m_Pages
Definition Cache.h:746
bool(* writeback_t)(CacheConstants::CallbackCause cause, uintptr_t loc, uintptr_t page, void *meta)
Definition Cache.h:289
bool verifyChecksum(CachePage *pPage, bool replace=false)
Definition Cache.cc:2560
uint64_t m_Nanoseconds
Definition Cache.h:790
uintptr_t insert(uintptr_t key, bool *alreadyExisted=nullptr)
Definition Cache.cc:890
WaitQueue m_EvictionWaiters
Definition Cache.h:773
writeback_t m_Callback
Definition Cache.h:785
virtual uint64_t executeRequest(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4, uint64_t p5, uint64_t p6, uint64_t p7, uint64_t p8)
Definition Cache.cc:2449
bool empty()
Definition Cache.cc:1467
void markEditing(uintptr_t key, size_t length=0)
Definition Cache.cc:2613
size_t trim(size_t count=1)
Definition Cache.cc:1608
bool finishRetirement(CachePage *page, writeback_t callback, void *callbackMeta)
Definition Cache.cc:1282
void waitForPageEviction(uintptr_t key)
Definition Cache.cc:713
size_t m_PageConstraints
Definition Cache.h:804
MUST_USE_RESULT bool beginMutableLoan(uintptr_t key)
Definition Cache.cc:2160
MUST_USE_RESULT bool syncBatch(const uintptr_t *keys, size_t count, writeback_batch_t callback, void *metadata)
Definition Cache.cc:1834
CachePage * m_pLruHead
Definition Cache.h:758
BloomFilter< uintptr_t > m_PageFilter
Definition Cache.h:753
Atomic< size_t > m_bInCritical
Definition Cache.h:797
static MemoryAllocator m_Allocator
Definition Cache.h:763
void markExternallyWritable(uintptr_t key)
Definition Cache.cc:2146
void markDirty(uintptr_t key)
Definition Cache.cc:2135
void triggerChecksum(uintptr_t key)
Definition Cache.cc:2229
Atomic< size_t > m_ShutdownState
Definition Cache.h:801
bool sync(uintptr_t key, bool async)
Definition Cache.cc:1627
Tree< uintptr_t, CachePage * > m_WritebackPages
Definition Cache.h:749
bool ensureUsable(const char *operation) const
Definition Cache.cc:700
void markNoLongerEditing(uintptr_t key, size_t length=0)
Definition Cache.cc:2646
uintptr_t lookup(uintptr_t key)
Definition Cache.cc:737
void checksum(const void *data, size_t len, uint64_t out[2])
Definition Cache.cc:2609
Spinlock m_Lock
Definition Cache.h:769
void setDirtyTracking(DirtyTracking tracking)
Definition Cache.cc:2430
bool map(uintptr_t virt) const
Definition Cache.cc:1086
MUST_USE_RESULT bool pin(uintptr_t key)
Definition Cache.cc:1544
void calculateChecksum(CachePage *pPage)
Definition Cache.cc:2553
void * m_CallbackMeta
Definition Cache.h:794
uint64_t m_ManagerId
Definition Cache.h:780
virtual Timer * getTimer()=0
bool isOwnedByCurrentThread() const
Definition Mutex.cc:30
MUST_USE_RESULT bool tryAcquire(Lease &lease)
virtual size_t freePageCount() const
virtual physical_uintptr_t allocatePage(size_t pageConstraints=0)=0
static PhysicalMemoryManager & instance()
virtual void freePage(physical_uintptr_t page)=0
Process * getParent()
Definition Process.h:620
static ProcessorInformation & information()
bool allocate(T length, T &address)
Definition RangeList.h:318
void free(T address, T length, bool merge=true)
Definition RangeList.h:157
Mutex m_LifecycleMutex
virtual void destroy()
virtual void initialise()
MUST_USE_RESULT uint64_t addRequest(size_t priority, uint64_t p1=0, uint64_t p2=0, uint64_t p3=0, uint64_t p4=0, uint64_t p5=0, uint64_t p6=0, uint64_t p7=0, uint64_t p8=0)
uint64_t addAsyncRequest(size_t priority, uint64_t p1=0, uint64_t p2=0, uint64_t p3=0, uint64_t p4=0, uint64_t p5=0, uint64_t p6=0, uint64_t p7=0, uint64_t p8=0)
bool callbackActiveOnCurrentThread() const
void release()
Definition Spinlock.cc:168
bool acquire(bool recurse=false, bool safe=true)
Definition Spinlock.cc:36
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40
bool joinForCompletion()
Definition Thread.cc:2750
virtual bool supportsDeadlines() const
virtual bool armHandler(TimerHandler *, uint64_t)
An iterator applicable for many data structures.
Definition Iterator.h:147
Iterator begin()
Definition Tree.h:402
void remove(const K &key)
Definition Tree.h:301
bool lowerBound(const K &key, K &foundKey, E &foundValue) const
Definition Tree.h:239
E lookup(const K &key) const
Definition Tree.h:193
void insert(const K &key, const E &value)
Definition Tree.h:149
Iterator end()
Definition Tree.h:427
size_t count() const
Definition Tree.h:142
A vector / dynamic array.
Definition Vector.h:33
virtual uintptr_t getKernelCacheEnd() const =0
virtual uintptr_t getKernelCacheStart() const =0
virtual bool getMapping(void *virtualAddress, physical_uintptr_t &physicalAddress, size_t &flags)=0
static EXPORTED_PUBLIC VirtualAddressSpace & getKernelAddressSpace()
virtual void unmap(void *virtualAddress)=0
void prepareToWait()
Definition WaitQueue.cc:84
MUST_USE_RESULT WakeReason wait(const Channel &channel=Channel(), size_t debugState=0, uintptr_t debugAddress=0, StackDiscardCleanup onStackDiscard=nullptr, void *stackDiscardContext=nullptr)
Definition WaitQueue.cc:104
size_t wakeAllIfWaiting(WakeReason reason=WakeReason::Signalled, const Channel &channel=Channel())
Definition WaitQueue.cc:345
@ Dec
Definition Log.h:126
@ Hex
Definition Log.h:124
void pushBack(const T &value)
Definition Vector.h:275
size_t size() const
Definition Vector.h:265
size_t count() const
Definition Vector.h:270
uint64_t checksum[2]
Checksum of the page's contents (for dirty detection).
Definition Cache.h:239
size_t refcnt
Definition Cache.h:221
bool checkZeroChecksum() const
Check for an unset checksum.
Definition Cache.cc:2695
bool checkChecksum(uint64_t other[2]) const
Check the checksum against another.
Definition Cache.cc:2691
uintptr_t key
Key for this page.
Definition Cache.h:214
CachePage * pNext
Linked list components for LRU.
Definition Cache.h:267
uintptr_t location
The location of this page in memory.
Definition Cache.h:217
Status
Current page status.
Definition Cache.h:252