| 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<fuelControl_transitionIssue1592_Test*> TaggedPointer<fuelControl_transitionIssue1592_Test*>::make<InjectionEvent*>(InjectionEvent*, bool):
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
TaggedPointer<InjectionEvent*> TaggedPointer<InjectionEvent*>::make<InjectionEvent*>(InjectionEvent*, bool):
✗ Branch 0 not taken.
✓ Branch 1 taken 801 times.
|
802 | assert((reinterpret_cast<scheduler_arg_t>(ptr) & scheduler_arg_t{1}) == 0); | |
| 60 | #endif | |||
| 61 |
3/4TaggedPointer<fuelControl_transitionIssue1592_Test*> TaggedPointer<fuelControl_transitionIssue1592_Test*>::make<InjectionEvent*>(InjectionEvent*, bool):
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
TaggedPointer<InjectionEvent*> TaggedPointer<InjectionEvent*>::make<InjectionEvent*>(InjectionEvent*, bool):
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 800 times.
|
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 | 3139 | constexpr action_s(const schfunc_t callback, char const* const fn_name_) noexcept : m_callback(callback), fn_name{fn_name_} {} | ||
| 109 | 25600 | 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 | 25600 | 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 | 24793 | 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 | 16836 | 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 | 16150 | Func(reinterpret_cast<Arg>(raw)); | ||
| 150 | } else { | |||
| 151 | 686 | Func(static_cast<Arg>(raw)); | ||
| 152 | } | |||
| 153 | 16836 | } | ||
| 154 | ||||
| 155 | // Wrapper for zero arg callbacks | |||
| 156 | template<auto Func> | |||
| 157 | 2364 | 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 | 2364 | Func(); // stored argument is ignored | ||
| 162 | 2364 | } | ||
| 163 | ||||
| 164 | public: | |||
| 165 | ||||
| 166 | 64556 | action_s() noexcept = default; | ||
| 167 | ||||
| 168 | action_s(action_s const&) noexcept = default; | |||
| 169 | 19199 | action_s(action_s&& other) noexcept : m_callback{other.m_callback}, m_param{other.m_param} | ||
| 170 | #if EFI_UNIT_TEST | |||
| 171 | 19199 | , fn_name {other.fn_name} | ||
| 172 | #endif | |||
| 173 | { | |||
| 174 | 19199 | other.m_callback = nullptr; | ||
| 175 | 19199 | other.m_param = 0; | ||
| 176 | #if EFI_UNIT_TEST | |||
| 177 | 19199 | other.fn_name = "Moved"; | ||
| 178 | #endif | |||
| 179 | 19199 | } | ||
| 180 | ||||
| 181 | action_s& operator=(action_s const&) noexcept = default; | |||
| 182 | 6173 | action_s& operator=(action_s&& other) noexcept { | ||
| 183 | 6173 | m_callback = other.m_callback; | ||
| 184 | 6173 | m_param = other.m_param; | ||
| 185 | 6173 | other.m_callback = nullptr; | ||
| 186 | 6173 | other.m_param = 0; | ||
| 187 | #if EFI_UNIT_TEST | |||
| 188 | 6173 | fn_name = other.fn_name; | ||
| 189 | 6173 | other.fn_name = "Moved"; | ||
| 190 | #endif | |||
| 191 | 6173 | return *this; | ||
| 192 | } | |||
| 193 | ||||
| 194 | // Factory: wraps a typed function (integral or pointer) with the unified signature | |||
| 195 | template<auto Func, typename Arg> | |||
| 196 | 4375 | 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 | 4375 | 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 | 3139 | 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 | 3139 | return action_s(&trampoline_no_arg<Func>, __PRETTY_FUNCTION__); | ||
| 225 | #else | |||
| 226 | return action_s(&trampoline_no_arg<Func>); | |||
| 227 | #endif | |||
| 228 | } | |||
| 229 | ||||
| 230 | 19200 | void execute() const { | ||
| 231 |
1/2✓ Branch 0 taken 19200 times.
✗ Branch 1 not taken.
|
1/2✓ Decision 'true' taken 19200 times.
✗ Decision 'false' not taken.
|
19200 | 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 | 19200 | 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 | 19200 | } | ||
| 247 | ||||
| 248 | 27804 | [[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 | 29272 | 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 | 6461925 | [[nodiscard]] efitick_t getMomentNt() const { | ||
| 271 | 6461925 | 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 | 579 | [[nodiscard]] efitick_t getMomentUs() const { | ||
| 280 | 579 | return NT2US(momentNt); | ||
| 281 | } | |||
| 282 | #endif | |||
| 283 | ||||
| 284 | 22057 | void setMomentNt(efitick_t p_moment) { | ||
| 285 | 22057 | momentNt = p_moment; | ||
| 286 | 22057 | } | ||
| 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 |