diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 81caab334b..ae178db032 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -20,7 +20,7 @@ jobs: run: | export HOMEBREW_NO_INSTALL_CLEANUP=1 # saves time brew update - brew unlink gcc + # brew unlink gcc brew install doxygen boost gcc@12 avr-gcc@12 arm-gcc-bin@12 cmake || true brew link --force avr-gcc@12 # brew upgrade boost gcc git || true diff --git a/.gitmodules b/.gitmodules index 7882a65ec6..8658da233d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -48,4 +48,4 @@ url = https://github.com/modm-ext/cmsis-dsp-partial.git [submodule "ext/nlohmann/json"] path = ext/nlohmann/json - url = git@github.com:modm-ext/json-partial.git + url = https://github.com/modm-ext/json-partial.git diff --git a/examples/avr/fiber/main.cpp b/examples/avr/fiber/main.cpp index 89065e4270..bdb0aaaf3f 100644 --- a/examples/avr/fiber/main.cpp +++ b/examples/avr/fiber/main.cpp @@ -24,14 +24,14 @@ void fiber_function1() { MODM_LOG_INFO << MODM_FILE_INFO << modm::endl; - while (++f1counter < cycles) { modm::fiber::yield(); total_counter++; } + while (++f1counter < cycles) { modm::this_fiber::yield(); total_counter++; } } void fiber_function2(uint32_t cyc) { MODM_LOG_INFO << MODM_FILE_INFO << modm::endl; - while (++f2counter < cyc) { modm::fiber::yield(); total_counter++; } + while (++f2counter < cyc) { modm::this_fiber::yield(); total_counter++; } } struct Test @@ -40,14 +40,14 @@ struct Test fiber_function3() { MODM_LOG_INFO << MODM_FILE_INFO << modm::endl; - while (++f3counter < cycles) { modm::fiber::yield(); total_counter++; } + while (++f3counter < cycles) { modm::this_fiber::yield(); total_counter++; } } void fiber_function4(uint32_t cyc) { MODM_LOG_INFO << MODM_FILE_INFO << modm::endl; - while (++f4counter < cyc) { modm::fiber::yield(); total_counter++; } + while (++f4counter < cyc) { modm::this_fiber::yield(); total_counter++; } } volatile uint32_t f3counter{0}; diff --git a/examples/generic/fiber/main.cpp b/examples/generic/fiber/main.cpp index 9331388171..2965b627bd 100644 --- a/examples/generic/fiber/main.cpp +++ b/examples/generic/fiber/main.cpp @@ -26,14 +26,14 @@ void fiber_function1() { MODM_LOG_INFO << MODM_FILE_INFO << modm::endl; - while (++f1counter < cycles) { modm::fiber::yield(); total_counter++; } + while (++f1counter < cycles) { modm::this_fiber::yield(); total_counter++; } } void fiber_function2(uint32_t cyc) { MODM_LOG_INFO << MODM_FILE_INFO << modm::endl; - while (++f2counter < cyc) { modm::fiber::yield(); total_counter++; } + while (++f2counter < cyc) { modm::this_fiber::yield(); total_counter++; } } struct Test @@ -42,14 +42,14 @@ struct Test fiber_function3() { MODM_LOG_INFO << MODM_FILE_INFO << modm::endl; - while (++f3counter < cycles) { modm::fiber::yield(); total_counter++; } + while (++f3counter < cycles) { modm::this_fiber::yield(); total_counter++; } } void fiber_function4(uint32_t cyc) { MODM_LOG_INFO << MODM_FILE_INFO << modm::endl; - while (++f4counter < cyc) { modm::fiber::yield(); total_counter++; } + while (++f4counter < cyc) { modm::this_fiber::yield(); total_counter++; } } volatile uint32_t f3counter{0}; @@ -57,8 +57,8 @@ struct Test } test; // Single purpose fibers to time the yield -modm_faststack modm::Fiber<> fiber_y1([](){ modm::fiber::yield(); counter.stop(); }); -modm_faststack modm::Fiber<> fiber_y2([](){ counter.start(); modm::fiber::yield(); }); +modm_faststack modm::Fiber<> fiber_y1([](){ modm::this_fiber::yield(); counter.stop(); }); +modm_faststack modm::Fiber<> fiber_y2([](){ counter.start(); modm::this_fiber::yield(); }); modm_faststack modm::Fiber<> fiber1(fiber_function1, modm::fiber::Start::Later); modm_faststack modm::Fiber<> fiber2([](){ fiber_function2(cycles); }, modm::fiber::Start::Later); @@ -71,12 +71,12 @@ extern modm::Fiber<> fiber_pong; extern modm::Fiber<> fiber_ping; modm_faststack modm::Fiber<> fiber_ping([](){ MODM_LOG_INFO << "ping = " << fiber_ping.stack_usage() << modm::endl; - modm::fiber::sleep(1s); + modm::this_fiber::sleep_for(1s); fiber_pong.start(); }, modm::fiber::Start::Later); modm_faststack modm::Fiber<> fiber_pong([](){ MODM_LOG_INFO << "pong = " << fiber_pong.stack_usage() << modm::endl; - modm::fiber::sleep(1s); + modm::this_fiber::sleep_for(1s); fiber_ping.start(); }, modm::fiber::Start::Later); diff --git a/examples/linux/fiber/main.cpp b/examples/linux/fiber/main.cpp index 919bf2574a..a3cdee8d33 100644 --- a/examples/linux/fiber/main.cpp +++ b/examples/linux/fiber/main.cpp @@ -17,7 +17,7 @@ void hello() for(int ii=0; ii<10; ii++) { MODM_LOG_INFO << "Hello "; - modm::fiber::yield(); + modm::this_fiber::yield(); } } @@ -28,7 +28,7 @@ struct Test for(int ii=0; ii<10; ii++) { MODM_LOG_INFO << arg << modm::endl; - modm::fiber::yield(); + modm::this_fiber::yield(); } } } test; diff --git a/examples/nucleo_f429zi/spi_flash_fatfs/main.cpp b/examples/nucleo_f429zi/spi_flash_fatfs/main.cpp index f6c1335a5d..46dbe0e108 100644 --- a/examples/nucleo_f429zi/spi_flash_fatfs/main.cpp +++ b/examples/nucleo_f429zi/spi_flash_fatfs/main.cpp @@ -319,7 +319,7 @@ modm_faststack modm::Fiber<> blinkyFiber([]() while(true) { Board::Leds::toggle(); - modm::fiber::sleep(200ms); + modm::this_fiber::sleep_for(200ms); } }); diff --git a/examples/rp_pico/fiber/main.cpp b/examples/rp_pico/fiber/main.cpp index e65fd9d1da..8fc2983068 100644 --- a/examples/rp_pico/fiber/main.cpp +++ b/examples/rp_pico/fiber/main.cpp @@ -42,7 +42,7 @@ fiber_function1(CoreData& d) { while (++d.f1counter < cycles) { - modm::fiber::yield(); + modm::this_fiber::yield(); d.total_counter++; } } @@ -52,7 +52,7 @@ fiber_function2(CoreData& d) { while (++d.f2counter < cycles) { - modm::fiber::yield(); + modm::this_fiber::yield(); d.total_counter++; } } diff --git a/examples/stm32f3_discovery/rotation/main.cpp b/examples/stm32f3_discovery/rotation/main.cpp index 4c26d9dbe1..d1aad754f0 100644 --- a/examples/stm32f3_discovery/rotation/main.cpp +++ b/examples/stm32f3_discovery/rotation/main.cpp @@ -69,7 +69,7 @@ modm_faststack modm::Fiber<> fiber_gyro([]() } // repeat every 5 ms - modm::fiber::sleep(5ms); + modm::this_fiber::sleep_for(5ms); } }); @@ -78,7 +78,7 @@ modm_faststack modm::Fiber<> fiber_blinky([]() while (true) { Board::LedSouth::toggle(); - modm::fiber::sleep(1s); + modm::this_fiber::sleep_for(1s); } }); diff --git a/examples/stm32f469_discovery/touchscreen/main.cpp b/examples/stm32f469_discovery/touchscreen/main.cpp index 454ab37062..ad23a7f1a5 100644 --- a/examples/stm32f469_discovery/touchscreen/main.cpp +++ b/examples/stm32f469_discovery/touchscreen/main.cpp @@ -96,7 +96,7 @@ modm_faststack modm::Fiber<> fiber_blinky([]() while(true) { Board::LedGreen::toggle(); - modm::fiber::sleep(20ms); + modm::this_fiber::sleep_for(20ms); } }); diff --git a/ext/gcc/assert.cpp.in b/ext/gcc/assert.cpp.in index 49a2e1d272..7e52baed0b 100644 --- a/ext/gcc/assert.cpp.in +++ b/ext/gcc/assert.cpp.in @@ -76,5 +76,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION void __throw_bad_any_cast() { __modm_stdcpp_failure("bad_any_cast"); } + + void + __throw_system_error(int errc __attribute__((unused))) + { __modm_stdcpp_failure("system_error"); } _GLIBCXX_END_NAMESPACE_VERSION } // namespace diff --git a/ext/gcc/cxxabi.cpp.in b/ext/gcc/cxxabi.cpp.in index 30e3f7df3f..74d633733b 100644 --- a/ext/gcc/cxxabi.cpp.in +++ b/ext/gcc/cxxabi.cpp.in @@ -27,6 +27,9 @@ void __cxa_deleted_virtual() %% if with_threadsafe_statics #include +%% if with_fibers +#include +%% endif %% if is_avr %# // Even thought the actual guard size is uint64_t on AVR, we only need to access @@ -71,6 +74,10 @@ __cxa_guard_acquire(guard_type *guard) // We got called from inside an interrupt, but we cannot yield back modm_assert(not is_in_irq, "stat.rec", "Recursive initialization of a function static!", guard); +%% if with_fibers + // we're not in an interrupt, try to yield back to the initializing fiber + modm::this_fiber::yield(); +%% endif } value = UNINITIALIZED; } diff --git a/ext/gcc/libstdc++ b/ext/gcc/libstdc++ index d37fa8184f..844ed14b5c 160000 --- a/ext/gcc/libstdc++ +++ b/ext/gcc/libstdc++ @@ -1 +1 @@ -Subproject commit d37fa8184f4f5fd8fbc645e396385896d6b435a1 +Subproject commit 844ed14b5c996778c5ae15040ba542c4528981e2 diff --git a/ext/gcc/module_c++.lb b/ext/gcc/module_c++.lb index 3bd270cfa4..f0f78595a0 100644 --- a/ext/gcc/module_c++.lb +++ b/ext/gcc/module_c++.lb @@ -92,6 +92,7 @@ def build(env): "with_threadsafe_statics": with_threadsafe_statics, "with_memory_traits": env.has_module(":architecture:memory"), "with_heap": env.has_module(":platform:heap"), + "with_fibers": env.has_module(":processing:fiber"), "is_avr": is_avr, "is_cortex_m": is_cortex_m, } diff --git a/src/modm/architecture/detect.hpp b/src/modm/architecture/detect.hpp index dce4b0a38e..ad317e0ed4 100644 --- a/src/modm/architecture/detect.hpp +++ b/src/modm/architecture/detect.hpp @@ -203,15 +203,19 @@ # define MODM_CPU_ARM 1 # define MODM_ALIGNMENT 4 # if defined __ARM_ARCH_6SM__ || defined __ARM_ARCH_6M__ +# define MODM_CPU_CORTEX_M 1 # define MODM_CPU_CORTEX_M0 1 # define MODM_CPU_STRING "ARM Cortex-M0" # elif defined __ARM_ARCH_7M__ +# define MODM_CPU_CORTEX_M 1 # define MODM_CPU_CORTEX_M3 1 # define MODM_CPU_STRING "ARM Cortex-M3" # elif defined __ARM_ARCH_7EM__ +# define MODM_CPU_CORTEX_M 1 # define MODM_CPU_CORTEX_M4 1 # define MODM_CPU_STRING "ARM Cortex-M4" # elif defined __ARM_ARCH_8M_MAIN__ +# define MODM_CPU_CORTEX_M 1 # define MODM_CPU_CORTEX_M33 1 # define MODM_CPU_STRING "ARM Cortex-M33" # elif defined __ARM_ARCH_ISA_A64 diff --git a/src/modm/driver/inertial/bmi088_impl.hpp b/src/modm/driver/inertial/bmi088_impl.hpp index b94dac0604..9f47d75fb1 100644 --- a/src/modm/driver/inertial/bmi088_impl.hpp +++ b/src/modm/driver/inertial/bmi088_impl.hpp @@ -225,7 +225,7 @@ void Bmi088::timerWait() { while (timer_.isArmed()) { - modm::fiber::yield(); + modm::this_fiber::yield(); } } diff --git a/src/modm/driver/inertial/bmi088_transport_impl.hpp b/src/modm/driver/inertial/bmi088_transport_impl.hpp index 8e62894091..8fb3e34e23 100644 --- a/src/modm/driver/inertial/bmi088_transport_impl.hpp +++ b/src/modm/driver/inertial/bmi088_transport_impl.hpp @@ -57,7 +57,7 @@ Bmi088SpiTransport::readRegisters(uint8_t startReg, } while (!this->acquireMaster()) { - modm::fiber::yield(); + modm::this_fiber::yield(); } Cs::reset(); @@ -95,7 +95,7 @@ bool Bmi088SpiTransport::writeRegister(uint8_t reg, uint8_t data) { while (!this->acquireMaster()) { - modm::fiber::yield(); + modm::this_fiber::yield(); } Cs::reset(); diff --git a/src/modm/platform/spi/at90_tiny_mega/spi_master.cpp.in b/src/modm/platform/spi/at90_tiny_mega/spi_master.cpp.in index b93ecdf57d..724ad1e3c3 100644 --- a/src/modm/platform/spi/at90_tiny_mega/spi_master.cpp.in +++ b/src/modm/platform/spi/at90_tiny_mega/spi_master.cpp.in @@ -45,7 +45,7 @@ modm::platform::SpiMaster{{ id }}::transfer(uint8_t data) // start transfer by copying data into register SPDR{{ id }} = data; - do modm::fiber::yield(); + do modm::this_fiber::yield(); while (!(SPSR{{ id }} & (1 << SPIF{{ id }}))); return SPDR{{ id }}; diff --git a/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.cpp.in b/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.cpp.in index e7af684dc8..fe9677929b 100644 --- a/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.cpp.in +++ b/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.cpp.in @@ -62,7 +62,7 @@ modm::platform::UartSpiMaster{{id}}::transfer(uint8_t data) %% if use_fiber // wait for transmit register empty while (!((UCSR{{id}}A & (1 << UDRE{{id}})))) - modm::fiber::yield(); + modm::this_fiber::yield(); %% if not extended if(dataOrder == DataOrder::MsbFirst) { @@ -72,7 +72,7 @@ modm::platform::UartSpiMaster{{id}}::transfer(uint8_t data) UDR{{id}} = data; // wait for receive register not empty - do modm::fiber::yield(); + do modm::this_fiber::yield(); while (!((UCSR{{id}}A & (1 << RXC{{id}})))); data = UDR{{id}}; diff --git a/src/modm/platform/spi/rp/spi_master.cpp.in b/src/modm/platform/spi/rp/spi_master.cpp.in index 622f3c2e74..ca1d026cb9 100644 --- a/src/modm/platform/spi/rp/spi_master.cpp.in +++ b/src/modm/platform/spi/rp/spi_master.cpp.in @@ -30,12 +30,12 @@ modm::platform::SpiMaster{{ id }}::transfer(uint8_t data) { %% if use_fiber // wait for previous transfer to finish - while (txFifoFull()) modm::fiber::yield(); + while (txFifoFull()) modm::this_fiber::yield(); // start transfer by copying data into register write(data); - while (rxFifoEmpty()) modm::fiber::yield(); + while (rxFifoEmpty()) modm::this_fiber::yield(); return read(); %% else diff --git a/src/modm/platform/spi/rp/spi_master_dma_impl.hpp.in b/src/modm/platform/spi/rp/spi_master_dma_impl.hpp.in index 6f6ddb5b3b..96bfbd1c95 100644 --- a/src/modm/platform/spi/rp/spi_master_dma_impl.hpp.in +++ b/src/modm/platform/spi/rp/spi_master_dma_impl.hpp.in @@ -123,16 +123,16 @@ modm::platform::SpiMaster{{ id }}_Dma::transfer( startTransfer(tx, rx, length); while (Dma::TxChannel::isBusy() or (rx and Dma::RxChannel::isBusy())) - modm::fiber::yield(); + modm::this_fiber::yield(); while (!txFifoEmpty() or (rx and !rxFifoEmpty()) or isBusy()) - modm::fiber::yield(); + modm::this_fiber::yield(); if (!rx) { // Drain RX FIFO, then wait for shifting to finish (which // may be *after* TX FIFO drains), then drain RX FIFO again while (!rxFifoEmpty()) read(); - while (isBusy()) modm::fiber::yield(); + while (isBusy()) modm::this_fiber::yield(); // Don't leave overrun flag set spi{{ id }}_hw->icr = SPI_SSPICR_RORIC_BITS; diff --git a/src/modm/platform/spi/sam/spi_master.cpp.in b/src/modm/platform/spi/sam/spi_master.cpp.in index 1c5991ce07..b069d79172 100644 --- a/src/modm/platform/spi/sam/spi_master.cpp.in +++ b/src/modm/platform/spi/sam/spi_master.cpp.in @@ -26,14 +26,14 @@ modm::platform::SpiMaster{{ id }}::transfer(uint8_t data) // wait for previous transfer to finish while (!isTransmitDataRegisterEmpty()) - modm::fiber::yield(); + modm::this_fiber::yield(); // start transfer by copying data into register write(data); // wait for current transfer to finish while(!isReceiveDataRegisterFull()) - modm::fiber::yield(); + modm::this_fiber::yield(); return read(); %% else diff --git a/src/modm/platform/spi/sam_x7x/spi_master.cpp.in b/src/modm/platform/spi/sam_x7x/spi_master.cpp.in index 218c7dc44d..1b17901a16 100644 --- a/src/modm/platform/spi/sam_x7x/spi_master.cpp.in +++ b/src/modm/platform/spi/sam_x7x/spi_master.cpp.in @@ -23,14 +23,14 @@ modm::platform::SpiMaster{{ id }}::transfer(uint8_t data) %% if use_fiber // wait for previous transfer to finish while(!(SpiHal{{ id }}::readStatusFlags() & StatusFlag::TxRegisterEmpty)) - modm::fiber::yield(); + modm::this_fiber::yield(); // start transfer by copying data into register SpiHal{{ id }}::write(data); // wait for current transfer to finish while(!(SpiHal{{ id }}::readStatusFlags() & StatusFlag::RxRegisterFull)) - modm::fiber::yield(); + modm::this_fiber::yield(); // read the received byte SpiHal{{ id }}::read(data); diff --git a/src/modm/platform/spi/stm32/spi_master.cpp.in b/src/modm/platform/spi/stm32/spi_master.cpp.in index 825fc911fe..07baf4b5da 100644 --- a/src/modm/platform/spi/stm32/spi_master.cpp.in +++ b/src/modm/platform/spi/stm32/spi_master.cpp.in @@ -22,14 +22,14 @@ modm::platform::SpiMaster{{ id }}::transfer(uint8_t data) %% if use_fiber // wait for previous transfer to finish while(!SpiHal{{ id }}::isTransmitRegisterEmpty()) - modm::fiber::yield(); + modm::this_fiber::yield(); // start transfer by copying data into register SpiHal{{ id }}::write(data); // wait for current transfer to finish while(!SpiHal{{ id }}::isReceiveRegisterNotEmpty()) - modm::fiber::yield(); + modm::this_fiber::yield(); // read the received byte SpiHal{{ id }}::read(data); diff --git a/src/modm/platform/spi/stm32/spi_master_dma_impl.hpp.in b/src/modm/platform/spi/stm32/spi_master_dma_impl.hpp.in index da2587a585..5d8102a8f9 100644 --- a/src/modm/platform/spi/stm32/spi_master_dma_impl.hpp.in +++ b/src/modm/platform/spi/stm32/spi_master_dma_impl.hpp.in @@ -61,14 +61,14 @@ modm::platform::SpiMaster{{ id }}_Dma::transfer(uint // wait for previous transfer to finish while(!SpiHal{{ id }}::isTransmitRegisterEmpty()) - modm::fiber::yield(); + modm::this_fiber::yield(); // start transfer by copying data into register SpiHal{{ id }}::write(data); // wait for current transfer to finish while(!SpiHal{{ id }}::isReceiveRegisterNotEmpty()) - modm::fiber::yield(); + modm::this_fiber::yield(); // read the received byte SpiHal{{ id }}::read(data); @@ -146,14 +146,14 @@ modm::platform::SpiMaster{{ id }}_Dma::transfer( { if (dmaError) break; else if (not dmaTransmitComplete and not dmaReceiveComplete) - modm::fiber::yield(); + modm::this_fiber::yield(); %% if "fifo" in features else if (SpiHal{{ id }}::getInterruptFlags() & (SpiBase::InterruptFlag::Busy | SpiBase::InterruptFlag::FifoTxLevel | SpiBase::InterruptFlag::FifoRxLevel)) %% else else if (SpiHal{{ id }}::getInterruptFlags() & SpiBase::InterruptFlag::Busy) %% endif - modm::fiber::yield(); + modm::this_fiber::yield(); else break; } diff --git a/src/modm/platform/spi/stm32_uart/uart_spi_master.cpp.in b/src/modm/platform/spi/stm32_uart/uart_spi_master.cpp.in index 282303cd39..f03238143c 100644 --- a/src/modm/platform/spi/stm32_uart/uart_spi_master.cpp.in +++ b/src/modm/platform/spi/stm32_uart/uart_spi_master.cpp.in @@ -31,7 +31,7 @@ modm::platform::UartSpiMaster{{ id }}::transfer(uint8_t data) %% if use_fiber // wait for previous transfer to finish while (!UsartHal{{ id }}::isTransmitRegisterEmpty()) - modm::fiber::yield(); + modm::this_fiber::yield(); // start transfer by copying data into register if(dataOrder == DataOrder::MsbFirst) @@ -40,7 +40,7 @@ modm::platform::UartSpiMaster{{ id }}::transfer(uint8_t data) // wait for current transfer to finish while (!UsartHal{{ id }}::isReceiveRegisterNotEmpty()) - modm::fiber::yield(); + modm::this_fiber::yield(); UsartHal{{ id }}::read(data); diff --git a/src/modm/platform/spi/stm32h7/spi_master.cpp.in b/src/modm/platform/spi/stm32h7/spi_master.cpp.in index 02b498a617..b7275e86c6 100644 --- a/src/modm/platform/spi/stm32h7/spi_master.cpp.in +++ b/src/modm/platform/spi/stm32h7/spi_master.cpp.in @@ -25,12 +25,12 @@ SpiMaster{{ id }}::transfer(uint8_t data) { %% if use_fiber while (Hal::isTxFifoFull()) - modm::fiber::yield(); + modm::this_fiber::yield(); Hal::write(data); while (!Hal::isRxDataAvailable()) - modm::fiber::yield(); + modm::this_fiber::yield(); return Hal::read(); %% else @@ -83,7 +83,7 @@ SpiMaster{{ id }}::transfer( ++rxIndex; } if (rxIndex < length) { - modm::fiber::yield(); + modm::this_fiber::yield(); } } %% else diff --git a/src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in b/src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in index a159b8f3b2..92039310b4 100644 --- a/src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in +++ b/src/modm/platform/spi/stm32h7/spi_master_dma_impl.hpp.in @@ -66,7 +66,7 @@ SpiMaster{{ id }}_Dma::transfer(uint8_t data) // wait for transfer to complete while (!Hal::isTransferCompleted()) - modm::fiber::yield(); + modm::this_fiber::yield(); data = SpiHal{{ id }}::read(); finishTransfer(); @@ -174,7 +174,7 @@ SpiMaster{{ id }}_Dma::transfer( break; } } - modm::fiber::yield(); + modm::this_fiber::yield(); } finishTransfer(); %% else diff --git a/src/modm/processing/fiber/barrier.hpp b/src/modm/processing/fiber/barrier.hpp new file mode 100644 index 0000000000..1045553f19 --- /dev/null +++ b/src/modm/processing/fiber/barrier.hpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include "fiber.hpp" +#include + +namespace modm::fiber +{ + +/// @ingroup modm_processing_fiber +/// @{ + +/// Implements the `std::barrier` interface for fibers. +/// @warning This implementation is not interrupt-safe! +/// @see https://en.cppreference.com/w/cpp/thread/barrier +template< class CompletionFunction = decltype([]{}) > +class barrier +{ + barrier(const barrier&) = delete; + barrier& operator=(const barrier&) = delete; + using count_t = uint16_t; + + const CompletionFunction completion; + count_t expected; + count_t count; + count_t sequence{}; + +public: + using arrival_token = count_t; + + constexpr explicit + barrier(std::ptrdiff_t expected, CompletionFunction f = CompletionFunction()) + : completion(std::move(f)), expected(expected), count(expected) {} + + [[nodiscard]] + static constexpr std::ptrdiff_t + max() { return std::numeric_limits::max(); } + + [[nodiscard]] + arrival_token + arrive(count_t n=1) + { + count_t last_arrival{sequence}; + if (n < count) count -= n; + else + { + count = expected; + sequence++; + completion(); + } + return last_arrival; + } + + void + wait(arrival_token arrival) const + { + while (arrival == sequence) modm::this_fiber::yield(); + } + + void + arrive_and_wait() + { + wait(arrive()); + } + + void + arrive_and_drop() + { + if (expected) expected--; + (void) arrive(); + } +}; + +/// @} + +} diff --git a/src/modm/processing/fiber/condition_variable.hpp b/src/modm/processing/fiber/condition_variable.hpp new file mode 100644 index 0000000000..5b03ac29af --- /dev/null +++ b/src/modm/processing/fiber/condition_variable.hpp @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include "fiber.hpp" +#include "stop_token.hpp" +#include + + +namespace modm::fiber +{ + +/// @ingroup modm_processing_fiber +/// @{ + +enum class +cv_status +{ + no_timeout, + timeout +}; + +/// Implements the `std::condition_variable_any` interface for fibers. +/// @see https://en.cppreference.com/w/cpp/thread/condition_variable +class condition_variable_any +{ + condition_variable_any(const condition_variable_any&) = delete; + condition_variable_any& operator=(const condition_variable_any&) = delete; + + std::atomic sequence{}; + + const auto inline wait_on_sequence() + { + return [this, poll_sequence = sequence.load(std::memory_order_acquire)] + { return poll_sequence != sequence.load(std::memory_order_acquire); }; + } +public: + constexpr condition_variable_any() = default; + + /// @note This function can be called from an interrupt. + void inline + notify_one() + { + sequence.fetch_add(1, std::memory_order_release); + } + + /// @note This function can be called from an interrupt. + void inline + notify_any() + { + notify_one(); + } + + + template< class Lock > + void + wait(Lock& lock) + { + lock.unlock(); + this_fiber::poll(wait_on_sequence()); + lock.lock(); + } + + template< class Lock, class Predicate > + requires requires { std::is_invocable_r_v; } + void + wait(Lock& lock, Predicate&& pred) + { + while (not std::forward(pred)()) wait(lock); + } + + template< class Lock, class Predicate > + requires requires { std::is_invocable_r_v; } + bool + wait(Lock& lock, stop_token stoken, Predicate&& pred) + { + while (not stoken.stop_requested()) + { + if (std::forward(pred)()) return true; + wait(lock); + } + return std::forward(pred)(); + } + + + template< class Lock, class Rep, class Period > + cv_status + wait_for(Lock& lock, std::chrono::duration rel_time) + { + lock.unlock(); + const bool result = this_fiber::poll_for(rel_time, wait_on_sequence()); + lock.lock(); + return result ? cv_status::no_timeout : cv_status::timeout; + } + + template< class Lock, class Rep, class Period, class Predicate > + requires requires { std::is_invocable_r_v; } + bool + wait_for(Lock& lock, std::chrono::duration rel_time, Predicate&& pred) + { + while (not std::forward(pred)()) + { + if (wait_for(lock, rel_time) == cv_status::timeout) + return std::forward(pred)(); + } + return true; + } + + template< class Lock, class Rep, class Period, class Predicate > + requires requires { std::is_invocable_r_v; } + bool + wait_for(Lock& lock, stop_token stoken, + std::chrono::duration rel_time, Predicate&& pred) + { + while (not stoken.stop_requested()) + { + if (std::forward(pred)()) return true; + if (wait_for(lock, rel_time) == cv_status::timeout) + return std::forward(pred)(); + } + return std::forward(pred)(); + } + + + template< class Lock, class Clock, class Duration > + cv_status + wait_until(Lock& lock, std::chrono::time_point abs_time) + { + lock.unlock(); + const bool result = this_fiber::poll_until(abs_time, wait_on_sequence()); + lock.lock(); + return result ? cv_status::no_timeout : cv_status::timeout; + } + + template< class Lock, class Clock, class Duration, class Predicate > + requires requires { std::is_invocable_r_v; } + bool + wait_until(Lock& lock, std::chrono::time_point abs_time, Predicate&& pred) + { + while (not std::forward(pred)()) + { + if (wait_until(lock, abs_time) == cv_status::timeout) + return std::forward(pred)(); + } + return true; + } + + template< class Lock, class Clock, class Duration, class Predicate > + requires requires { std::is_invocable_r_v; } + bool + wait_until(Lock& lock, stop_token stoken, + std::chrono::time_point abs_time, Predicate&& pred) + { + while (not stoken.stop_requested()) + { + if (std::forward(pred)()) return true; + if (wait_until(lock, abs_time) == cv_status::timeout) + return std::forward(pred)(); + } + return std::forward(pred)(); + } +}; + +/// There is no specialization for `std::unique_lock`. +using condition_variable = condition_variable_any; + +/// @} + +} diff --git a/src/modm/processing/fiber/functions.hpp b/src/modm/processing/fiber/functions.hpp index bef9c5a73f..0000684cb7 100644 --- a/src/modm/processing/fiber/functions.hpp +++ b/src/modm/processing/fiber/functions.hpp @@ -15,8 +15,9 @@ #include "scheduler.hpp" #include +#include -namespace modm::fiber +namespace modm::this_fiber { /// @ingroup modm_processing_fiber @@ -39,35 +40,129 @@ namespace modm::fiber inline void yield() { - Scheduler::instance().yield(); + modm::fiber::Scheduler::instance().yield(); +} + +/// Returns the id of the current fiber +inline modm::fiber::id +get_id() +{ + return modm::fiber::Scheduler::instance().get_id(); +} + +/// Yields the current fiber until `bool condition()` returns true. +template< class Function > +requires requires { std::is_invocable_r_v; } +void +poll(Function &&condition) +{ + while(not std::forward(condition)()) + modm::this_fiber::yield(); } /** - * Yields the current fiber until the time interval has elapsed. - * This functionality is a convenience wrapper around `modm::Timeout` - * if interval ≥1ms or `modm::PreciseTimeout` if interval ≥1µs. - * For nanosecond delays, use `modm::delay(ns)`. + * Yields the current fiber until `bool condition()` returns true or the time + * duration has elapsed. + * + * @returns `true` if the condition was met, `false` if the time duration has + * elapsed. * * @note Due to the overhead of `yield()` and the scheduling other fibers, the - * sleep interval may be longer without any guarantee of an upper limit. + * sleep duration may be longer without any guarantee of an upper limit. */ -template< typename Rep, typename Period > -void -sleep(std::chrono::duration interval) +template< class Rep, class Period, class Function > +requires requires { std::is_invocable_r_v; } +[[nodiscard]] +bool +poll_for(std::chrono::duration sleep_duration, Function &&condition) { // Only choose the microsecond clock if necessary - using TimeoutType = std::conditional_t< + using Clock = std::conditional_t< std::is_convertible_v, std::chrono::duration>, - modm::GenericTimeout< modm::chrono::milli_clock, modm::chrono::milli_clock::duration>, - modm::GenericTimeout< modm::chrono::micro_clock, modm::chrono::micro_clock::duration> - >; + modm::chrono::milli_clock, modm::chrono::micro_clock>; - TimeoutType timeout(interval); - while(not timeout.isExpired()) - modm::fiber::yield(); + const auto start = Clock::now(); + do { + if (std::forward(condition)()) return true; + modm::this_fiber::yield(); + } + while((Clock::now() - start) <= sleep_duration); + return false; +} + +/** + * Yields the current fiber until `bool condition()` returns true or the sleep + * time has been reached. + * + * @returns `true` if the condition was met, `false` if the sleep time has + * elapsed. + * + * @note Due to the overhead of `yield()` and the scheduling other fibers, the + * sleep duration may be longer without any guarantee of an upper limit. + */ +template< class Clock, class Duration, class Function > +requires requires { std::is_invocable_r_v; } +[[nodiscard]] +bool +poll_until(std::chrono::time_point sleep_time, Function &&condition) +{ + const auto start = Clock::now(); + const auto sleep_duration = sleep_time - start; + do { + if (std::forward(condition)()) return true; + modm::this_fiber::yield(); + } + while((Clock::now() - start) <= sleep_duration); + return false; +} + +/** + * Yields the current fiber until the time duration has elapsed. + * + * @note For nanosecond delays, use `modm::delay(ns)`. + * @note Due to the overhead of `yield()` and the scheduling other fibers, the + * sleep duration may be longer without any guarantee of an upper limit. + * @see https://en.cppreference.com/w/cpp/thread/sleep_for + */ +template< class Rep, class Period > +void +sleep_for(std::chrono::duration sleep_duration) +{ + (void) poll_for(sleep_duration, []{ return false; }); +} + +/** + * Yields the current fiber until the sleep time has been reached. + * + * @note Due to the overhead of `yield()` and the scheduling other fibers, the + * sleep duration may be longer without any guarantee of an upper limit. + * @see https://en.cppreference.com/w/cpp/thread/sleep_until + */ +template< class Clock, class Duration > +void +sleep_until(std::chrono::time_point sleep_time) +{ + (void) poll_until(sleep_time, []{ return false; }); } /// @} -} // namespace modm::fiber +} // namespace modm::this_fiber + +/// @cond +// DEPRECATE: 2025q2 +namespace modm::fiber +{ + +[[deprecated("Use `modm::this_fiber::yield()` instead!")]] +void inline yield() +{ this_fiber::yield(); } + +template< class Rep, class Period > +[[deprecated("Use `modm::this_fiber::sleep_for()` instead!")]] +void sleep(std::chrono::duration sleep_duration) +{ this_fiber::sleep_for(sleep_duration); } + +} +/// @endcond diff --git a/src/modm/processing/fiber/latch.hpp b/src/modm/processing/fiber/latch.hpp new file mode 100644 index 0000000000..ef05a4ddcc --- /dev/null +++ b/src/modm/processing/fiber/latch.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include "fiber.hpp" +#include +#include + +namespace modm::fiber +{ + +/// @ingroup modm_processing_fiber +/// @{ + +/// Implements the `std::latch` interface for fibers. +/// @see https://en.cppreference.com/w/cpp/thread/latch +class latch +{ + latch(const latch&) = delete; + latch& operator=(const latch&) = delete; + + using count_t = uint16_t; + std::atomic count; + +public: + constexpr explicit + latch(std::ptrdiff_t expected) + : count(expected) {} + + [[nodiscard]] + static constexpr std::ptrdiff_t + max() { return std::numeric_limits::max(); } + + /// @note This function can be called from an interrupt. + void inline + count_down(count_t n=1) + { + // ensure we do not underflow the counter! + count_t value = count.load(std::memory_order_relaxed); + do if (value == 0) return; + while (not count.compare_exchange_weak(value, value >= n ? value - n : 0, + std::memory_order_acquire, std::memory_order_relaxed)); + } + + /// @note This function can be called from an interrupt. + [[nodiscard]] + bool inline + try_wait() const + { + return count.load(std::memory_order_relaxed) == 0; + } + + void inline + wait() const + { + while(not try_wait()) modm::this_fiber::yield(); + } + + void inline + arrive_and_wait(std::ptrdiff_t n=1) + { + count_down(n); + wait(); + } +}; + +/// @} + +} diff --git a/src/modm/processing/fiber/module.lb b/src/modm/processing/fiber/module.lb index 9289a2608f..b13285bae2 100644 --- a/src/modm/processing/fiber/module.lb +++ b/src/modm/processing/fiber/module.lb @@ -21,12 +21,13 @@ def is_enabled(env): not env.has_module(":processing:protothread") def prepare(module, options): - module.depends(":processing:timer") + module.depends(":processing:timer", ":architecture:atomic") module.add_query( EnvironmentQuery(name="__enabled", factory=is_enabled)) core = options[":target"].get_driver("core")["type"] + if core.startswith("cortex-m"): module.depends(":cmsis:device") return (core.startswith("cortex-m") or core.startswith("avr") or "x86_64" in core or "arm64" in core) @@ -46,6 +47,7 @@ def build(env): "with_fpu": with_fpu, "target": env[":target"].identifier, "multicore": env.has_module(":platform:multicore"), + "num_cores": 1, } if env.has_module(":platform:multicore"): cores = int(env[":target"].identifier.cores) @@ -77,3 +79,11 @@ def build(env): env.copy("task.hpp") env.copy("functions.hpp") env.copy("fiber.hpp") + + env.copy("mutex.hpp") + env.copy("shared_mutex.hpp") + env.copy("semaphore.hpp") + env.copy("latch.hpp") + env.copy("barrier.hpp") + env.copy("stop_token.hpp") + env.copy("condition_variable.hpp") diff --git a/src/modm/processing/fiber/module.md b/src/modm/processing/fiber/module.md index c114dff869..b3a5fb3db6 100644 --- a/src/modm/processing/fiber/module.md +++ b/src/modm/processing/fiber/module.md @@ -4,17 +4,16 @@ This module provides a lightweight stackful fiber implementation including a simple round-robin scheduler. Here is a minimal example that blinks an LED: ```cpp -modm::Fiber<> fiber([]() +modm::Fiber<> fiber([] { Board::LedBlue::setOutput(); - modm::fiber::yield(); while(true) { Board::LedBlue::toggle(); - modm::fiber::sleep(1s); + modm::this_fiber::sleep_for(1s); } }); -int main(void) +int main() { modm::fiber::Scheduler::run(); return 0; @@ -27,7 +26,7 @@ int main(void) You can construct a fiber from any function without return type or arguments: ```cpp -modm::Fiber<> fiber([](){}); +modm::Fiber<> fiber([]{}); void function() {} modm::Fiber<> fiber2(function); ``` @@ -42,7 +41,7 @@ struct DataObject void member_function(int arg); } object; int number{42}; -modm::Fiber<> fiber([&]() +modm::Fiber<> fiber([&] { object.member_function(number); }); @@ -54,7 +53,7 @@ capture, or construct them in the capture directly, if they would get destroyed after fiber construction. You may need to mark the lambda mutable: ```cpp -modm::Fiber<> fiber2([obj=std::move(object), obj2=DataObject()]() mutable +modm::Fiber<> fiber2([obj=std::move(object), obj2=DataObject()] mutable { obj.member_function(24); obj2.member_function(42); @@ -66,25 +65,47 @@ modm::Fiber<> fiber2([obj=std::move(object), obj2=DataObject()]() mutable the allocated fiber stack size is likely too large for the caller stack and will lead to a stack overflow. +A fiber can be passed a `modm::fiber::stop_token` to allow the fiber to be +stopped cooperatively. -## Execution +```cpp +modm::Fiber<> fiber([](modm::fiber::stop_token stoken) +{ + // set up + while(not stoken.stop_requested()) + { + // run your task + } + // clean up +}); +// externally request the fiber to stop +fiber.request_stop(); +// wait until fiber has stopped +fiber.join(); +``` + +Note that the fiber destructor requests to stop and joins automatically. +The interface and behavior is similar to the C++20 `std::jthread`. + + +## Delayed Start Fiber are added to the scheduler automatically and start execution when the -scheduler is run. You can disable this behavior by setting `start` to `false` -during construction and manually starting the fiber when it is ready, also from -another fiber: +scheduler is run. You can disable this behavior by setting `start` to +`modm::fiber::Start::Later` during construction and manually starting the fiber +when it is ready, also from another fiber: ```cpp // fiber does not automatically start executing -modm::Fiber<> fiber(function, false); +modm::Fiber<> fiber2(function, modm::fiber::Start::Later); // fiber2 is automatically executing -modm::Fiber<> fiber2([&]() +modm::Fiber<> fiber1([&] { - modm::fiber::sleep(1s); - fiber.start(); + modm::this_fiber::sleep_for(1s); + fiber2.start(); }); modm::fiber::Scheduler::run(); -// fiber waits 1s, then starts fiber2 and exits +// fiber1 waits 1s, then starts fiber2 and exits ``` Fibers can end by returning from their wrapper, after which they will be removed @@ -95,7 +116,7 @@ restarts. If you need a fiber that is only callable once, you can implement this behavior manually with a boolean in the capture: ```cpp -modm::Fiber<> fiber([ran=false]() +modm::Fiber<> fiber([ran=false] { if (ran) return; ran = true; @@ -104,31 +125,6 @@ modm::Fiber<> fiber([ran=false]() ``` -## Scheduling - -The scheduler `run()` function will suspend execution of the call site, usually -the main function, start each fiber and continue to execute them until they all -ended and then return execution to the call site: - -```cpp -while(true) -{ - modm::fiber::Scheduler::run(); - // sleep until the next interrupt? - __WFI(); - // then start the fibers again - fiber.start(); -} -``` - -Please note that neither the fiber nor scheduler is interrupt safe, so starting -threads from interrupt context is a bad idea! - -!!! note "Using `yield()` outside of a fiber" - If `yield()` is called before the scheduler started or if only one fiber is - running, it simply returns in-place, since there is nowhere to switch to. - - ## Customization The most important customization is the fiber stack size expressed in bytes: @@ -160,6 +156,96 @@ modm_fastdata modm::fiber::Task fiber(large_stack, big_function); ``` +## Concurrency Support + +The `modm::fiber` namespace provides several standard concurrency primitives to +synchronize fibers based on the [`std::thread` interface behavior][std_thread]. +Most primitives are implemented on top of ``, therefore can be called +from within (nested) interrupts. The API docs explicitly mention if a function +is safe to call from an interrupt. + + +### Threads + +- `Task` implements most of the `std::jthread` interface. + +In particular, `Task` only implements functionality that does not require +dynamic memory allocations. The stack memory needs to be allocated externally +and fibers are not movable or copyable and therefore cannot be detached or +swapped. + + +### Thread Cancellation + +- `stop_token` and `stop_source` with simplified implementations. +- `stop_callback` **not implemented**. + +To avoid dynamic memory allocations, a `stop_state` object provides the actual +memory required for the limited functionality: + +```cpp +modm::fiber::stop_state state; +// only valid as long as state is valid! +auto source = state.get_source(); +auto token = state.get_token(); +// use token in a condition variable +cv.wait(lock, token, predicate); +// request a stop somewhere else +source.request_stop(); +``` + +Implemented using interrupt-safe atomics. + + +### Mutual Exclusion + +- `mutex` and `timed_mutex`. +- `recursive_mutex` and `recursive_timed_mutex`. +- `shared_mutex` and `shared_timed_mutex`. + +Implemented using interrupt-safe atomics. + +#### Generic Mutex Management + +- `lock_guard`, `scoped_lock`, `unique_lock` and `shared_lock`. +- `defer_lock_t`, `try_to_lock_t` and `adopt_lock_t`. +- `defer_lock`, `try_to_lock` and `adopt_lock`. + +#### Generic Locking Algorithms + +- `try_lock` and `lock`. + +#### Call Once + +- `once_flag` and `call_once`. + +Implemented using interrupt-safe atomic flag. + + +### Condition Variables + +- `condition_variable` and `condition_variable_any`. +- `cv_status`. +- `notify_all_at_thread_exit` **not implemented**. + +Notification is implemented as a interrupt-safe 16-bit atomic counter. + + +### Semaphores + +- `counting_semaphore` and `binary_semaphore`. + +Counts are implemented as interrupt-safe 16-bits atomics. + + +### Latches and Barriers + +- `latch`: implemented as interrupt-safe atomics. +- `barrier`: **not** interrupt-safe! + +Counts are implemented as 16-bits. + + ## Stack Usage It is difficult to measure stack usage without hardware support, however, @@ -190,11 +276,36 @@ Note that stack usage measurement through watermarking can be inaccurate if the registers contain the watermark value. +## Scheduling + +The scheduler `run()` function will suspend execution of the call site, usually +the main function, start each fiber and continue to execute them until they all +ended and then return execution to the call site: + +```cpp +while(true) +{ + modm::fiber::Scheduler::run(); + // sleep until the next interrupt? + __WFI(); + // then start the fibers again + fiber.start(); +} +``` + +Please note that neither the fiber nor scheduler is interrupt safe, so starting +threads from interrupt context is a bad idea! + +!!! note "Using `yield()` outside of a fiber" + If `yield()` is called before the scheduler started or if only one fiber is + running, it simply returns in-place, since there is nowhere to switch to. + + ## Platforms Fibers are implemented by saving callee registers to the current stack, then switching to a new stack and restoring callee registers from this stack. -The static `modm::fiber::yield()` function wraps this functionality in a +The static `modm::this_fiber::yield()` function wraps this functionality in a transparent way. ### AVR @@ -228,7 +339,7 @@ and task into the core-affine memory: // allocate into core0 memory modm_faststack_core0 modm::Fiber<> fiber0(function); // allocate into core1 memory but DO NOT start yet! -modm_faststack_core1 modm::Fiber<> fiber1(function, false); +modm_faststack_core1 modm::Fiber<> fiber1(function, modm::fiber::Start::Later); void core1_main() { @@ -245,3 +356,5 @@ int main() return 0; } ``` + +[std_thread]: https://en.cppreference.com/w/cpp/thread diff --git a/src/modm/processing/fiber/mutex.hpp b/src/modm/processing/fiber/mutex.hpp new file mode 100644 index 0000000000..47bccd4769 --- /dev/null +++ b/src/modm/processing/fiber/mutex.hpp @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include "fiber.hpp" +#include +#include +#include +#include + +namespace modm::fiber +{ + +/// @ingroup modm_processing_fiber +/// @{ + +/// Implements the `std::mutex` interface for fibers. +/// @see https://en.cppreference.com/w/cpp/thread/mutex +class mutex +{ + mutex(const mutex&) = delete; + mutex& operator=(const mutex&) = delete; + + std::atomic_bool locked{false}; +public: + constexpr mutex() = default; + + /// @note This function can be called from an interrupt. + [[nodiscard]] + bool inline + try_lock() + { + return not locked.exchange(true, std::memory_order_acquire); + } + + void inline + lock() + { + while(not try_lock()) modm::this_fiber::yield(); + } + + /// @note This function can be called from an interrupt. + void inline + unlock() + { + locked.store(false, std::memory_order_release); + } +}; + +/// Implements the `std::timed_mutex` interface for fibers. +/// @see https://en.cppreference.com/w/cpp/thread/timed_mutex +class timed_mutex : public mutex +{ +public: + template< typename Rep, typename Period > + [[nodiscard]] + bool + try_lock_for(std::chrono::duration sleep_duration) + { + return this_fiber::poll_for(sleep_duration, [this]{ return try_lock(); }); + } + + template< class Clock, class Duration > + [[nodiscard]] + bool + try_lock_until(std::chrono::time_point sleep_time) + { + return this_fiber::poll_until(sleep_time, [this]{ return try_lock(); }); + } +}; + +/// Implements the `std::recursive_mutex` interface for fibers. +/// @see https://en.cppreference.com/w/cpp/thread/recursive_mutex +class recursive_mutex +{ + recursive_mutex(const recursive_mutex&) = delete; + recursive_mutex& operator=(const recursive_mutex&) = delete; + using count_t = uint16_t; + + static constexpr fiber::id NoOwner{fiber::id(-1)}; + volatile fiber::id owner{NoOwner}; + static constexpr count_t countMax{count_t(-1)}; + volatile count_t count{1}; + +public: + constexpr recursive_mutex() = default; + + /// @note This function can be called from an interrupt. + [[nodiscard]] + bool inline + try_lock() + { + const auto id = modm::this_fiber::get_id(); + modm::atomic::Lock _; + if (owner == NoOwner) { + owner = id; + // count = 1; is implicit + return true; + } + if (owner == id and count < countMax) { + count++; + return true; + } + return false; + } + + void inline + lock() + { + while(not try_lock()) modm::this_fiber::yield(); + } + + /// @note This function can be called from an interrupt. + void inline + unlock() + { + modm::atomic::Lock _; + if (count > 1) count--; + else { + // count = 1; is implicit + owner = NoOwner; + } + } +}; + +/// Implements the `std::recursive_timed_mutex` interface for fibers. +/// @see https://en.cppreference.com/w/cpp/thread/recursive_timed_mutex +class recursive_timed_mutex : public recursive_mutex +{ +public: + template< typename Rep, typename Period > + [[nodiscard]] + bool + try_lock_for(std::chrono::duration sleep_duration) + { + return this_fiber::poll_for(sleep_duration, [this]{ return try_lock(); }); + } + + template< class Clock, class Duration > + [[nodiscard]] + bool + try_lock_until(std::chrono::time_point sleep_time) + { + return this_fiber::poll_until(sleep_time, [this]{ return try_lock(); }); + } +}; + + + +/// Implements the `std::once_flag` interface for fibers. +/// @see https://en.cppreference.com/w/cpp/thread/once_flag +class once_flag +{ + template + friend void call_once(once_flag&, Callable&&, Args&&...); + + once_flag(const once_flag&) = delete; + once_flag& operator=(const once_flag&) = delete; + + std::atomic_flag state{}; + inline bool try_call() { return not state.test_and_set(std::memory_order_acquire); } + +public: + constexpr once_flag() = default; +}; + +/// Implements the `std::call_once` interface for fibers. +/// @see https://en.cppreference.com/w/cpp/thread/call_once +template +void +call_once(once_flag& flag, Callable&& f, Args&&... args) +{ + if (flag.try_call()) std::forward(f)(std::forward(args)...); +} + +// Reuse the stdlibc++ implementation for the rest + +/// @see https://en.cppreference.com/w/cpp/thread/lock_guard +using ::std::lock_guard; + +/// @see https://en.cppreference.com/w/cpp/thread/scoped_lock +using ::std::scoped_lock; +/// @see https://en.cppreference.com/w/cpp/thread/unique_lock +using ::std::unique_lock; + +/// @see https://en.cppreference.com/w/cpp/thread/lock_tag_t +using ::std::defer_lock_t; +/// @see https://en.cppreference.com/w/cpp/thread/lock_tag_t +using ::std::try_to_lock_t; +/// @see https://en.cppreference.com/w/cpp/thread/lock_tag_t +using ::std::adopt_lock_t; + +/// @see https://en.cppreference.com/w/cpp/thread/lock_tag +using ::std::defer_lock; +/// @see https://en.cppreference.com/w/cpp/thread/lock_tag +using ::std::try_to_lock; +/// @see https://en.cppreference.com/w/cpp/thread/lock_tag +using ::std::adopt_lock; + +/// @see https://en.cppreference.com/w/cpp/thread/try_lock +using ::std::try_lock; +/// @see https://en.cppreference.com/w/cpp/thread/lock +using ::std::lock; + +/// @} + +} diff --git a/src/modm/processing/fiber/scheduler.hpp.in b/src/modm/processing/fiber/scheduler.hpp.in index 1e6747753d..4f48549cad 100644 --- a/src/modm/processing/fiber/scheduler.hpp.in +++ b/src/modm/processing/fiber/scheduler.hpp.in @@ -13,11 +13,17 @@ #pragma once -#include "fiber.hpp" +#include "task.hpp" #include %% if multicore #include %% endif +%% if core.startswith("cortex-m") +#include +%% endif + +// forward declaration +namespace modm::this_fiber { void yield(); modm::fiber::id get_id(); } namespace modm::fiber { @@ -33,7 +39,8 @@ namespace modm::fiber class Scheduler { friend class Task; - friend void yield(); + friend void modm::this_fiber::yield(); + friend modm::fiber::id modm::this_fiber::get_id(); Scheduler(const Scheduler&) = delete; Scheduler& operator=(const Scheduler&) = delete; @@ -41,6 +48,26 @@ protected: Task* last{nullptr}; Task* current{nullptr}; + uintptr_t + get_id() const + { +%% if core.startswith("cortex-m") + // Ensure that calling this in an interrupt gives a different ID + if (const auto irq = __get_IPSR(); irq) return irq; +%% endif + return reinterpret_cast(current); + } + + static bool + isInsideInterrupt() + { +%% if core.startswith("cortex-m") + return __get_IPSR(); +%% else + return false; +%% endif + } + void runNext(Task* task) { @@ -145,6 +172,12 @@ protected: public: constexpr Scheduler() = default; + static constexpr unsigned int + hardware_concurrency() + { + return {{num_cores}}; + } + /// Runs the currently active scheduler. static void run() diff --git a/src/modm/processing/fiber/semaphore.hpp b/src/modm/processing/fiber/semaphore.hpp new file mode 100644 index 0000000000..4b41fb73cc --- /dev/null +++ b/src/modm/processing/fiber/semaphore.hpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include "fiber.hpp" +#include +#include + +namespace modm::fiber +{ + +/// @ingroup modm_processing_fiber +/// @{ + +/// Implements the `std::counting_semaphore` interface for fibers. +/// @see https://en.cppreference.com/w/cpp/thread/counting_semaphore +template< std::ptrdiff_t LeastMaxValue = 255 > +class counting_semaphore +{ + counting_semaphore(const counting_semaphore&) = delete; + counting_semaphore& operator=(const counting_semaphore&) = delete; + + static_assert(LeastMaxValue <= uint16_t(-1), "counting_semaphore uses a 16-bit counter!"); + using count_t = std::conditional_t<(LeastMaxValue < 256), uint8_t, uint16_t>; + std::atomic count{}; + +public: + constexpr explicit + counting_semaphore(std::ptrdiff_t desired) + : count(desired) {} + + [[nodiscard]] + static constexpr std::ptrdiff_t + max() { return std::numeric_limits::max(); } + + /// @note This function can be called from an interrupt. + [[nodiscard]] + bool inline + try_acquire() + { + count_t current = count.load(std::memory_order_relaxed); + do if (current == 0) return false; + while(not count.compare_exchange_weak(current, current - 1, + std::memory_order_acquire, std::memory_order_relaxed)); + return true; + } + + void inline + acquire() + { + while(not try_acquire()) modm::this_fiber::yield(); + } + + /// @note This function can be called from an interrupt. + void inline + release() + { + count.fetch_add(1, std::memory_order_release); + } + + template< typename Rep, typename Period > + [[nodiscard]] + bool + try_acquire_for(std::chrono::duration sleep_duration) + { + return this_fiber::poll_for(sleep_duration, [this]{ return try_acquire(); }); + } + + template< class Clock, class Duration > + [[nodiscard]] + bool + try_acquire_until(std::chrono::time_point sleep_time) + { + return this_fiber::poll_until(sleep_time, [this]{ return try_acquire(); }); + } +}; + +/// Implements the `std::binary_semaphore` interface for fibers. +/// @see https://en.cppreference.com/w/cpp/thread/counting_semaphore +using binary_semaphore = counting_semaphore<1>; + +/// @} + +} diff --git a/src/modm/processing/fiber/shared_mutex.hpp b/src/modm/processing/fiber/shared_mutex.hpp new file mode 100644 index 0000000000..000a92348b --- /dev/null +++ b/src/modm/processing/fiber/shared_mutex.hpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include "fiber.hpp" +#include +#include + +namespace modm::fiber +{ + +/// @ingroup modm_processing_fiber +/// @{ + +/// Implements the `std::shared_mutex` interface for fibers. +/// @see https://en.cppreference.com/w/cpp/thread/shared_mutex +class shared_mutex +{ + shared_mutex(const shared_mutex&) = delete; + shared_mutex& operator=(const shared_mutex&) = delete; + + static constexpr fiber::id NoOwner{fiber::id(-1)}; + static constexpr fiber::id SharedOwner{fiber::id(-2)}; + std::atomic owner{NoOwner}; +public: + constexpr shared_mutex() = default; + + /// @note This function can be called from an interrupt. + [[nodiscard]] + bool inline + try_lock() + { + const fiber::id new_owner = modm::this_fiber::get_id(); + fiber::id expected{NoOwner}; + return owner.compare_exchange_strong(expected, new_owner, + std::memory_order_acquire, std::memory_order_relaxed); + } + + void inline + lock() + { + while(not try_lock()) modm::this_fiber::yield(); + } + + /// @note This function can be called from an interrupt. + void inline + unlock() + { + owner.store(NoOwner, std::memory_order_release); + } + + /// @note This function can be called from an interrupt. + [[nodiscard]] + bool inline + try_lock_shared() + { + fiber::id current = owner.load(std::memory_order_relaxed); + do if (current < SharedOwner) return false; + while(not owner.compare_exchange_weak(current, SharedOwner, + std::memory_order_acquire, std::memory_order_relaxed)); + return true; + } + + void inline + lock_shared() + { + while(not try_lock_shared()) modm::this_fiber::yield(); + } + + /// @note This function can be called from an interrupt. + void inline + unlock_shared() + { + owner.store(NoOwner, std::memory_order_release); + } +}; + +/// Implements the `std::shared_timed_mutex` interface for fibers. +/// @see https://en.cppreference.com/w/cpp/thread/shared_timed_mutex +class shared_timed_mutex : public shared_mutex +{ +public: + template< typename Rep, typename Period > + [[nodiscard]] + bool + try_lock_for(std::chrono::duration sleep_duration) + { + return this_fiber::poll_for(sleep_duration, [this]{ return try_lock(); }); + } + + template< class Clock, class Duration > + [[nodiscard]] + bool + try_lock_until(std::chrono::time_point sleep_time) + { + return this_fiber::poll_until(sleep_time, [this]{ return try_lock(); }); + } + + template< typename Rep, typename Period > + [[nodiscard]] + bool + try_lock_shared_for(std::chrono::duration sleep_duration) + { + return this_fiber::poll_for(sleep_duration, [this]{ return try_lock_shared(); }); + } + + template< class Clock, class Duration > + [[nodiscard]] + bool + try_lock_shared_until(std::chrono::time_point sleep_time) + { + return this_fiber::poll_until(sleep_time, [this]{ return try_lock_shared(); }); + } +}; + + +// Reuse the stdlibc++ implementation +using ::std::shared_lock; + +/// @} + +} diff --git a/src/modm/processing/fiber/stop_token.hpp b/src/modm/processing/fiber/stop_token.hpp new file mode 100644 index 0000000000..7529c1daf7 --- /dev/null +++ b/src/modm/processing/fiber/stop_token.hpp @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include + +namespace modm::fiber +{ + +/// @ingroup modm_processing_fiber +/// @{ + +class stop_source; +class stop_token; + +/** + * Provides the implementation and memory storage for the stop request. + * You must allocate this object yourself and derive the token and source then + * from this object. This manual memory management differs from the `std` + * interface. + * + * @warning The lifetime of this object must be longer or equal to the lifetime + * of the derived `stop_token` and `stop_source`. + */ +class stop_state +{ + std::atomic_bool requested{false}; + +public: + /// @note This function can be called from an interrupt. + [[nodiscard]] + bool inline + stop_requested() const + { + return requested.load(std::memory_order_relaxed); + } + + /// @note This function can be called from an interrupt. + bool inline + request_stop() + { + return not requested.exchange(true, std::memory_order_relaxed); + } + + constexpr stop_source + get_source(); + + constexpr stop_token + get_token(); +}; + +/// Implements the `std::stop_token` interface for fibers. +/// @see https://en.cppreference.com/w/cpp/thread/stop_token +class stop_token +{ + friend class stop_state; + const stop_state *state{nullptr}; + + constexpr explicit stop_token(const stop_state *state) : state(state) {} +public: + constexpr stop_token() = default; + constexpr stop_token(const stop_token&) = default; + constexpr stop_token(stop_token&&) = default; + constexpr ~stop_token() = default; + constexpr stop_token& operator=(const stop_token&) = default; + constexpr stop_token& operator=(stop_token&&) = default; + + [[nodiscard]] + bool inline + stop_possible() const + { + return state; + } + + [[nodiscard]] + bool inline + stop_requested() const + { + return stop_possible() and state->stop_requested(); + } + + void inline + swap(stop_token& rhs) + { + std::swap(state, rhs.state); + } + + [[nodiscard]] + friend bool inline + operator==(const stop_token& a, const stop_token& b) + { + return a.state == b.state; + } + + friend void inline + swap(stop_token& lhs, stop_token& rhs) + { + lhs.swap(rhs); + } +}; + +/// Implements the `std::stop_source` interface for fibers. +/// @see https://en.cppreference.com/w/cpp/thread/stop_source +class stop_source +{ + friend class stop_state; + stop_state *state{nullptr}; + explicit constexpr stop_source(stop_state *state) : state(state) {} + +public: + constexpr stop_source() = default; + constexpr stop_source(const stop_source&) = default; + constexpr stop_source(stop_source&&) = default; + constexpr ~stop_source() = default; + constexpr stop_source& operator=(const stop_source&) = default; + constexpr stop_source& operator=(stop_source&&) = default; + + [[nodiscard]] + constexpr bool + stop_possible() const + { + return state; + } + + [[nodiscard]] + bool inline + stop_requested() const + { + return stop_possible() and state->stop_requested(); + } + + bool inline + request_stop() + { + return stop_possible() and state->request_stop(); + } + + [[nodiscard]] + stop_token inline + get_token() const + { + return state->get_token(); + } + + void inline + swap(stop_source& rhs) + { + std::swap(state, rhs.state); + } + + [[nodiscard]] + friend bool inline + operator==(const stop_source& a, const stop_source& b) + { + return a.state == b.state; + } + + friend void inline + swap(stop_source& lhs, stop_source& rhs) + { + lhs.swap(rhs); + } +}; + +/// @cond +constexpr stop_source +stop_state::get_source() +{ + return stop_source{this}; +} + +constexpr stop_token +stop_state::get_token() +{ + return stop_token{this}; +} +/// @endcond + +/// @} + +} diff --git a/src/modm/processing/fiber/task.hpp b/src/modm/processing/fiber/task.hpp index a0270add92..b9957c3a3b 100644 --- a/src/modm/processing/fiber/task.hpp +++ b/src/modm/processing/fiber/task.hpp @@ -13,11 +13,16 @@ #include "stack.hpp" #include "context.h" +#include "stop_token.hpp" #include +// forward declaration +namespace modm::this_fiber { void yield(); } + namespace modm::fiber { +// forward declaration class Scheduler; /// The Fiber scheduling policy. @@ -29,6 +34,10 @@ Start Later, // Manually add the fiber to a scheduler. }; +/// Identifier of a fiber task. +/// @ingroup modm_processing_fiber +using id = uintptr_t; + /** * The fiber task connects the callable fiber object with the fiber context and * scheduler. It constructs the fiber function on the stack if necessary, and @@ -40,6 +49,7 @@ Start * managing a fiber. You may therefore place objects of this class in fast * core-local memory, while the stack must remain in DMA-able memory! * + * @see https://en.cppreference.com/w/cpp/thread/jthread * @author Erik Henriksson * @author Niklas Hauser * @ingroup modm_processing_fiber @@ -56,17 +66,72 @@ class Task modm_context_t ctx; Task* next; Scheduler *scheduler{nullptr}; + stop_state stop{}; public: /// @param stack A stack object that is *NOT* shared with other tasks. - /// @param closure A callable object of signature `void(*)()`. + /// @param closure A callable object of signature `void()`. /// @param start When to start this task. - template - Task(Stack& stack, T&& closure, Start start=Start::Now); + template + Task(Stack& stack, Callable&& closure, Start start=Start::Now); + + inline + ~Task() + { + request_stop(); + join(); + } + + /// Returns the number of concurrent threads supported by the implementation. + [[nodiscard]] static constexpr unsigned int + hardware_concurrency(); + + /// Returns a value of std::thread::id identifying the thread associated + /// with `*this`. + /// @note This function can be called from an interrupt. + [[nodiscard]] modm::fiber::id inline + get_id() const + { + return reinterpret_cast(this); + } + + /// Checks if the Task object identifies an active fiber of execution. + [[nodiscard]] bool + joinable() const; + + /// Blocks the current fiber until the fiber identified by `*this` + /// finishes its execution. Returns immediately if the thread is not joinable. + void inline + join() + { + if (joinable()) while(isRunning()) modm::this_fiber::yield(); + } + + [[nodiscard]] + stop_source inline + get_stop_source() + { + return stop.get_source(); + } + + [[nodiscard]] + stop_token inline + get_stop_token() + { + return stop.get_token(); + } + + /// @note This function can be called from an interrupt. + bool inline + request_stop() + { + return stop.request_stop(); + } + /// Watermarks the stack to measure `stack_usage()` later. /// @see `modm_context_watermark()`. - void + void inline watermark_stack() { modm_context_watermark(&ctx); @@ -74,7 +139,7 @@ class Task /// @returns the stack usage as measured by a watermark level. /// @see `modm_context_stack_usage()`. - size_t + [[nodiscard]] size_t inline stack_usage() const { return modm_context_stack_usage(&ctx); @@ -82,7 +147,7 @@ class Task /// @returns if the bottom word on the stack has been overwritten. /// @see `modm_context_stack_overflow()`. - bool + [[nodiscard]] bool inline stack_overflow() const { return modm_context_stack_overflow(&ctx); @@ -94,7 +159,7 @@ class Task start(); /// @returns if the fiber is attached to a scheduler. - bool + [[nodiscard]] bool inline isRunning() const { return scheduler; @@ -113,16 +178,21 @@ namespace modm::fiber template Task::Task(Stack& stack, T&& closure, Start start) { - if constexpr (std::is_convertible_v) + constexpr bool with_stop_token = std::is_invocable_r_v; + if constexpr (std::is_convertible_v or + std::is_convertible_v) { // A plain function without closure - auto caller = (uintptr_t) +[](void(*fn)()) + using Callable = std::conditional_t; + auto caller = (uintptr_t) +[](Callable fn) { - fn(); + if constexpr (with_stop_token) { + fn(fiber::Scheduler::instance().current->get_stop_token()); + } else fn(); fiber::Scheduler::instance().unschedule(); }; modm_context_init(&ctx, stack.memory, stack.memory + stack.words, - caller, (uintptr_t) static_cast(closure)); + caller, (uintptr_t) static_cast(closure)); } else { @@ -130,7 +200,7 @@ Task::Task(Stack& stack, T&& closure, Start start) constexpr size_t align_mask = std::max(StackAlignment, alignof(std::decay_t)) - 1u; constexpr size_t closure_size = (sizeof(std::decay_t) + align_mask) & ~align_mask; static_assert(Size >= closure_size + StackSizeMinimum, - "Stack size must ≥({{min_stack_size}}B + aligned sizeof(closure))!"); + "Stack size must be larger than minimum stack size + aligned sizeof(closure))!"); // Find a suitable aligned area at the top of stack to allocate the closure const uintptr_t ptr = uintptr_t(stack.memory + stack.words) - closure_size; // construct closure in place @@ -138,7 +208,9 @@ Task::Task(Stack& stack, T&& closure, Start start) // Encapsulate the proper ABI function call into a simpler function auto caller = (uintptr_t) +[](std::decay_t* closure) { - (*closure)(); + if constexpr (with_stop_token) { + (*closure)(fiber::Scheduler::instance().current->get_stop_token()); + } else (*closure)(); fiber::Scheduler::instance().unschedule(); }; // initialize the stack below the allocated closure @@ -152,9 +224,23 @@ Task::start() { if (isRunning()) return false; modm_context_reset(&ctx); - fiber::Scheduler::instance().add(*this); + Scheduler::instance().add(*this); return true; } +constexpr unsigned int +Task::hardware_concurrency() +{ + return Scheduler::hardware_concurrency(); +} + +bool inline +Task::joinable() const +{ + if (not isRunning()) return false; + if (Scheduler::isInsideInterrupt()) return false; + return get_id() != Scheduler::instance().get_id(); +} + } /// @endcond diff --git a/src/modm/processing/protothread/macros_fiber.hpp b/src/modm/processing/protothread/macros_fiber.hpp index acd5cd35d7..df61ffe2ea 100644 --- a/src/modm/processing/protothread/macros_fiber.hpp +++ b/src/modm/processing/protothread/macros_fiber.hpp @@ -39,7 +39,7 @@ /// Yield protothread till next call to its run(). /// \hideinitializer #define PT_YIELD() \ - modm::fiber::yield() + modm::this_fiber::yield() /// Cause protothread to wait **while** given condition is true. /// \hideinitializer diff --git a/src/modm/processing/protothread/module.md b/src/modm/processing/protothread/module.md index 8db6ef0ace..e90b182139 100644 --- a/src/modm/processing/protothread/module.md +++ b/src/modm/processing/protothread/module.md @@ -83,11 +83,11 @@ option, which replaces the preprocessor macros and C++ implementations of this and the `modm:processing:resumable` module with a fiber version. Specifically, the `PT_*` and `RF_*` macros are now forwarding their arguments -unmodified and instead relying on `modm::fiber::yield()` for context switching: +unmodified and instead relying on `modm::this_fiber::yield()` for context switching: ```cpp -#define PT_YIELD() modm::fiber::yield() -#define PT_WAIT_WHILE(cond) while(cond) { modm::fiber::yield(); } +#define PT_YIELD() modm::this_fiber::yield() +#define PT_WAIT_WHILE(cond) while(cond) { modm::this_fiber::yield(); } #define PT_CALL(func) func ``` diff --git a/src/modm/processing/protothread/protothread_fiber.hpp b/src/modm/processing/protothread/protothread_fiber.hpp index 9c13df71ab..816b27f4be 100644 --- a/src/modm/processing/protothread/protothread_fiber.hpp +++ b/src/modm/processing/protothread/protothread_fiber.hpp @@ -30,7 +30,7 @@ class Protothread : public modm::Fiber< MODM_PROTOTHREAD_STACK_SIZE > { public: Protothread(modm::fiber::Start start=modm::fiber::Start::Now) - : Fiber([this](){ while(update()) modm::fiber::yield(); }, start) + : Fiber([this](){ while(update()) modm::this_fiber::yield(); }, start) {} void restart() { this->start(); } diff --git a/src/modm/processing/resumable/macros_fiber.hpp b/src/modm/processing/resumable/macros_fiber.hpp index a867bbb2ec..60dde2c981 100644 --- a/src/modm/processing/resumable/macros_fiber.hpp +++ b/src/modm/processing/resumable/macros_fiber.hpp @@ -52,7 +52,7 @@ /// Yield resumable function until next invocation. /// @hideinitializer #define RF_YIELD() \ - modm::fiber::yield() + modm::this_fiber::yield() /// Cause resumable function to wait until given child protothread completes. /// @hideinitializer diff --git a/src/modm/processing/resumable/module.md b/src/modm/processing/resumable/module.md index 300ae5e176..e815774716 100644 --- a/src/modm/processing/resumable/module.md +++ b/src/modm/processing/resumable/module.md @@ -164,17 +164,17 @@ preprocessor macros and C++ implementations of this and the `modm:processing:protothreads` module with a fiber version. Specifically, the `PT_*` and `RF_*` macros are now forwarding their arguments -unmodified and instead relying on `modm::fiber::yield()` for context switching: +unmodified and instead relying on `modm::this_fiber::yield()` for context switching: ```cpp -#define RF_YIELD() modm::fiber::yield() -#define RF_WAIT_WHILE(cond) while(cond) { modm::fiber::yield(); } +#define RF_YIELD() modm::this_fiber::yield() +#define RF_WAIT_WHILE(cond) while(cond) { modm::this_fiber::yield(); } #define RF_CALL(func) func #define RF_RETURN(value) return value ``` You may call `RF_CALL_BLOCKING(resumable)` outside a fiber context, in which -case the `modm::fiber::yield()` will return immediately, which is the same +case the `modm::this_fiber::yield()` will return immediately, which is the same behavior as before. However, the `modm::ResumableResult`, `modm::Resumable`, and `modm::NestedResumable` diff --git a/test/modm/ext/cxa_guard_test.cpp b/test/modm/ext/cxa_guard_test.cpp index aa89e13f69..8908604433 100644 --- a/test/modm/ext/cxa_guard_test.cpp +++ b/test/modm/ext/cxa_guard_test.cpp @@ -16,10 +16,10 @@ extern "C" { -#ifdef MODM_CPU_ARM -using guard_t = uint32_t; +#ifdef MODM_CPU_CORTEX_M +using guard_t = int32_t; #else -using guard_t = uint64_t; +using guard_t = int64_t; #endif int __cxa_guard_acquire(guard_t*); diff --git a/test/modm/processing/fiber/fiber_condition_variable_test.cpp b/test/modm/processing/fiber/fiber_condition_variable_test.cpp new file mode 100644 index 0000000000..458c8a7b75 --- /dev/null +++ b/test/modm/processing/fiber/fiber_condition_variable_test.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "fiber_condition_variable_test.hpp" +#include "shared.hpp" +#include + +static struct +{ + uint8_t lock_count{0}, unlock_count{0}; + void inline reset() { lock_count = 0; unlock_count = 0; } + void inline lock() { lock_count++; } + void inline unlock() { unlock_count++; } +} elock; + +bool predicate_value{false}; +static bool predicate() { return predicate_value; } + +void +ConditionVariableTest::setUp() +{ + state = 0; + elock.reset(); + predicate_value = false; +} + +// ========================== CONDITION VARIABLE WAIT ========================= +static modm::fiber::condition_variable cv; + +static void +f1() +{ + TEST_ASSERT_EQUALS(state++, 0u); + + cv.wait(elock); // goto 1 + + TEST_ASSERT_EQUALS(elock.lock_count, 1u); + TEST_ASSERT_EQUALS(elock.unlock_count, 1u); + + TEST_ASSERT_EQUALS(state++, 3u); +} + +static void +f2() +{ + TEST_ASSERT_EQUALS(state++, 1u); + // let f1 wait for a while + modm::this_fiber::yield(); + modm::this_fiber::yield(); + modm::this_fiber::yield(); + + TEST_ASSERT_EQUALS(state++, 2u); + cv.notify_one(); + modm::this_fiber::yield(); // goto 3 + + TEST_ASSERT_EQUALS(state++, 4u); +} + +void +ConditionVariableTest::testConditionVariableWait() +{ + modm::fiber::Task fiber1(stack1, f1), fiber2(stack2, f2); + modm::fiber::Scheduler::run(); +} + +// ===================== CONDITION VARIABLE WAIT PREDICATE ==================== +static void +f3() +{ + TEST_ASSERT_EQUALS(state++, 0u); + + cv.wait(elock, predicate); // goto 1 + + TEST_ASSERT_EQUALS(elock.lock_count, 4u); + TEST_ASSERT_EQUALS(elock.unlock_count, 4u); + + TEST_ASSERT_EQUALS(state++, 4u); +} + +static void +f4() +{ + TEST_ASSERT_EQUALS(state++, 1u); + // let f3 wait for a while + modm::this_fiber::yield(); + modm::this_fiber::yield(); + modm::this_fiber::yield(); + + TEST_ASSERT_EQUALS(state++, 2u); + cv.notify_one(); + modm::this_fiber::yield(); + cv.notify_any(); + modm::this_fiber::yield(); + cv.notify_one(); + modm::this_fiber::yield(); + + TEST_ASSERT_EQUALS(state++, 3u); + predicate_value = true; + cv.notify_one(); + modm::this_fiber::yield(); // goto 4 + + TEST_ASSERT_EQUALS(state++, 5u); +} + +void +ConditionVariableTest::testConditionVariableWaitPredicate() +{ + modm::fiber::Task fiber1(stack1, f3), fiber2(stack2, f4); + modm::fiber::Scheduler::run(); +} + +// ===================== CONDITION VARIABLE WAIT PREDICATE ==================== +static modm::fiber::stop_state stop; + +static void +f5() +{ + TEST_ASSERT_EQUALS(state++, 0u); + + cv.wait(elock, stop.get_token(), predicate); // goto 1 + + TEST_ASSERT_EQUALS(elock.lock_count, 4u); + TEST_ASSERT_EQUALS(elock.unlock_count, 4u); + + TEST_ASSERT_EQUALS(state++, 4u); +} + +static void +f6() +{ + TEST_ASSERT_EQUALS(state++, 1u); + // let f3 wait for a while + modm::this_fiber::yield(); + modm::this_fiber::yield(); + modm::this_fiber::yield(); + + TEST_ASSERT_EQUALS(state++, 2u); + cv.notify_one(); + modm::this_fiber::yield(); + cv.notify_any(); + modm::this_fiber::yield(); + cv.notify_one(); + modm::this_fiber::yield(); + + TEST_ASSERT_EQUALS(state++, 3u); + stop.request_stop(); + cv.notify_one(); + modm::this_fiber::yield(); // goto 4 + + TEST_ASSERT_EQUALS(state++, 5u); +} + +void +ConditionVariableTest::testConditionVariableWaitStopTokenPredicate() +{ + modm::fiber::Task fiber1(stack1, f5), fiber2(stack2, f6); + modm::fiber::Scheduler::run(); +} diff --git a/test/modm/processing/fiber/fiber_condition_variable_test.hpp b/test/modm/processing/fiber/fiber_condition_variable_test.hpp new file mode 100644 index 0000000000..46a5455657 --- /dev/null +++ b/test/modm/processing/fiber/fiber_condition_variable_test.hpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020, Erik Henriksson + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include + +/// @ingroup modm_test_test_architecture +class ConditionVariableTest : public unittest::TestSuite +{ +public: + void + setUp(); + + void + testConditionVariableWait(); + + void + testConditionVariableWaitPredicate(); + + void + testConditionVariableWaitStopTokenPredicate(); +}; diff --git a/test/modm/processing/fiber/fiber_guard_test.cpp b/test/modm/processing/fiber/fiber_guard_test.cpp new file mode 100644 index 0000000000..8a0acbfafe --- /dev/null +++ b/test/modm/processing/fiber/fiber_guard_test.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2023, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "fiber_guard_test.hpp" +#include "shared.hpp" +#include + +extern "C" +{ + +#ifdef MODM_CPU_CORTEX_M +using guard_t = int32_t; +#else +using guard_t = int64_t; +#endif + +int __cxa_guard_acquire(guard_t*); +void __cxa_guard_release(guard_t*); +void __cxa_guard_abort(guard_t*); + +static guard_t guard{0}; + +} + +void +FiberGuardTest::setUp() +{ + state = 0; +} + +// ================================== GUARD =================================== +static void +f1() +{ + TEST_ASSERT_EQUALS(state++, 0u); + TEST_ASSERT_EQUALS(guard, guard_t(0)); + + TEST_ASSERT_EQUALS(__cxa_guard_acquire(&guard), 1); + TEST_ASSERT_EQUALS(guard, guard_t(0x10)); + // now while initializing yield to another fiber + modm::this_fiber::yield(); // goto 1 + + TEST_ASSERT_EQUALS(state++, 2u); + __cxa_guard_release(&guard); + TEST_ASSERT_EQUALS(guard, guard_t(1)); +} + +static void +f2() +{ + TEST_ASSERT_EQUALS(state++, 1u); + TEST_ASSERT_EQUALS(guard, guard_t(0x10)); + TEST_ASSERT_EQUALS(__cxa_guard_acquire(&guard), 0); // goto 2 + + TEST_ASSERT_EQUALS(state++, 3u); + TEST_ASSERT_EQUALS(guard, guard_t(1)); +} + + +void +FiberGuardTest::testGuard() +{ +#ifndef MODM_OS_HOSTED + modm::fiber::Task fiber1(stack1, f1), fiber2(stack2, f2); + modm::fiber::Scheduler::run(); +#endif +} + +// =============================== CONSTRUCTOR ================================ +static uint8_t constructor_calls{0}; +struct StaticClass +{ + uint8_t counter{0}; + StaticClass() + { + constructor_calls++; + } + + void increment() + { + counter++; + } +}; + +static StaticClass& instance() +{ + static StaticClass obj; + return obj; +} + +void +FiberGuardTest::testConstructor() +{ + TEST_ASSERT_EQUALS(constructor_calls, 0); + + instance().increment(); + TEST_ASSERT_EQUALS(constructor_calls, 1); + TEST_ASSERT_EQUALS(instance().counter, 1); + TEST_ASSERT_EQUALS(constructor_calls, 1); + + instance().increment(); + TEST_ASSERT_EQUALS(constructor_calls, 1); + TEST_ASSERT_EQUALS(instance().counter, 2); + TEST_ASSERT_EQUALS(constructor_calls, 1); + + instance().increment(); + TEST_ASSERT_EQUALS(constructor_calls, 1); + TEST_ASSERT_EQUALS(instance().counter, 3); + TEST_ASSERT_EQUALS(constructor_calls, 1); +} diff --git a/test/modm/processing/fiber/fiber_guard_test.hpp b/test/modm/processing/fiber/fiber_guard_test.hpp new file mode 100644 index 0000000000..3b988dac43 --- /dev/null +++ b/test/modm/processing/fiber/fiber_guard_test.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020, Erik Henriksson + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include + +/// @ingroup modm_test_test_architecture +class FiberGuardTest : public unittest::TestSuite +{ +public: + void + setUp(); + + void + testGuard(); + + void + testConstructor(); +}; diff --git a/test/modm/processing/fiber/fiber_latch_barrier_test.cpp b/test/modm/processing/fiber/fiber_latch_barrier_test.cpp new file mode 100644 index 0000000000..be5ba0f291 --- /dev/null +++ b/test/modm/processing/fiber/fiber_latch_barrier_test.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "fiber_latch_barrier_test.hpp" +#include "shared.hpp" +#include +#include + +void +LatchBarrierTest::setUp() +{ + state = 0; +} + +// ================================== LATCH =================================== +void +LatchBarrierTest::testLatch0() +{ + modm::fiber::latch latch{0}; + TEST_ASSERT_TRUE(latch.try_wait()); + latch.count_down(1); + TEST_ASSERT_TRUE(latch.try_wait()); + latch.count_down(100); + TEST_ASSERT_TRUE(latch.try_wait()); +} + +void +LatchBarrierTest::testLatch1() +{ + modm::fiber::latch latch{1}; + TEST_ASSERT_FALSE(latch.try_wait()); + latch.count_down(1); + TEST_ASSERT_TRUE(latch.try_wait()); + latch.count_down(100); + TEST_ASSERT_TRUE(latch.try_wait()); +} + +void +LatchBarrierTest::testLatch2() +{ + modm::fiber::latch latch{2}; + TEST_ASSERT_FALSE(latch.try_wait()); + latch.count_down(1); + TEST_ASSERT_FALSE(latch.try_wait()); + latch.count_down(100); + TEST_ASSERT_TRUE(latch.try_wait()); + latch.count_down(100); + TEST_ASSERT_TRUE(latch.try_wait()); +} + +void +LatchBarrierTest::testLatch10() +{ + modm::fiber::latch latch{10}; + TEST_ASSERT_FALSE(latch.try_wait()); + latch.count_down(1); + TEST_ASSERT_FALSE(latch.try_wait()); + latch.count_down(100); + TEST_ASSERT_TRUE(latch.try_wait()); + latch.count_down(100); + TEST_ASSERT_TRUE(latch.try_wait()); +} + +static modm::fiber::latch ltc{3}; + +static void +f1() +{ + TEST_ASSERT_EQUALS(state++, 0u); + TEST_ASSERT_FALSE(ltc.try_wait()); + + ltc.wait(); // goto 1 + + TEST_ASSERT_TRUE(ltc.try_wait()); + TEST_ASSERT_EQUALS(state++, 4u); +} + +static void +f2() +{ + TEST_ASSERT_EQUALS(state++, 1u); + // let f1 wait for a while + modm::this_fiber::yield(); + modm::this_fiber::yield(); + modm::this_fiber::yield(); + + TEST_ASSERT_EQUALS(state++, 2u); + ltc.count_down(); + TEST_ASSERT_FALSE(ltc.try_wait()); + modm::this_fiber::yield(); + + TEST_ASSERT_EQUALS(state++, 3u); + ltc.count_down(2); + TEST_ASSERT_TRUE(ltc.try_wait()); + modm::this_fiber::yield(); // goto 4 + + TEST_ASSERT_EQUALS(state++, 5u); +} + +void +LatchBarrierTest::testLatchWait() +{ + modm::fiber::Task fiber1(stack1, f1), fiber2(stack2, f2); + modm::fiber::Scheduler::run(); +} + +// ================================= BARRIER ================================== + +static modm::fiber::id completion_id; +static void on_completion() +{ + state++; + completion_id = modm::this_fiber::get_id(); +} + +void +LatchBarrierTest::testBarrier() +{ + modm::fiber::barrier bar{2, on_completion}; + + TEST_ASSERT_EQUALS(bar.arrive(0), 0u); + TEST_ASSERT_EQUALS(state, 0u); + TEST_ASSERT_EQUALS(bar.arrive(), 0u); + TEST_ASSERT_EQUALS(state, 0u); + TEST_ASSERT_EQUALS(bar.arrive(1), 0u); + TEST_ASSERT_EQUALS(state, 1u); + TEST_ASSERT_EQUALS(completion_id, 0u); + + TEST_ASSERT_EQUALS(bar.arrive(2), 1u); + TEST_ASSERT_EQUALS(state, 2u); + + TEST_ASSERT_EQUALS(bar.arrive(10), 2u); + TEST_ASSERT_EQUALS(state, 3u); + + bar.arrive_and_drop(); // expected=1 + TEST_ASSERT_EQUALS(state, 3u); + TEST_ASSERT_EQUALS(bar.arrive(), 3u); + TEST_ASSERT_EQUALS(state, 4u); + + TEST_ASSERT_EQUALS(bar.arrive(), 4u); + TEST_ASSERT_EQUALS(state, 5u); + + bar.arrive_and_drop(); // expected=0 + TEST_ASSERT_EQUALS(state, 6u); + + TEST_ASSERT_EQUALS(bar.arrive(), 6u); + TEST_ASSERT_EQUALS(state, 7u); +} + + +static modm::fiber::barrier brr{2, on_completion}; +static modm::fiber::id f3_id, f4_id; + +static void +f3() +{ + TEST_ASSERT_EQUALS(state++, 0u); + const auto token = brr.arrive(); + TEST_ASSERT_EQUALS(token, 0u); + + TEST_ASSERT_EQUALS(state++, 1u); + brr.wait(token); // goto 2 + TEST_ASSERT_EQUALS(completion_id, f4_id); + + const auto token2 = brr.arrive(); + // on_completion() called: state++ + TEST_ASSERT_EQUALS(token2, 1u); + + TEST_ASSERT_EQUALS(state++, 7u); + brr.wait(token2); // does not wait + TEST_ASSERT_EQUALS(completion_id, f3_id); + + TEST_ASSERT_EQUALS(state++, 8u); +} + +static void +f4() +{ + TEST_ASSERT_EQUALS(state++, 2u); + // let f3 wait for a while + modm::this_fiber::yield(); + modm::this_fiber::yield(); + modm::this_fiber::yield(); + + const auto token = brr.arrive(); + // on_completion() called: state++ + TEST_ASSERT_EQUALS(token, 0u); + + TEST_ASSERT_EQUALS(state++, 4u); + brr.wait(token); // does not wait + TEST_ASSERT_EQUALS(completion_id, f4_id); + + const auto token2 = brr.arrive(); + TEST_ASSERT_EQUALS(token2, 1u); + + TEST_ASSERT_EQUALS(state++, 5u); + brr.wait(token2); // goto 6 + TEST_ASSERT_EQUALS(completion_id, f3_id); + + TEST_ASSERT_EQUALS(state++, 9u); +} + + +void +LatchBarrierTest::testBarrierWait() +{ + modm::fiber::Task fiber1(stack1, f3), fiber2(stack2, f4); + f3_id = fiber1.get_id(); + f4_id = fiber2.get_id(); + modm::fiber::Scheduler::run(); +} diff --git a/test/modm/processing/fiber/fiber_latch_barrier_test.hpp b/test/modm/processing/fiber/fiber_latch_barrier_test.hpp new file mode 100644 index 0000000000..3d5c1f45e6 --- /dev/null +++ b/test/modm/processing/fiber/fiber_latch_barrier_test.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020, Erik Henriksson + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include + +/// @ingroup modm_test_test_architecture +class LatchBarrierTest : public unittest::TestSuite +{ +public: + void + setUp(); + + void + testLatch0(); + + void + testLatch1(); + + void + testLatch2(); + + void + testLatch10(); + + void + testLatchWait(); + + void + testBarrier(); + + void + testBarrierWait(); +}; diff --git a/test/modm/processing/fiber/fiber_mutex_test.cpp b/test/modm/processing/fiber/fiber_mutex_test.cpp new file mode 100644 index 0000000000..3eaebdfd62 --- /dev/null +++ b/test/modm/processing/fiber/fiber_mutex_test.cpp @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "fiber_mutex_test.hpp" +#include "shared.hpp" +#include +#include + +void +FiberMutexTest::setUp() +{ + state = 0; +} + +// ================================== MUTEX =================================== +static modm::fiber::mutex mtx; + +static void +f1() +{ + TEST_ASSERT_EQUALS(state++, 0u); + TEST_ASSERT_TRUE(mtx.try_lock()); + TEST_ASSERT_FALSE(mtx.try_lock()); + TEST_ASSERT_FALSE(mtx.try_lock()); + mtx.unlock(); + mtx.unlock(); + + TEST_ASSERT_EQUALS(state++, 1u); + mtx.lock(); // should not yield + TEST_ASSERT_EQUALS(state++, 2u); + mtx.lock(); // goto 3 + + mtx.unlock(); + mtx.unlock(); + TEST_ASSERT_EQUALS(state++, 5u); + mtx.lock(); // should not yield + TEST_ASSERT_EQUALS(state++, 6u); + mtx.lock(); // goto 7 + + TEST_ASSERT_EQUALS(state++, 8u); +} + +static void +f2() +{ + TEST_ASSERT_EQUALS(state++, 3u); + // let f1 wait for a while + modm::this_fiber::yield(); + modm::this_fiber::yield(); + modm::this_fiber::yield(); + TEST_ASSERT_EQUALS(state++, 4u); + mtx.unlock(); + modm::this_fiber::yield(); // goto 5 + + TEST_ASSERT_EQUALS(state++, 7u); + mtx.unlock(); + modm::this_fiber::yield(); // goto 8 + TEST_ASSERT_EQUALS(state++, 9u); +} + +void +FiberMutexTest::testMutex() +{ + // should not block + TEST_ASSERT_TRUE(mtx.try_lock()); + TEST_ASSERT_FALSE(mtx.try_lock()); + TEST_ASSERT_FALSE(mtx.try_lock()); + mtx.unlock(); + // multiple unlock calls should be fine too + mtx.unlock(); + mtx.unlock(); + mtx.unlock(); + // shouldn't block without scheduler + mtx.lock(); + mtx.unlock(); + + modm::fiber::Task fiber1(stack1, f1), fiber2(stack2, f2); + modm::fiber::Scheduler::run(); +} + +// ============================== RECURSIVE MUTEX ============================= +static modm::fiber::recursive_mutex rc_mtx; + +static void +f3() +{ + TEST_ASSERT_EQUALS(state++, 0u); + + TEST_ASSERT_TRUE(rc_mtx.try_lock()); + TEST_ASSERT_TRUE(rc_mtx.try_lock()); + TEST_ASSERT_TRUE(rc_mtx.try_lock()); + modm::this_fiber::yield(); // goto 1 + + TEST_ASSERT_EQUALS(state++, 3u); + rc_mtx.unlock(); + modm::this_fiber::yield(); + + TEST_ASSERT_EQUALS(state++, 4u); + rc_mtx.unlock(); + modm::this_fiber::yield(); + + TEST_ASSERT_EQUALS(state++, 5u); + rc_mtx.unlock(); + rc_mtx.unlock(); // more than necessary + rc_mtx.unlock(); + rc_mtx.unlock(); + modm::this_fiber::yield(); // goto 6 + + TEST_ASSERT_EQUALS(state++, 7u); + rc_mtx.lock(); // goto 8 + + TEST_ASSERT_EQUALS(state++, 11u); + rc_mtx.unlock(); + rc_mtx.unlock(); + + TEST_ASSERT_EQUALS(state++, 12u); +} + +static void +f4() +{ + TEST_ASSERT_EQUALS(state++, 1u); + TEST_ASSERT_FALSE(rc_mtx.try_lock()); + TEST_ASSERT_FALSE(rc_mtx.try_lock()); + TEST_ASSERT_FALSE(rc_mtx.try_lock()); + + TEST_ASSERT_EQUALS(state++, 2u); + rc_mtx.lock(); // goto 3 + + TEST_ASSERT_EQUALS(state++, 6u); + rc_mtx.lock(); + rc_mtx.lock(); + modm::this_fiber::yield(); // goto 7 + + TEST_ASSERT_EQUALS(state++, 8u); + rc_mtx.unlock(); + modm::this_fiber::yield(); + TEST_ASSERT_EQUALS(state++, 9u); + rc_mtx.unlock(); + modm::this_fiber::yield(); + TEST_ASSERT_EQUALS(state++, 10u); + rc_mtx.unlock(); + modm::this_fiber::yield(); // goto 11 + + TEST_ASSERT_EQUALS(state++, 13u); +} + +void +FiberMutexTest::testRecursiveMutex() +{ + // this should also work without scheduler, since fiber id is zero then + TEST_ASSERT_TRUE(rc_mtx.try_lock()); + TEST_ASSERT_TRUE(rc_mtx.try_lock()); + TEST_ASSERT_TRUE(rc_mtx.try_lock()); + rc_mtx.unlock(); + rc_mtx.unlock(); + rc_mtx.unlock(); + // more unlocks should be fine + rc_mtx.unlock(); + rc_mtx.unlock(); + + // should not block either without scheduler + rc_mtx.lock(); + rc_mtx.lock(); + rc_mtx.lock(); + rc_mtx.unlock(); + rc_mtx.unlock(); + rc_mtx.unlock(); + rc_mtx.unlock(); + + modm::fiber::Task fiber1(stack1, f3), fiber2(stack2, f4); + modm::fiber::Scheduler::run(); + + TEST_ASSERT_TRUE(rc_mtx.try_lock()); + TEST_ASSERT_TRUE(rc_mtx.try_lock()); + rc_mtx.unlock(); + rc_mtx.unlock(); +} + +// =============================== SHARED MUTEX =============================== +static modm::fiber::shared_mutex sh_mtx; + +static void +f5() +{ + TEST_ASSERT_EQUALS(state++, 0u); + // get the exclusive lock + sh_mtx.lock(); + TEST_ASSERT_FALSE(sh_mtx.try_lock()); + modm::this_fiber::yield(); // goto 1 + + TEST_ASSERT_EQUALS(state++, 2u); + sh_mtx.unlock(); + modm::this_fiber::yield(); // goto 3 + + TEST_ASSERT_EQUALS(state++, 4u); + // get the shared lock + sh_mtx.lock_shared(); + sh_mtx.lock_shared(); + modm::this_fiber::yield(); // goto 5 + + TEST_ASSERT_EQUALS(state++, 6u); + modm::this_fiber::yield(); + modm::this_fiber::yield(); + modm::this_fiber::yield(); + // still locked + sh_mtx.unlock_shared(); + modm::this_fiber::yield(); // goto 7 + + TEST_ASSERT_EQUALS(state++, 9u); +} + +static void +f6() +{ + TEST_ASSERT_EQUALS(state++, 1u); + // cannot get exclusive lock + sh_mtx.lock(); // goto 2 + + TEST_ASSERT_EQUALS(state++, 3u); + TEST_ASSERT_FALSE(sh_mtx.try_lock()); + sh_mtx.unlock(); + modm::this_fiber::yield(); // goto 4 + + TEST_ASSERT_EQUALS(state++, 5u); + // can get shared lock + sh_mtx.lock_shared(); + sh_mtx.lock_shared(); + // cannot get exclusive lock + sh_mtx.lock(); // goto 6 + + TEST_ASSERT_EQUALS(state++, 7u); + sh_mtx.unlock(); + + TEST_ASSERT_EQUALS(state++, 8u); +} + +void +FiberMutexTest::testSharedMutex() +{ + TEST_ASSERT_TRUE(sh_mtx.try_lock()); + TEST_ASSERT_FALSE(sh_mtx.try_lock()); + TEST_ASSERT_FALSE(sh_mtx.try_lock()); + sh_mtx.unlock(); + // more unlocks should be fine + sh_mtx.unlock(); + sh_mtx.unlock(); + + TEST_ASSERT_TRUE(sh_mtx.try_lock_shared()); + TEST_ASSERT_TRUE(sh_mtx.try_lock_shared()); + TEST_ASSERT_TRUE(sh_mtx.try_lock_shared()); + sh_mtx.unlock(); + // more unlocks should be fine + sh_mtx.unlock(); + sh_mtx.unlock(); + + + modm::fiber::Task fiber1(stack1, f5), fiber2(stack2, f6); + modm::fiber::Scheduler::run(); + + TEST_ASSERT_TRUE(rc_mtx.try_lock()); + rc_mtx.unlock(); + rc_mtx.unlock(); +} + +// =============================== TIMED MUTEX ================================ +// =========================== TIMED RECURSIVE MUTEX ========================== +// ============================ TIMED SHARED MUTEX ============================ +// All implementations only add poll_for/_until with try_lock*() function. +// Explicit tests are omitted, since they are tested in FiberTest::testSleep*(). + +void +FiberMutexTest::testCallOnce() +{ + modm::fiber::once_flag flag; + uint8_t ii = 0; + while(++ii < 10) modm::fiber::call_once(flag, []{ state++; }); + modm::fiber::call_once(flag, [&]{ + modm::fiber::call_once(flag, [&]{ + modm::fiber::call_once(flag, []{ state++; }); + }); + }); + TEST_ASSERT_EQUALS(ii, 10u); + TEST_ASSERT_EQUALS(state, 1u); +} diff --git a/test/modm/processing/fiber/fiber_mutex_test.hpp b/test/modm/processing/fiber/fiber_mutex_test.hpp new file mode 100644 index 0000000000..129a448ca3 --- /dev/null +++ b/test/modm/processing/fiber/fiber_mutex_test.hpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020, Erik Henriksson + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include + +/// @ingroup modm_test_test_architecture +class FiberMutexTest : public unittest::TestSuite +{ +public: + void + setUp(); + + void + testMutex(); + + void + testRecursiveMutex(); + + void + testSharedMutex(); + + void + testCallOnce(); +}; diff --git a/test/modm/processing/fiber/fiber_semaphore_test.cpp b/test/modm/processing/fiber/fiber_semaphore_test.cpp new file mode 100644 index 0000000000..ddf8bb601e --- /dev/null +++ b/test/modm/processing/fiber/fiber_semaphore_test.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "fiber_semaphore_test.hpp" +#include "shared.hpp" +#include + +void +FiberSemaphoreTest::setUp() +{ + state = 0; +} + +// ================================== MUTEX =================================== +static modm::fiber::counting_semaphore sem{3}; + +static void +f1() +{ + TEST_ASSERT_EQUALS(state++, 0u); + TEST_ASSERT_TRUE(sem.try_acquire()); // 2 + TEST_ASSERT_TRUE(sem.try_acquire()); // 1 + TEST_ASSERT_TRUE(sem.try_acquire()); // 0 + TEST_ASSERT_FALSE(sem.try_acquire()); // 0 + TEST_ASSERT_FALSE(sem.try_acquire()); // 0 + sem.release(); // 1 + sem.acquire(); // 0 + TEST_ASSERT_EQUALS(state++, 1u); + sem.acquire(); // goto 2, 0 + + TEST_ASSERT_EQUALS(state++, 4u); + sem.release(); // 1 + sem.release(); // 2 + modm::this_fiber::yield(); // goto 5 + + TEST_ASSERT_EQUALS(state++, 6u); + sem.acquire(); + sem.acquire(); // goto 7, 0 + + TEST_ASSERT_EQUALS(state++, 9u); +} + +static void +f2() +{ + TEST_ASSERT_EQUALS(state++, 2u); + modm::this_fiber::yield(); + + TEST_ASSERT_EQUALS(state++, 3u); + sem.release(); // 1 + modm::this_fiber::yield(); // goto 4 + + TEST_ASSERT_EQUALS(state++, 5u); + sem.acquire(); // 1 + modm::this_fiber::yield(); // goto 6 + + TEST_ASSERT_EQUALS(state++, 7u); + sem.release(); // 1 + sem.release(); // 2 + sem.release(); // 3 + + TEST_ASSERT_EQUALS(state++, 8u); +} + +void +FiberSemaphoreTest::testCountingSemaphore() +{ + // should not block + TEST_ASSERT_TRUE(sem.try_acquire()); + TEST_ASSERT_TRUE(sem.try_acquire()); + TEST_ASSERT_TRUE(sem.try_acquire()); + TEST_ASSERT_FALSE(sem.try_acquire()); + TEST_ASSERT_FALSE(sem.try_acquire()); + sem.release(); + // shouldn't block without scheduler + sem.acquire(); + sem.release(); + sem.release(); + sem.release(); + + modm::fiber::Task fiber1(stack1, f1), fiber2(stack2, f2); + modm::fiber::Scheduler::run(); +} + diff --git a/test/modm/processing/fiber/fiber_semaphore_test.hpp b/test/modm/processing/fiber/fiber_semaphore_test.hpp new file mode 100644 index 0000000000..888ca31eee --- /dev/null +++ b/test/modm/processing/fiber/fiber_semaphore_test.hpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020, Erik Henriksson + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include + +/// @ingroup modm_test_test_architecture +class FiberSemaphoreTest : public unittest::TestSuite +{ +public: + void + setUp(); + + void + testCountingSemaphore(); +}; diff --git a/test/modm/processing/fiber/fiber_test.cpp b/test/modm/processing/fiber/fiber_test.cpp index fb760b6ece..02b97775e1 100644 --- a/test/modm/processing/fiber/fiber_test.cpp +++ b/test/modm/processing/fiber/fiber_test.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Erik Henriksson + * Copyright (c) 2024, Niklas Hauser * * This file is part of the modm project. * @@ -10,107 +11,214 @@ // ---------------------------------------------------------------------------- #include "fiber_test.hpp" +#include "shared.hpp" -#include -#include -#include +#include -namespace -{ +using namespace std::chrono_literals; +using test_clock = modm_test::chrono::milli_clock; -enum State +void +FiberTest::setUp() { - INVALID, - F1_START, - F1_END, - F2_START, - F2_END, - F3_START, - F3_END, - SUBROUTINE_START, - SUBROUTINE_END, - CONSUMER_START, - CONSUMER_END, - PRODUCER_START, - PRODUCER_END, -}; + state = 0; +} -std::array states = {}; -size_t states_pos = 0; +// ================================== FIBER =================================== +static void +f1() +{ + TEST_ASSERT_EQUALS(state++, 0u); + modm::this_fiber::yield(); + TEST_ASSERT_EQUALS(state++, 1u); +} -#define ADD_STATE(state) states[states_pos++] = state; +void +FiberTest::testOneFiber() +{ + modm::fiber::Task fiber(stack1, f1); + TEST_ASSERT_DIFFERS(fiber.get_id(), modm::fiber::id(0)); + TEST_ASSERT_TRUE(fiber.joinable()); + modm::fiber::Scheduler::run(); + TEST_ASSERT_FALSE(fiber.joinable()); +} void -f1() +FiberTest::testTwoFibers() { - ADD_STATE(F1_START); - modm::fiber::yield(); - ADD_STATE(F1_END); + modm::fiber::Task fiber1(stack1, [] + { + TEST_ASSERT_EQUALS(state++, 0u); + modm::this_fiber::yield(); // goto 1 + TEST_ASSERT_EQUALS(state++, 2u); + modm::this_fiber::yield(); + TEST_ASSERT_EQUALS(state++, 3u); + modm::this_fiber::yield(); + TEST_ASSERT_EQUALS(state++, 4u); + modm::this_fiber::yield(); + TEST_ASSERT_EQUALS(state++, 5u); + }); + modm::fiber::Task fiber2(stack2, [&] + { + TEST_ASSERT_EQUALS(state++, 1u); + TEST_ASSERT_TRUE(fiber1.joinable()); + TEST_ASSERT_FALSE(fiber2.joinable()); + fiber1.join(); // goto 2 + TEST_ASSERT_EQUALS(state++, 6u); + fiber2.join(); // should not hang + TEST_ASSERT_EQUALS(state++, 7u); + }); + modm::fiber::Scheduler::run(); +} + +static __attribute__((noinline)) void +subroutine() +{ + TEST_ASSERT_EQUALS(state++, 2u); + modm::this_fiber::yield(); // goto 3 + TEST_ASSERT_EQUALS(state++, 4u); } void -f2() +FiberTest::testYieldFromSubroutine() { - ADD_STATE(F2_START); - modm::fiber::yield(); - ADD_STATE(F2_END); + modm::fiber::Task fiber1(stack1, [] + { + TEST_ASSERT_EQUALS(state++, 0u); + modm::this_fiber::yield(); // goto 1 + TEST_ASSERT_EQUALS(state++, 3u); + }); + modm::fiber::Task fiber2(stack2, [&] + { + TEST_ASSERT_EQUALS(state++, 1u); + TEST_ASSERT_TRUE(fiber1.joinable()); + subroutine(); + TEST_ASSERT_FALSE(fiber1.joinable()); + TEST_ASSERT_EQUALS(state++, 5u); + }); + modm::fiber::Scheduler::run(); } -modm::fiber::Stack<1024> stack1, stack2; -} // namespace +void +FiberTest::testPollFor() +{ + test_clock::setTime(1251); + TEST_ASSERT_TRUE(modm::this_fiber::poll_for(20ms, []{ return true; })); + // timeout is tested in the SleepFor() test +} + void -FiberTest::testOneFiber() +FiberTest::testPollUntil() { - states_pos = 0; - modm::fiber::Task fiber(stack1, f1); + test_clock::setTime(451250); + TEST_ASSERT_TRUE(modm::this_fiber::poll_until(modm::Clock::now() + 20ms, []{ return true; })); + TEST_ASSERT_TRUE(modm::this_fiber::poll_until(modm::Clock::now() - 20ms, []{ return true; })); + // timeout is tested in the SleepUntil() tests +} + + +static void +f4() +{ + TEST_ASSERT_EQUALS(state++, 0u); + modm::this_fiber::sleep_for(50ms); // goto 1 + TEST_ASSERT_EQUALS(state++, 5u); +} + +static void +f5() +{ + TEST_ASSERT_EQUALS(state++, 1u); + test_clock::increment(10); + TEST_ASSERT_EQUALS(state++, 2u); + modm::this_fiber::yield(); + + test_clock::increment(20); + TEST_ASSERT_EQUALS(state++, 3u); + modm::this_fiber::yield(); + + test_clock::increment(30); + TEST_ASSERT_EQUALS(state++, 4u); + modm::this_fiber::yield(); // goto 5 + + TEST_ASSERT_EQUALS(state++, 6u); +} + +static void +runSleepFor(uint32_t startTime) +{ + test_clock::setTime(startTime); + modm::fiber::Task fiber1(stack1, f4), fiber2(stack2, f5); modm::fiber::Scheduler::run(); - TEST_ASSERT_EQUALS(states_pos, 2u); - TEST_ASSERT_EQUALS(states[0], F1_START); - TEST_ASSERT_EQUALS(states[1], F1_END); } + void -FiberTest::testTwoFibers() +FiberTest::testSleepFor() { - states_pos = 0; - modm::fiber::Task fiber1(stack1, f1), fiber2(stack2, f2); - modm::fiber::Scheduler::run(); - TEST_ASSERT_EQUALS(states_pos, 4u); - TEST_ASSERT_EQUALS(states[0], F1_START); - TEST_ASSERT_EQUALS(states[1], F2_START); - TEST_ASSERT_EQUALS(states[2], F1_END); - TEST_ASSERT_EQUALS(states[3], F2_END); + runSleepFor(16203); + state = 0; + runSleepFor(0xffff'ffff - 30); } -__attribute__((noinline)) void -subroutine() +static void +f6() { - ADD_STATE(SUBROUTINE_START); - modm::fiber::yield(); - ADD_STATE(SUBROUTINE_END); + TEST_ASSERT_EQUALS(state++, 0u); // goto 1 + modm::this_fiber::sleep_until(modm::Clock::now() + 50ms); + TEST_ASSERT_EQUALS(state++, 5u); + modm::this_fiber::yield(); // goto 6 + modm::this_fiber::sleep_until(modm::Clock::now() - 50ms); + TEST_ASSERT_EQUALS(state++, 7u); +} + +static void +runSleepUntil(uint32_t startTime) +{ + test_clock::setTime(startTime); + modm::fiber::Task fiber1(stack1, f6), fiber2(stack2, f5); + modm::fiber::Scheduler::run(); } void -f3() +FiberTest::testSleepUntil() +{ + runSleepUntil(1502); + state = 0; + runSleepUntil(0xffff'ffff - 30); +} + +static void +f7(modm::fiber::stop_token stoken) { - ADD_STATE(F3_START); - subroutine(); - ADD_STATE(F3_END); + TEST_ASSERT_EQUALS(state++, 0u); + TEST_ASSERT_TRUE(stoken.stop_possible()); + TEST_ASSERT_FALSE(stoken.stop_requested()); + while(not stoken.stop_requested()) + { + modm::this_fiber::yield(); // goto 1 + state++; // 2, 4 + } + TEST_ASSERT_TRUE(stoken.stop_requested()); + TEST_ASSERT_EQUALS(state++, 5u); } void -FiberTest::testYieldFromSubroutine() +FiberTest::testStopToken() { - states_pos = 0; - modm::fiber::Task fiber1(stack1, f1), fiber2(stack2, f3); + modm::fiber::Task fiber1(stack1, f7); + modm::fiber::Task fiber2(stack2, [&](modm::fiber::stop_token stoken) + { + TEST_ASSERT_EQUALS(state++, 1u); + TEST_ASSERT_TRUE(stoken.stop_possible()); + TEST_ASSERT_FALSE(stoken.stop_requested()); + modm::this_fiber::yield(); // goto 2 + TEST_ASSERT_EQUALS(state++, 3u); + fiber1.request_stop(); + modm::this_fiber::yield(); // goto 4 + TEST_ASSERT_EQUALS(state++, 6u); + }); modm::fiber::Scheduler::run(); - TEST_ASSERT_EQUALS(states_pos, 6u); - TEST_ASSERT_EQUALS(states[0], F1_START); - TEST_ASSERT_EQUALS(states[1], F3_START); - TEST_ASSERT_EQUALS(states[2], SUBROUTINE_START); - TEST_ASSERT_EQUALS(states[3], F1_END); - TEST_ASSERT_EQUALS(states[4], SUBROUTINE_END); - TEST_ASSERT_EQUALS(states[5], F3_END); } diff --git a/test/modm/processing/fiber/fiber_test.hpp b/test/modm/processing/fiber/fiber_test.hpp index d8b91fe686..f288f0ba99 100644 --- a/test/modm/processing/fiber/fiber_test.hpp +++ b/test/modm/processing/fiber/fiber_test.hpp @@ -17,9 +17,8 @@ class FiberTest : public unittest::TestSuite { public: - void - subroutine(); + setUp(); void testOneFiber(); @@ -29,4 +28,19 @@ class FiberTest : public unittest::TestSuite void testYieldFromSubroutine(); + + void + testPollFor(); + + void + testPollUntil(); + + void + testSleepFor(); + + void + testSleepUntil(); + + void + testStopToken(); }; diff --git a/test/modm/processing/fiber/shared.hpp b/test/modm/processing/fiber/shared.hpp new file mode 100644 index 0000000000..3bdae1e8d0 --- /dev/null +++ b/test/modm/processing/fiber/shared.hpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024, Niklas Hauser + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once +#include +#include + +// shared objects to reduce memory consumption +[[maybe_unused]] static inline modm::fiber::Stack<> stack1, stack2; +[[maybe_unused]] static inline uint8_t state; diff --git a/tools/scripts/generate_module_docs.py b/tools/scripts/generate_module_docs.py index c346bb4e47..efbb3823d4 100755 --- a/tools/scripts/generate_module_docs.py +++ b/tools/scripts/generate_module_docs.py @@ -88,6 +88,7 @@ def get_modules(builder, limit=None): modules = {"modm": DeviceTree("modm", target)} modules["modm"].addSortKey(lambda mo: (mo.name, mo["filename"])) for init in imodules: + if init.name.startswith("__"): continue if init.name.isdigit(): continue; m = modules[init.parent].addChild(init.fullname.split(":")[-1]) modules[init.fullname] = m @@ -102,6 +103,7 @@ def get_modules(builder, limit=None): num_options.append(len(init._options)) for o in init._options: + if o.name.startswith("__"): continue op = m.addChild(o.fullname.split(":")[-1]) op.addSortKey(lambda oo: (oo.name, oo["type"], oo.get("value", ""))) op.setAttribute("type", type(o).__name__) @@ -123,6 +125,7 @@ def get_modules(builder, limit=None): opdep.setValue("{} -> [{}]".format(valin, vconcat)) for c in init._collectors: + if c.name.startswith("__"): continue cp = m.addChild(c.fullname.split(":")[-1]) cp.addSortKey(lambda cc: (cc.name)) cp.setAttribute("type", type(c).__name__) @@ -131,6 +134,7 @@ def get_modules(builder, limit=None): opvals.setValue(c.format_values()) for q in init._queries: + if q.name.startswith("__"): continue qp = m.addChild(q.fullname.split(":")[-1]) qp.addSortKey(lambda qq: (qq.name)) qp.setAttribute("type", type(q).__name__)