Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | /** | |||
2 | * @file scheduler.h | |||
3 | * | |||
4 | * @date May 18, 2014 | |||
5 | * @author Andrey Belomutskiy, (c) 2012-2020 | |||
6 | */ | |||
7 | #pragma once | |||
8 | ||||
9 | #include <cstdint> | |||
10 | ||||
11 | #ifndef EFI_UNIT_TEST_VERBOSE_ACTION | |||
12 | #define EFI_UNIT_TEST_VERBOSE_ACTION 0 | |||
13 | #endif | |||
14 | ||||
15 | #if EFI_UNIT_TEST | |||
16 | #include <cassert> | |||
17 | #include <iostream> | |||
18 | #endif | |||
19 | ||||
20 | // For safely storing and manipulating addresses as integral values | |||
21 | using scheduler_arg_t = uintptr_t; | |||
22 | ||||
23 | template<class To, class From> | |||
24 | std::enable_if_t< | |||
25 | sizeof(To) == sizeof(From) && | |||
26 | std::is_trivially_copyable_v<From> && | |||
27 | std::is_trivially_copyable_v<To>, | |||
28 | To> | |||
29 | // constexpr support needs compiler magic | |||
30 | bit_cast(const From& src) noexcept | |||
31 | { | |||
32 | static_assert(std::is_trivially_constructible_v<To>, | |||
33 | "This implementation additionally requires " | |||
34 | "destination type to be trivially constructible"); | |||
35 | ||||
36 | To dst; | |||
37 | std::memcpy(&dst, &src, sizeof(To)); | |||
38 | return dst; | |||
39 | } | |||
40 | ||||
41 | template<typename T> | |||
42 | struct TaggedPointer { | |||
43 | private: | |||
44 | ||||
45 | scheduler_arg_t m_raw{}; | |||
46 | ||||
47 | public: | |||
48 | ||||
49 | static_assert(alignof(T) >= 2, "Type must be at least 2-aligned"); | |||
50 | static_assert(sizeof(scheduler_arg_t) == sizeof(void*), "Unexpected scheduler_arg_t (i.e. m_raw) size, won't hold pointer"); | |||
51 | ||||
52 | 685 | static constexpr TaggedPointer fromRaw(scheduler_arg_t raw) { | ||
53 | 685 | return TaggedPointer{raw}; | ||
54 | } | |||
55 | ||||
56 | template<typename U = T> | |||
57 | 802 | static constexpr TaggedPointer make(U ptr, const bool flag) requires std::is_pointer_v<U> { | ||
58 | #if EFI_UNIT_TEST | |||
59 |
2/4TaggedPointer<InjectionEvent*> TaggedPointer<InjectionEvent*>::make<InjectionEvent*>(InjectionEvent*, bool):
✗ Branch 0 not taken.
✓ Branch 1 taken 801 times.
TaggedPointer<fuelControl_transitionIssue1592_Test*> TaggedPointer<fuelControl_transitionIssue1592_Test*>::make<InjectionEvent*>(InjectionEvent*, bool):
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
802 | assert((reinterpret_cast<scheduler_arg_t>(ptr) & scheduler_arg_t{1}) == 0); | |
60 | #endif | |||
61 |
3/4TaggedPointer<InjectionEvent*> TaggedPointer<InjectionEvent*>::make<InjectionEvent*>(InjectionEvent*, bool):
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 800 times.
TaggedPointer<fuelControl_transitionIssue1592_Test*> TaggedPointer<fuelControl_transitionIssue1592_Test*>::make<InjectionEvent*>(InjectionEvent*, bool):
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
802 | return TaggedPointer{reinterpret_cast<scheduler_arg_t>(ptr) | (flag ? 1 : 0)}; | |
62 | } | |||
63 | ||||
64 | 681 | [[nodiscard]] constexpr T* getOriginalPointer() const { | ||
65 | 681 | return reinterpret_cast<T*>(m_raw & ~scheduler_arg_t{1}); | ||
66 | } | |||
67 | ||||
68 | 685 | [[nodiscard]] constexpr bool getFlag() const { | ||
69 | 685 | return m_raw & scheduler_arg_t{1}; | ||
70 | } | |||
71 | ||||
72 | 804 | [[nodiscard]] constexpr scheduler_arg_t getRaw() const { | ||
73 | 804 | return m_raw; | ||
74 | } | |||
75 | ||||
76 | explicit constexpr operator bool() const { return m_raw != 0; } | |||
77 | ||||
78 | 1487 | explicit TaggedPointer(scheduler_arg_t const raw) noexcept : m_raw{raw} { } | ||
79 | ||||
80 | TaggedPointer() noexcept = default; | |||
81 | ||||
82 | TaggedPointer(TaggedPointer const&) noexcept = default; | |||
83 | TaggedPointer(TaggedPointer&& other) noexcept : m_raw{other.m_raw} { | |||
84 | other.m_raw = 0; | |||
85 | } | |||
86 | ||||
87 | TaggedPointer& operator=(TaggedPointer const&) noexcept = default; | |||
88 | TaggedPointer& operator=(TaggedPointer&& other) noexcept { | |||
89 | m_raw = other.m_raw; | |||
90 | other.m_raw = 0; | |||
91 | return *this; | |||
92 | } | |||
93 | }; | |||
94 | ||||
95 | // Regarding overhead, starting from GCC 10.1 through gcc 15 and clang 15-20 you have everything on -O0 | |||
96 | // but at -O1 there are not even constructors in ASM... | |||
97 | // Few more ASM instructions on minGw but due to CRT in WIN requiring some stack alignment by Windows OS | |||
98 | class action_s { | |||
99 | private: | |||
100 | using schfunc_t = void (*)(scheduler_arg_t); | |||
101 | ||||
102 | schfunc_t m_callback{}; | |||
103 | scheduler_arg_t m_param{}; | |||
104 | ||||
105 | // We want to have callback name in tests for easy debug | |||
106 | #if EFI_UNIT_TEST | |||
107 | char const* fn_name{}; | |||
108 | 2947 | constexpr action_s(const schfunc_t callback, char const* const fn_name_) noexcept : m_callback(callback), fn_name{fn_name_} {} | ||
109 | 23268 | constexpr action_s(const schfunc_t callback, const scheduler_arg_t param, char const* const fn_name_) noexcept : m_callback(callback), m_param(param), fn_name{fn_name_} {} | ||
110 | #else | |||
111 | constexpr action_s(const schfunc_t callback) noexcept : m_callback(callback) {} | |||
112 | constexpr action_s(const schfunc_t callback, const scheduler_arg_t param) noexcept : m_callback(callback), m_param(param) {} | |||
113 | #endif | |||
114 | ||||
115 | // We can pass either pointer or integral this enforces the rule at compile time | |||
116 | // Applies correct cast to argument | |||
117 | template<typename T> | |||
118 | 102 | static constexpr T from_scheduler_arg_t(scheduler_arg_t raw) noexcept { | ||
119 | static_assert(std::is_pointer_v<T> || std::is_integral_v<T>, "Unsupported type"); | |||
120 | if constexpr (std::is_pointer_v<T>) { | |||
121 | 102 | return reinterpret_cast<T>(raw); | ||
122 | } else { | |||
123 | return static_cast<T>(raw); | |||
124 | } | |||
125 | } | |||
126 | ||||
127 | // We can pass either pointer or integral this enforces the rule at compile time | |||
128 | // Applies correct cast to argument | |||
129 | template<typename T> | |||
130 | 23268 | static constexpr scheduler_arg_t to_scheduler_arg_t(T val) noexcept { | ||
131 | static_assert(std::is_pointer_v<T> || std::is_integral_v<T>, "Unsupported type"); | |||
132 | if constexpr (std::is_pointer_v<T>) { | |||
133 | 22461 | return reinterpret_cast<scheduler_arg_t>(val); | ||
134 | } else { | |||
135 | 807 | return static_cast<scheduler_arg_t>(val); | ||
136 | } | |||
137 | } | |||
138 | ||||
139 | // Wraps the original function for unified signature | |||
140 | // Applies correct cast to argument before dispatching to Func | |||
141 | template<auto Func, typename Arg> | |||
142 | 15406 | static constexpr void trampoline(scheduler_arg_t raw) noexcept { | ||
143 | static_assert(std::is_pointer_v<Arg> || std::is_integral_v<Arg>, "Unsupported type"); | |||
144 | #if EFI_UNIT_TEST_VERBOSE_ACTION | |||
145 | std::cout << "action_s::trampoline: " << __PRETTY_FUNCTION__ << "(" << reinterpret_cast<scheduler_arg_t>(Func) << ") " | |||
146 | "with raw arg = " << raw << "; is_ptr = " << std::is_pointer_v<Arg> << std::endl; | |||
147 | #endif | |||
148 | if constexpr (std::is_pointer_v<Arg>) { | |||
149 | 14720 | Func(reinterpret_cast<Arg>(raw)); | ||
150 | } else { | |||
151 | 686 | Func(static_cast<Arg>(raw)); | ||
152 | } | |||
153 | 15406 | } | ||
154 | ||||
155 | // Wrapper for zero arg callbacks | |||
156 | template<auto Func> | |||
157 | 2173 | static constexpr void trampoline_no_arg(scheduler_arg_t) noexcept { | ||
158 | #if EFI_UNIT_TEST_VERBOSE_ACTION | |||
159 | std::cout << "action_s::trampoline_no_arg: " << __PRETTY_FUNCTION__ << "(" << reinterpret_cast<scheduler_arg_t>(Func) << ") with no arg" << std::endl; | |||
160 | #endif | |||
161 | 2173 | Func(); // stored argument is ignored | ||
162 | 2173 | } | ||
163 | ||||
164 | public: | |||
165 | ||||
166 | 63873 | action_s() noexcept = default; | ||
167 | ||||
168 | action_s(action_s const&) noexcept = default; | |||
169 | 17578 | action_s(action_s&& other) noexcept : m_callback{other.m_callback}, m_param{other.m_param} | ||
170 | #if EFI_UNIT_TEST | |||
171 | 17578 | , fn_name {other.fn_name} | ||
172 | #endif | |||
173 | { | |||
174 | 17578 | other.m_callback = nullptr; | ||
175 | 17578 | other.m_param = 0; | ||
176 | #if EFI_UNIT_TEST | |||
177 | 17578 | other.fn_name = "Moved"; | ||
178 | #endif | |||
179 | 17578 | } | ||
180 | ||||
181 | action_s& operator=(action_s const&) noexcept = default; | |||
182 | 5584 | action_s& operator=(action_s&& other) noexcept { | ||
183 | 5584 | m_callback = other.m_callback; | ||
184 | 5584 | m_param = other.m_param; | ||
185 | 5584 | other.m_callback = nullptr; | ||
186 | 5584 | other.m_param = 0; | ||
187 | #if EFI_UNIT_TEST | |||
188 | 5584 | fn_name = other.fn_name; | ||
189 | 5584 | other.fn_name = "Moved"; | ||
190 | #endif | |||
191 | 5584 | return *this; | ||
192 | } | |||
193 | ||||
194 | // Factory: wraps a typed function (integral or pointer) with the unified signature | |||
195 | template<auto Func, typename Arg> | |||
196 | 4377 | static constexpr action_s make(Arg arg) noexcept(std::is_nothrow_constructible_v<action_s, schfunc_t, scheduler_arg_t>) { | ||
197 | static_assert(std::is_invocable_r_v<void, decltype(Func), Arg>, "Function signature mismatch"); | |||
198 | #if EFI_UNIT_TEST_VERBOSE_ACTION | |||
199 | std::cout << "action_s::make: " << __PRETTY_FUNCTION__ << "(" << reinterpret_cast<scheduler_arg_t>(Func) << ") " | |||
200 | "with raw arg = " << arg << "; is_ptr = " << std::is_pointer_v<Arg> << std::endl; | |||
201 | #endif | |||
202 | if constexpr (std::is_pointer_v<Arg>) { | |||
203 | // alignment is a must because pointers have to be dividable by 2 with no loss (i.e., no loss on bit shifts) | |||
204 | // if it is 1-aligned, there will be address data in the last bit, so it cannot be used for any flags | |||
205 | // There could be theoretic architectures where it is the case though I don't know what to do then, | |||
206 | // Maybe introduce some other member for flag here? | |||
207 | // Anyway, we have so much code relying on specific architecture, and we are not testing on some fancy | |||
208 | // PowerPC/mainframe/... machines so should always be true, but just in case... | |||
209 | static_assert(alignof(std::remove_pointer_t<Arg>) >= 2, "Pointer must be at least 2-aligned"); | |||
210 | static_assert(sizeof(scheduler_arg_t) == sizeof(void*), "Unexpected scheduler_arg_t size"); | |||
211 | } | |||
212 | #if EFI_UNIT_TEST | |||
213 | 4377 | return action_s(&trampoline<Func, Arg>, to_scheduler_arg_t(arg), __PRETTY_FUNCTION__); | ||
214 | #else | |||
215 | return action_s(&trampoline<Func, Arg>, to_scheduler_arg_t(arg)); | |||
216 | #endif | |||
217 | } | |||
218 | ||||
219 | // For functions with no arguments | |||
220 | template<auto Func> | |||
221 | 2947 | static constexpr action_s make() noexcept(std::is_nothrow_constructible_v<action_s, schfunc_t>) { | ||
222 | static_assert(std::is_invocable_r_v<void, decltype(Func)>, "Function signature mismatch"); | |||
223 | #if EFI_UNIT_TEST | |||
224 | 2947 | return action_s(&trampoline_no_arg<Func>, __PRETTY_FUNCTION__); | ||
225 | #else | |||
226 | return action_s(&trampoline_no_arg<Func>); | |||
227 | #endif | |||
228 | } | |||
229 | ||||
230 | 17579 | void execute() const { | ||
231 |
1/2✓ Branch 0 taken 17579 times.
✗ Branch 1 not taken.
|
1/2✓ Decision 'true' taken 17579 times.
✗ Decision 'false' not taken.
|
17579 | if (m_callback) { |
232 | #if EFI_UNIT_TEST_VERBOSE_ACTION | |||
233 | std::cout << "action_s::execute: " << fn_name << "(" << reinterpret_cast<scheduler_arg_t>(m_callback) << ") " | |||
234 | "with raw arg = " << m_param << std::endl; | |||
235 | #endif | |||
236 | 17579 | m_callback(m_param); | ||
237 | } else { | |||
238 | #ifdef WE_HAVE_CRITICAL_ERROR_METHOD | |||
239 | efiCriticalError("clear nullptr"); | |||
240 | #endif | |||
241 | ||||
242 | #if EFI_UNIT_TEST | |||
243 | ✗ | assert(false); | ||
244 | #endif | |||
245 | } | |||
246 | 17579 | } | ||
247 | ||||
248 | 25593 | [[nodiscard]] constexpr schfunc_t getCallback() const { return m_callback; } | ||
249 | 11 | [[nodiscard]] constexpr scheduler_arg_t const& getArgumentRaw() const { return m_param; } | ||
250 | ||||
251 | template<typename T> | |||
252 | 102 | [[nodiscard]] constexpr T getArgument() const { return from_scheduler_arg_t<T>(m_param); } | ||
253 | ||||
254 | 26596 | explicit constexpr operator bool() const { return m_callback != nullptr; } | ||
255 | ||||
256 | 3 | bool constexpr operator==(const action_s& other) const { | ||
257 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | return m_callback == other.m_callback && m_param == other.m_param; | |
258 | } | |||
259 | ||||
260 | #if EFI_UNIT_TEST | |||
261 | [[nodiscard]] constexpr char const* getCallbackName() const { return fn_name ? fn_name : "nullptr"; } | |||
262 | #endif | |||
263 | }; | |||
264 | ||||
265 | /** | |||
266 | * This structure holds information about an event scheduled in the future: when to execute what callback with what parameters | |||
267 | */ | |||
268 | #pragma pack(push, 4) | |||
269 | struct scheduling_s { | |||
270 | 39829771 | [[nodiscard]] efitick_t getMomentNt() const { | ||
271 | 39829771 | return momentNt; | ||
272 | } | |||
273 | ||||
274 | #if EFI_UNIT_TEST | |||
275 | #ifndef NT2US | |||
276 | #define NT2US(x) ((x) / US_TO_NT_MULTIPLIER) | |||
277 | #endif | |||
278 | ||||
279 | 76264 | [[nodiscard]] efitick_t getMomentUs() const { | ||
280 | 76264 | return NT2US(momentNt); | ||
281 | } | |||
282 | #endif | |||
283 | ||||
284 | 19838 | void setMomentNt(efitick_t p_moment) { | ||
285 | 19838 | momentNt = p_moment; | ||
286 | 19838 | } | ||
287 | ||||
288 | #if EFI_SIMULATOR | |||
289 | // used by signal_executor_sleep executor implementation | |||
290 | virtual_timer_t timer; | |||
291 | #endif /* EFI_SIMULATOR */ | |||
292 | ||||
293 | // Scheduler implementation uses a sorted linked list of these scheduling records. | |||
294 | scheduling_s *nextScheduling_s = nullptr; | |||
295 | ||||
296 | action_s action; | |||
297 | /** | |||
298 | * timestamp represented as 64-bit value of ticks since MCU start | |||
299 | */ | |||
300 | private: | |||
301 | volatile efitick_t momentNt = 0; | |||
302 | }; | |||
303 | #pragma pack(pop) | |||
304 | ||||
305 | struct Scheduler { | |||
306 | /** | |||
307 | * @brief Schedule an action to be executed in the future. | |||
308 | * | |||
309 | * scheduleByAngle is useful if you want to schedule something in terms of crank angle instead of time. | |||
310 | * | |||
311 | * @param msg Name of this event to use for logging in case of an error. | |||
312 | * @param scheduling Storage to use for the scheduled event. If null, one will be used from the pool. | |||
313 | * @param targetTime When to execute the specified action. If this time is in the past or | |||
314 | * very near future, it may execute immediately. | |||
315 | * @param action An action to execute at the specified time. | |||
316 | */ | |||
317 | virtual void schedule(const char *msg, scheduling_s *scheduling, efitick_t targetTime, action_s const& action) = 0; | |||
318 | ||||
319 | /** | |||
320 | * @brief Cancel the specified scheduling_s so that, if currently scheduled, it does not execute. | |||
321 | * | |||
322 | * @param scheduling The scheduling_s to cancel. | |||
323 | */ | |||
324 | virtual void cancel(scheduling_s* scheduling) = 0; | |||
325 | }; | |||
326 | ||||
327 | Scheduler *getScheduler(); | |||
328 |