avoid template overhead

This commit is contained in:
J. Nick Koston
2026-03-22 13:54:00 -10:00
parent bccaee8d79
commit 8b89f5e036
7 changed files with 21 additions and 35 deletions
-21
View File
@@ -2222,27 +2222,6 @@ template<std::totally_ordered T, comparable_with<T> U> T clamp_at_most(T value,
return value;
}
/**
* @brief Provides properly aligned, uninitialized static storage for a given type T.
*
* This struct is designed to replace dynamic heap allocations (`new T(...)`) for
* global or static singletons within ESPHome, preventing memory fragmentation.
* The underlying object must be explicitly constructed using placement new
* before access. No destructor is called — this is intentional since ESPHome
* singletons live for the entire device lifetime.
*/
template<class T> struct PlacementStorage {
/// @brief Raw byte storage, strictly aligned for type T.
alignas(T) unsigned char data[sizeof(T)];
/// @brief Retrieves a pointer to the storage as the target type.
/// The caller must ensure the object has been constructed via placement new before dereferencing.
T *get() { return reinterpret_cast<T *>(data); }
/// @brief Retrieves a const pointer to the storage as the target type.
const T *get() const { return reinterpret_cast<const T *>(data); }
};
/// @name Internal functions
///@{
+6 -4
View File
@@ -584,18 +584,20 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
# For 'new' allocations, use placement new into static storage
# to avoid heap fragmentation on embedded devices.
the_type = id_.type
storage_type = MockObj(f"esphome::PlacementStorage<{the_type}>")
storage_id = ID(f"{id_.id}_storage_", type=storage_type)
storage_name = f"{id_.id}_storage_"
# Declare aligned byte array for the object storage
CORE.add_global(
VariableDeclarationExpression(storage_type, "", storage_id, static=True)
RawStatement(
f"alignas({the_type}) static unsigned char {storage_name}[sizeof({the_type})];"
)
)
CORE.add_global(
AssignmentExpression(
f"static {the_type}",
"*const ",
id_,
MockObj(f"{storage_id.id}.get()"),
MockObj(f"reinterpret_cast<{the_type} *>({storage_name})"),
)
)
# Extract args from the CallExpression and rebuild as placement new.
@@ -8,7 +8,7 @@ def test_deep_sleep_setup(generate_main):
main_cpp = generate_main("tests/component_tests/deep_sleep/test_deep_sleep1.yaml")
assert (
"static deep_sleep::DeepSleepComponent *const deepsleep = deepsleep_storage_.get();"
"static deep_sleep::DeepSleepComponent *const deepsleep = reinterpret_cast<deep_sleep::DeepSleepComponent *>(deepsleep_storage_);"
in main_cpp
)
assert "new(deepsleep) deep_sleep::DeepSleepComponent();" in main_cpp
@@ -16,12 +16,12 @@ def test_globals_placement_new_with_template_args(
# Globals uses Pvariable with Type.new(template_args, initial_value)
# which exercises the template_args preservation in placement new.
assert "static globals::GlobalsComponent<int> *const my_global_int" in main_cpp
assert "PlacementStorage<globals::GlobalsComponent<int>>" in main_cpp
assert "sizeof(globals::GlobalsComponent<int>)" in main_cpp
assert "new(my_global_int) globals::GlobalsComponent<int>" in main_cpp
# Verify initial value is passed as constructor arg
assert "42" in main_cpp
# Check other globals are also generated
assert "PlacementStorage<globals::GlobalsComponent<float>>" in main_cpp
assert "PlacementStorage<globals::GlobalsComponent<bool>>" in main_cpp
assert "sizeof(globals::GlobalsComponent<float>)" in main_cpp
assert "sizeof(globals::GlobalsComponent<bool>)" in main_cpp
+6 -2
View File
@@ -242,9 +242,13 @@ def test_image_generation(
main_cpp = generate_main(component_config_path("image_test.yaml"))
assert "uint8_t_id[] PROGMEM = {0x24, 0x21, 0x24, 0x21" in main_cpp
assert (
"static esphome::PlacementStorage<image::Image> cat_img_storage_;" in main_cpp
"alignas(image::Image) static unsigned char cat_img_storage_[sizeof(image::Image)];"
in main_cpp
)
assert (
"static image::Image *const cat_img = reinterpret_cast<image::Image *>(cat_img_storage_);"
in main_cpp
)
assert "static image::Image *const cat_img = cat_img_storage_.get();" in main_cpp
assert (
"new(cat_img) image::Image(uint8_t_id, 32, 24, image::IMAGE_TYPE_RGB565, image::TRANSPARENCY_OPAQUE);"
in main_cpp
@@ -119,11 +119,12 @@ def test_code_generation(
main_cpp = generate_main(component_fixture_path("mipi_dsi.yaml"))
assert (
"static esphome::PlacementStorage<mipi_dsi::MIPI_DSI> p4_nano_storage_;"
"alignas(mipi_dsi::MIPI_DSI) static unsigned char p4_nano_storage_[sizeof(mipi_dsi::MIPI_DSI)];"
in main_cpp
)
assert (
"static mipi_dsi::MIPI_DSI *const p4_nano = p4_nano_storage_.get();" in main_cpp
"static mipi_dsi::MIPI_DSI *const p4_nano = reinterpret_cast<mipi_dsi::MIPI_DSI *>(p4_nano_storage_);"
in main_cpp
)
assert (
"new(p4_nano) mipi_dsi::MIPI_DSI(800, 1280, display::COLOR_BITNESS_565, 16);"
@@ -13,11 +13,11 @@ def test_status_led_generation(
"""Test status_led generation."""
main_cpp = generate_main(component_config_path("status_led_test.yaml"))
assert (
"static esphome::PlacementStorage<status_led::StatusLED> status_led_statusled_id_storage_;"
"alignas(status_led::StatusLED) static unsigned char status_led_statusled_id_storage_[sizeof(status_led::StatusLED)];"
in main_cpp
)
assert (
"static status_led::StatusLED *const status_led_statusled_id = status_led_statusled_id_storage_.get();"
"static status_led::StatusLED *const status_led_statusled_id = reinterpret_cast<status_led::StatusLED *>(status_led_statusled_id_storage_);"
in main_cpp
)
assert "new(status_led_statusled_id) status_led::StatusLED(" in main_cpp