Last step in the HAL reorg started in #15977 and continued in the
per-platform follow-ups #16111 (esp32), #16112 (esp8266), #16113
(libretiny), #16114 (rp2040), #16115 (host), #16116 (zephyr) — each of
which moved its platform's out-of-line HAL bodies from
components/<platform>/core.cpp into a new components/<platform>/hal.cpp.
This PR moves each platform's HAL header next to its hal.cpp inside
components/<platform>/, rather than under core/hal/. The dispatcher
core/hal.h is updated to include from the new locations and the
now-empty core/hal/ directory is removed.
Unlike the wake split (#15978) where freertos-class platforms (ESP32 +
LibreTiny) share a single wake_freertos.{h,cpp}, HAL primitives are
genuinely per-platform — there is no shared HAL implementation across
platforms — so the natural home is alongside each platform component
rather than in a separate core/ subdirectory.
Each .h file gains an empty 'namespace esphome::<platform> {}' block to
satisfy ci-custom's lint_namespace check. HAL functions live in
'namespace esphome' (root); they are not part of the per-component API,
matching the convention used by the corresponding hal.cpp files.
Comment-only updates to the components/<platform>/hal.cpp files to
reflect the new include path. No public API change, no behavior change.
The classic std::vector swap-with-copy idiom (vector<T>(other).swap(other))
instantiates the iterator-range copy constructor, which pulls in
std::__throw_bad_array_new_length and the related typeinfo + vtable + dtor +
what() method (~118 B of stdlib RTTI). Build into a temp via reserve +
push_back instead, then move-assign:
- reserve uses ::operator new (throws bad_alloc, already linked).
- push_back without growth is the noexcept tail path.
- move-assign just swaps pointers, no allocation.
Same shrink semantics, saves ~128 B flash on ESP32. Also adds a fast-path
early return for the case where capacity already equals size (common after
a quiet period, since vector capacity only grows when crossing the doubling
threshold).
Project's .clang-tidy applies different naming rules to static methods
(ClassMethodCase, no suffix) vs instance methods (PrivateMethodCase /
ProtectedMethodCase, _ suffix required). The helper had a trailing _ but
was static, so clang-tidy flagged it. Drop static -- the helper is already
noinline'd and called only at trim time, so the hidden this arg is free
in any meaningful sense.
Project's .clang-tidy applies different naming rules to static methods
(ClassMethodCase, no suffix) vs instance methods (PrivateMethodCase /
ProtectedMethodCase, _ suffix required). The helper had a trailing _ but
was static, so clang-tidy flagged it. Drop static -- the helper is already
noinline'd and called only at trim time, so the hidden this arg is free
in any meaningful sense.
The three swap-with-copy lines in trim_freelist() inlined the
construct-swap-destruct dance per call site, costing ~440 B flash on ESP32.
Move the swap-shrink into a noinline private static helper so all three
callers share one body. Net flash cost of vector shrinking drops to ~296 B
(from ~444 B) while preserving the same RAM-reclaim behaviour.
shrink_to_fit() is a non-binding hint that the toolchain ignores, so the
swap-with-copy idiom is still required.
The freelist holds the boot-peak count of recycled SchedulerItem*; items_,
to_add_, and defer_queue_ hold the boot-peak vector *capacity* of live
SchedulerItem*. std::vector grows by doubling and retains capacity even when
items drain, so post-boot the vector slack can be larger than the freelist
itself. Swap each with a same-content copy to size them exactly to their
current contents.
Live items are preserved -- the swap-copy idiom builds the new vector from
the existing pointers, then the old (over-capacity) vector is destroyed.
Boot churn (component init, first sensor reads, retries) inflates the freelist
beyond the steady-state high-water mark. Without a one-shot trim that peak
would be retained forever. Adds Scheduler::trim_freelist() and schedules it
from Application::setup() to fire SCHEDULER_FREELIST_TRIM_DELAY_MS (10s) after
setup completes -- well past the bulk of post-setup async work.
Items currently in items_/to_add_/defer_queue_ are untouched; only the
freelist's recycled items are deleted. Post-trim, the freelist regrows to
the new (post-startup) high-water mark.