Skip to content

Make new_with_storage a const fn - #136

Open
qwerty19106 wants to merge 1 commit into
jamesmunns:mainfrom
qwerty19106:const-new-with-storage
Open

qwerty19106 wants to merge 1 commit into
jamesmunns:mainfrom
qwerty19106:const-new-with-storage

Conversation

@qwerty19106

Copy link
Copy Markdown

BBQueue::new() covers storage that can initialize itself, which today means
Inline<N>: the capacity lives in the queue's type. A Storage that points at
a buffer declared elsewhere cannot implement ConstInit — there is no single
value that could point at every buffer — so it has no way into a static,
because new_with_storage is not const.

The body only moves the storage in and reads two associated constants, so
const costs nothing and requires no other change.

The test shows what it buys: queues of 64 and 4096 bytes built in statics
share one concrete type and sit in the same array, so a lookup reaches them by
reference instead of through dyn or a function pointer.

Where this comes from

A defmt logger for latency-critical firmware, where a log call may not
disable interrupts or block. It keeps one framed queue per execution context —
thread mode, plus one per interrupt-driven executor — each sized for how much
that context actually logs: 8 KiB for one, 4 KiB for the others.

Different capacities mean different types, so the dispatch table cannot hold
the queues themselves and holds erased function pointers instead. On a
Cortex-M7 at 400 MHz that costs, per log frame:

  • an indirect call, plus the second load from the table it needs;
  • a full prologue/epilogue in the callee, because its body cannot be inlined
    into the logger's entry point — 8 registers pushed and popped;
  • 16 bytes per table entry instead of 4.

Measured with DWT CYCCNT: 50 cycles out of a 193-cycle log call.

There is a way to get this today — give every queue the same capacity, so the
types collapse. It trades RAM for it: 24 KiB instead of 16 KiB in that
firmware, and the gap grows with the number of contexts. With this patch
neither per-queue sizing nor the direct call has to be given up.

Checks

cargo test --features=std, both thumbv6m-none-eabi builds from
build.yml, and miri.sh — the new test uses raw pointers, so it is included
under miri:

test queue::test::storage_without_const_init_reaches_a_static ... ok

Disclosure: this patch was written with AI assistance (Claude), reviewed and
tested by me. The commit carries an Assisted-by: trailer. Happy to drop the
test or trim it to a doc example if you would rather keep the change to the
single word.

`BBQueue::new()` covers storage that can initialize itself, which today
means `Inline<N>`: the capacity lives in the queue's type. A `Storage`
that points at a buffer declared elsewhere cannot implement `ConstInit` —
there is no single value that could point at every buffer — so it has no
way into a `static`, because `new_with_storage` is not `const`.

The body only moves the storage in and reads two associated constants, so
`const` costs nothing and requires no other change.

The test shows what it buys: queues of 64 and 4096 bytes built in
`static`s share one concrete type and sit in the same array, so a lookup
reaches them by reference instead of through `dyn` or a function pointer.

Assisted-by: Claude Opus 5 (Anthropic)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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