Skip to content

⚡ (patch) De-coroutine pure-forwarder functions in the enumerator - #4

Merged
kammce merged 1 commit into
mainfrom
decoroutine-forwarders
Sep 21, 2026
Merged

kammce merged 1 commit into
mainfrom
decoroutine-forwarders

Conversation

@kammce

@kammce kammce commented Sep 3, 2026

Copy link
Copy Markdown
Member

Summary

Several endpoint_io/enumerator functions never actually suspend, or only tail-forward another future with nothing local borrowed across the await — each one was still paying for a full coroutine ramp/resume/destroy triple (and, where the failure path is real, an EH unwind table entry) for no benefit over a plain function:

  • size_eio::driver_read/driver_write — pure synchronous bookkeeping (a fixed 0, or a length computation), never suspends at all. Converted to plain functions returning an already-complete future.
  • enumerator_eio::driver_read — a pure tail call to m_ctrl_ep->read() with a matching return type and nothing borrowed across the await. Converted to return m_ctrl_ep->read(...);.
  • send_error_to_host — was a coroutine only because m_retry_counter += 1 ran after the await. Reordered ahead of it (verified against every use of m_retry_counter in run()'s loop: a throwing stall() unwinds run() entirely either way, so the increment's exact position relative to a throwing stall is unobservable), then converted to a tail call.

enumerator_eio::driver_write is intentionally left as a real coroutine — its return value (p_buffer.length()) does not match what write() itself returns (see the existing TODO(#99) about actually limiting/reporting bytes written), so it isn't a pure forwarder.

Also left a TODO(#3) on write_string_view, pointing at #3: its scratch locals are borrowed across its own await, which is what blocks it from collapsing the same way — filed as a separate, smaller follow-up rather than folded in here.

Measured end-to-end on the usb demo (stm32f103zg, clang 20, MinSizeRel): 64,757 → 63,309 bytes of flash (−1,448 B).

Test plan

  • test_enumerator passes
  • Full downstream rebuild (libhal-arm-mcu, demos) succeeds
  • usb demo binary flash size measured before/after on identical toolchain/profile

Several endpoint_io/enumerator functions never actually suspend, or only
tail-forward another future with nothing local borrowed across the await -
each one was still paying for a full coroutine ramp/resume/destroy triple
(and, where the failure path is real, an EH unwind table entry) for no
benefit over a plain function:

- size_eio::driver_read/driver_write - pure synchronous bookkeeping (a
  fixed 0, or a length computation), never suspends at all. Converted to
  plain functions returning an already-complete future.
- enumerator_eio::driver_read - a pure tail call to m_ctrl_ep->read() with
  a matching return type and nothing borrowed across the await. Converted
  to `return m_ctrl_ep->read(...);`.
- send_error_to_host - was a coroutine only because m_retry_counter += 1
  ran after the await. Reordered ahead of it (verified against every use
  of m_retry_counter in run()'s loop: a throwing stall unwinds run()
  entirely either way, so the increment's exact position relative to a
  *throwing* stall is unobservable), then converted to a tail call.

enumerator_eio::driver_write is intentionally left as a real coroutine -
its return value (p_buffer.length()) does not match what write() itself
returns (see the existing TODO(#99) about actually limiting/reporting
bytes written), so it isn't a pure forwarder.

Also left a TODO(#3) on write_string_view, pointing at a filed issue: its
scratch locals are borrowed across its own await, which is what blocks it
from collapsing the same way - filed as a separate, smaller follow-up
rather than folded in here.

Measured end-to-end on the usb demo (stm32f103zg, clang 20, MinSizeRel):
64,757 -> 63,309 bytes of flash (-1,448 B). test_enumerator passes.
@kammce
kammce force-pushed the decoroutine-forwarders branch from dffc24b to b2665c7 Compare September 4, 2026 00:18
@kammce
kammce merged commit 6cef915 into main Sep 21, 2026
0 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant