The Pedigree Project 0.1
DeferredTimeAccounting.h
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#ifndef PEDIGREE_KERNEL_PROCESS_DEFERREDTIMEACCOUNTING_H
9#define PEDIGREE_KERNEL_PROCESS_DEFERREDTIMEACCOUNTING_H
10#include "pedigree/kernel/compiler.h"
11#include "pedigree/kernel/processor/types.h"
12#include "pedigree/kernel/time/Time.h"
13
14#include <config.h>
15
16static_assert(__atomic_always_lock_free(sizeof(Time::Timestamp), nullptr),
17 "interrupt time accounting requires lock-free timestamps");
18static_assert(__atomic_always_lock_free(sizeof(size_t), nullptr),
19 "interrupt time accounting requires lock-free work generations");
20static_assert(__atomic_always_lock_free(sizeof(bool), nullptr),
21 "interrupt time accounting requires lock-free admission flags");
22
23enum class CpuTimeMode {
24 Kernel,
25 User,
26};
27
30 public:
31 static constexpr CpuTimeMode interrupted(bool fromUserspace) {
32 return fromUserspace ? CpuTimeMode::User : CpuTimeMode::Kernel;
33 }
34
35 static constexpr CpuTimeMode handler() {
36 return CpuTimeMode::Kernel;
37 }
38
39 static constexpr CpuTimeMode resumed(bool fromUserspace) {
40 return interrupted(fromUserspace);
41 }
42};
43
46 public:
47 ThreadTimeAccounting() : m_Kernel(), m_User() {}
48
49 void record(CpuTimeMode mode, Time::Timestamp now, size_t processor = 0) {
50 Entry* state = entry(mode);
51 if (installProcessorBaseline(state, processor, now)) {
52 return;
53 }
54
55 Time::Timestamp* baseline = &state->timestamp;
56 Time::Timestamp previous = __atomic_load_n(baseline, __ATOMIC_ACQUIRE);
57 if (now <= previous) {
58 return;
59 }
60
61 // A failed handoff means a nested scope installed a newer sample.
62 // Retrying is unnecessary and would make the raw path unbounded.
63 __atomic_compare_exchange_n(baseline, &previous, now, false, __ATOMIC_ACQ_REL,
64 __ATOMIC_ACQUIRE);
65 }
66
72 ALWAYS_INLINE void recordAtInterruptDisabled(CpuTimeMode mode, Time::Timestamp now,
73 size_t processor = 0) {
74 Entry* state = entry(mode);
75 if (state->processor != processor || now > state->timestamp) {
76 state->timestamp = now;
77 state->processor = processor;
78 }
79 }
80
81 Time::Timestamp elapsed(CpuTimeMode mode, Time::Timestamp now, size_t processor = 0) {
82 Entry* state = entry(mode);
83 if (installProcessorBaseline(state, processor, now)) {
84 return 0;
85 }
86
87 Time::Timestamp* baseline = &state->timestamp;
88 Time::Timestamp previous = __atomic_load_n(baseline, __ATOMIC_ACQUIRE);
89 if (now < previous) {
90 return 0;
91 }
92
93 // One failed handoff means a nested accounting scope advanced this
94 // exact Thread's baseline and already owns the overlapping interval.
95 // Do not spin in raw interrupt context or charge that interval twice.
96 const Time::Timestamp elapsed = now - previous;
97 return __atomic_compare_exchange_n(baseline, &previous, now, false, __ATOMIC_ACQ_REL,
98 __ATOMIC_ACQUIRE)
99 ? elapsed
100 : 0;
101 }
102
104 ALWAYS_INLINE Time::Timestamp elapsedAtInterruptDisabled(CpuTimeMode mode, Time::Timestamp now,
105 size_t processor = 0) {
106 Entry* state = entry(mode);
107 if (state->processor != processor) {
108 state->timestamp = now;
109 state->processor = processor;
110 return 0;
111 }
112 if (now < state->timestamp) {
113 return 0;
114 }
115 const Time::Timestamp elapsed = now - state->timestamp;
116 state->timestamp = now;
117 return elapsed;
118 }
119
120 private:
121 struct Entry {
122 Entry() : timestamp(0), processor(~static_cast<size_t>(0)) {}
123
124 Time::Timestamp timestamp;
125 size_t processor;
126 };
127
128 static bool installProcessorBaseline(Entry* state, size_t processor, Time::Timestamp now) {
129 if (__atomic_load_n(&state->processor, __ATOMIC_ACQUIRE) == processor) {
130 return false;
131 }
132
133 // A schedulable Thread cannot execute concurrently on its old and new
134 // CPUs. Publish the destination timestamp before its CPU identifier so
135 // nested scopes which observe the new identifier also see its sample.
136 __atomic_store_n(&state->timestamp, now, __ATOMIC_RELEASE);
137 __atomic_store_n(&state->processor, processor, __ATOMIC_RELEASE);
138 return true;
139 }
140
141 ALWAYS_INLINE Entry* entry(CpuTimeMode mode) {
142 return mode == CpuTimeMode::User ? &m_User : &m_Kernel;
143 }
144
145 Entry m_Kernel;
146 Entry m_User;
147
149 ThreadTimeAccounting& operator=(const ThreadTimeAccounting&) = delete;
150};
151
154 public:
155 DeferredTimeAccounting() : m_Pending(0) {}
156
158 bool publish(Time::Timestamp elapsed) {
159 if (!elapsed) {
160 return false;
161 }
162
163 return __atomic_exchange_n(&m_Pending, static_cast<size_t>(1), __ATOMIC_ACQ_REL) == 0;
164 }
165
167 bool take() {
168 return __atomic_exchange_n(&m_Pending, static_cast<size_t>(0), __ATOMIC_ACQ_REL) != 0;
169 }
170
171 bool pending() const {
172 return __atomic_load_n(&m_Pending, __ATOMIC_ACQUIRE) != 0;
173 }
174
175 private:
176 size_t m_Pending;
177
179 DeferredTimeAccounting& operator=(const DeferredTimeAccounting&) = delete;
180};
181
184 public:
185 DeferredTimeAccountingWorkerState() : m_Published(0), m_Completed(0), m_Active(0) {}
186
188 void publish() {
189 __atomic_add_fetch(&m_Published, static_cast<size_t>(1), __ATOMIC_RELEASE);
190 }
191
193 size_t beginBatch() {
194 __atomic_store_n(&m_Active, static_cast<size_t>(1), __ATOMIC_RELEASE);
195 return __atomic_load_n(&m_Published, __ATOMIC_ACQUIRE);
196 }
197
199 void finishBatch(size_t generation) {
200 __atomic_store_n(&m_Completed, generation, __ATOMIC_RELEASE);
201 __atomic_store_n(&m_Active, static_cast<size_t>(0), __ATOMIC_RELEASE);
202 }
203
204 bool ready(bool stopping = false) const {
205 return stopping || __atomic_load_n(&m_Active, __ATOMIC_ACQUIRE) || !caughtUp();
206 }
207
208 bool caughtUp() const {
209 return __atomic_load_n(&m_Published, __ATOMIC_ACQUIRE) ==
210 __atomic_load_n(&m_Completed, __ATOMIC_ACQUIRE);
211 }
212
213 private:
214 size_t m_Published;
215 size_t m_Completed;
216 size_t m_Active;
217
220};
221
222#endif
bool publish(Time::Timestamp elapsed)
ALWAYS_INLINE void recordAtInterruptDisabled(CpuTimeMode mode, Time::Timestamp now, size_t processor=0)
ALWAYS_INLINE Time::Timestamp elapsedAtInterruptDisabled(CpuTimeMode mode, Time::Timestamp now, size_t processor=0)
Definition User.h:32