Separate Application::feed_wdt() into two entry points so the hot path
callers stop paying for the time==0 check they never trigger:
- feed_wdt_with_time(time): inline, hot path. Rate-limit check in 3
Xtensa instructions (load + sub + branch). [[unlikely]] tells the
compiler the slow branch is rare so the common path stays
fall-through.
- feed_wdt(): cold, out of line. Fetches millis() and forwards through
the same rate limit. Used by setup loops, upload helpers, yield(),
and any other non-hot caller.
feed_wdt_slow_() is now pure worker code — 11 bytes. It just calls
arch_feed_wdt(), updates last_wdt_feed_, and runs the status LED
re-dispatch. Both entries have already confirmed the rate limit was
exceeded before calling.
Hot call sites updated:
- Application::loop() per-component feed
- Scheduler::execute_item_() after each scheduled item runs
- Application::teardown_components() inner loop (already has 'now')
Cleaner than the early-return form — the action (calling feed_wdt_slow_)
reads as the body of the conditional instead of falling through past a
guard clause. Logically identical and compiles to the same code.
The main loop used to feed the watchdog unconditionally right after
Scheduler::call() returned, regardless of whether the scheduler had any
actual work to do. On an idle device this meant every outer loop
iteration paid the inline rate-limit check (load + sub + branch) for no
benefit.
Move the feed into Scheduler::execute_item_() so it fires only after a
scheduled callback actually runs, and covers both the main heap path
and the defer queue path (both go through execute_item_). This also
bounds the max feed gap during a burst of back-to-back scheduled items
by max(item_runtime) instead of sum(item_runtime).
The top-of-loop feed in Application::before_loop_tasks_() is now
unnecessary — when Scheduler::call does no work, the only elapsed time
is the sleep wake plus a few instructions, and when it does have work,
it fed the wdt as it went.
Split Application::feed_wdt() into an ALWAYS_INLINE wrapper that checks
the 3ms rate limit against last_wdt_feed_ and a feed_wdt_slow_() callee
that performs the actual arch_feed_wdt() + status LED re-dispatch.
Callers on the hot path (loop_task before/after each component) that
already have a millis() timestamp in hand now pay only a load + sub +
branch on the no-op path instead of a full call8 / entry / retw.
Moves the rate-limit state from a function-local static to a class
member (last_wdt_feed_) so the inline can access it.