Repository navigation
⚡ (patch) De-coroutine pure-forwarder functions in the enumerator - #4
Merged
Merged
Conversation
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
force-pushed
the
decoroutine-forwarders
branch
from
September 4, 2026 00:18
dffc24b to
b2665c7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 fixed0, 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 tom_ctrl_ep->read()with a matching return type and nothing borrowed across the await. Converted toreturn m_ctrl_ep->read(...);.send_error_to_host— was a coroutine only becausem_retry_counter += 1ran after the await. Reordered ahead of it (verified against every use ofm_retry_counterinrun()'s loop: a throwingstall()unwindsrun()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_writeis intentionally left as a real coroutine — its return value (p_buffer.length()) does not match whatwrite()itself returns (see the existingTODO(#99)about actually limiting/reporting bytes written), so it isn't a pure forwarder.Also left a
TODO(#3)onwrite_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
usbdemo (stm32f103zg, clang 20, MinSizeRel): 64,757 → 63,309 bytes of flash (−1,448 B).Test plan
test_enumeratorpasseslibhal-arm-mcu, demos) succeedsusbdemo binary flash size measured before/after on identical toolchain/profile