Skip to content

[fiber] Use synchronization primitives without scheduler - #1388

Draft
salkinium wants to merge 2 commits into
modm-io:developfrom
salkinium:feature/fiber_no_scheduler
Draft

salkinium wants to merge 2 commits into
modm-io:developfrom
salkinium:feature/fiber_no_scheduler

Conversation

@salkinium

Copy link
Copy Markdown
Member

Follow-up to the missing volatile fixes in #1386. Those were only a symptom: without modm:processing:fiber the yield() function is an empty inline stub, so the compiler assumes that nothing changes while polling and happily hoists the load of the flag out of the loop:

bool done; // set in an interrupt
while (not done) modm::this_fiber::yield();
// GCC -O2: the whole loop is gone, whoops

So instead of hunting down every non-volatile flag in every driver, the stub now tells the compiler that memory changed, just like a real context switch does.

  • yield() stub contains an empty asm with a memory clobber. Emits no code, but the flag gets reloaded on every iteration.
  • mutex, shared_mutex, semaphore, latch, stop_token and condition_variable are now also available without the scheduler. They only need yield() and get_id(), so they work just fine for synchronizing the main context with interrupts.
  • barrier is not available without the scheduler, since it needs multiple fibers to make any progress.
  • get_id() stub returns the interrupt number inside interrupts on Cortex-M, same as the scheduler. It returned 0 everywhere before, so the recursive mutex could not tell the main context apart from an interrupt.
  • modm:architecture:fiber depends on modm:architecture:atomic for the recursive mutex.
  • Docs: without the scheduler a blocking lock(), acquire() or wait() waits in place and only returns if an interrupt releases it.
  • New modm-test:test:architecture:fiber unit test that uses the primitives from outside of a fiber, so it runs with and without the scheduler.
  • New hosted-no-fiber unit test configuration and CI step for the latter.

Tests:

  • Hosted unit tests pass on macOS arm64 with and without the scheduler.
  • Cortex-M without the scheduler compiles and links on NUCLEO-F103RB.
  • AVR and STM32 unit tests compile with unchanged sizes for the configurations I tried.
  • run-hosted-linux-no-fiber in CI, only ran the macOS equivalent locally.
  • Hardware test with an actual interrupt releasing a semaphore.

This is only the first half: the plan is to then replace the hand-rolled locks in modm (SpiLock, ResumableMutex, the AMNB node, the STM32 SPI DMA flags) with these primitives in a separate PR.

Without the fiber scheduler, yield() is an empty inline function, so the
compiler assumes that no memory changes while polling. It then hoists the
load of non-volatile variables modified in interrupts out of the polling
loop, which turns the loop into an endless loop or removes it entirely.

The memory clobber emits no code, but forces all memory to be reloaded
after yield(), just like for a real context switch.
The mutexes, semaphores, latch, stop token and condition variable only
depend on yield() and get_id(), so they also work with the empty yield
stub to synchronize the main context with interrupts. The barrier is
excluded, since it requires multiple fibers to make progress.

The get_id() stub now returns the interrupt number inside interrupts on
Cortex-M just like the scheduler does, otherwise the recursive mutex
cannot tell the main context apart from an interrupt.

The new hosted-no-fiber unit test configuration tests the fiber API
without the scheduler.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant