mirror of
https://github.com/esphome/esphome.git
synced 2026-09-22 12:38:40 +00:00
[core] Use placement new allocation for t Pvariables
This commit is contained in:
@@ -11,9 +11,11 @@
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <concepts>
|
||||
#include <strings.h>
|
||||
@@ -2220,6 +2222,41 @@ 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.
|
||||
*/
|
||||
template<class T> struct PlacementStorage {
|
||||
/// @brief Raw byte storage, strictly aligned for type T.
|
||||
alignas(T) unsigned char data[sizeof(T)];
|
||||
|
||||
/**
|
||||
* @brief Safely retrieves a pointer to the constructed object.
|
||||
* @return T* Pointer to the underlying object.
|
||||
*/
|
||||
constexpr T *get() { return reinterpret_cast<T *>(data); }
|
||||
|
||||
/**
|
||||
* @brief Safely retrieves a const pointer to the constructed object.
|
||||
* @return const T* Const pointer to the underlying object.
|
||||
*/
|
||||
constexpr const T *get() const { return reinterpret_cast<const T *>(data); }
|
||||
|
||||
/// @brief Member access operator pointing to the underlying object.
|
||||
constexpr T *operator->() { return get(); }
|
||||
/// @brief Const member access operator pointing to the underlying object.
|
||||
constexpr const T *operator->() const { return get(); }
|
||||
|
||||
/// @brief Dereference operator yielding a reference to the underlying object.
|
||||
constexpr T &operator*() { return *get(); }
|
||||
/// @brief Const dereference operator yielding a const reference to the underlying object.
|
||||
constexpr const T &operator*() const { return *get(); }
|
||||
};
|
||||
|
||||
/// @name Internal functions
|
||||
///@{
|
||||
|
||||
|
||||
@@ -579,10 +579,47 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
|
||||
obj = MockObj(id_, "->")
|
||||
if type_ is not None:
|
||||
id_.type = type_
|
||||
decl = VariableDeclarationExpression(id_.type, "*", id_, static=True)
|
||||
CORE.add_global(decl)
|
||||
assignment = AssignmentExpression(None, None, id_, rhs)
|
||||
CORE.add(assignment)
|
||||
|
||||
# Check if the right-hand side expression is an instantiation via the 'new' operator.
|
||||
# This typically happens when `.new(...)` is used to create a new object.
|
||||
# We detect this by checking if the base of the CallExpression starts with "new ".
|
||||
rhs_str = str(rhs)
|
||||
is_new = rhs_str.startswith("new ")
|
||||
|
||||
if is_new:
|
||||
# For 'new' allocations, we avoid dynamic heap allocation to prevent fragmentation.
|
||||
# Instead, we statically allocate raw, aligned storage for the object and use
|
||||
# raw placement new to initialize it in-place during setup().
|
||||
# We must use raw placement new here because C++ cannot deduce types
|
||||
# for brace-enclosed initializer lists passed to variadic templates.
|
||||
|
||||
call_str = rhs_str[4:] # Strip "new " from "new Type<T>(args)"
|
||||
the_type = id_.type if id_.type is not None else call_str.split("(")[0].strip()
|
||||
storage_name = f"{id_.id}_storage_"
|
||||
|
||||
# Declare the static PlacementStorage
|
||||
decl1 = RawStatement(
|
||||
f"static esphome::PlacementStorage<{the_type}> {storage_name};"
|
||||
)
|
||||
CORE.add_global(decl1)
|
||||
|
||||
# Declare a constant pointer referencing the storage so it can be used identically to a standard pointer
|
||||
decl2 = RawStatement(
|
||||
f"static {the_type} *const {id_.id} = {storage_name}.get();"
|
||||
)
|
||||
CORE.add_global(decl2)
|
||||
|
||||
# Construct the object via raw placement new in setup()
|
||||
assignment = RawStatement(f"new({id_.id}) {call_str};")
|
||||
CORE.add(assignment)
|
||||
else:
|
||||
# For standard assignments (e.g. passing an existing pointer like '&my_struct' or 'nullptr'),
|
||||
# we generate a standard static pointer and assign it directly.
|
||||
decl = VariableDeclarationExpression(id_.type, "*", id_, static=True)
|
||||
CORE.add_global(decl)
|
||||
assignment = AssignmentExpression(None, None, id_, rhs)
|
||||
CORE.add(assignment)
|
||||
|
||||
CORE.register_variable(id_, obj)
|
||||
return obj
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ def test_binary_sensor_is_setup(generate_main):
|
||||
)
|
||||
|
||||
# Then
|
||||
assert "new gpio::GPIOBinarySensor();" in main_cpp
|
||||
assert "static gpio::GPIOBinarySensor *const" in main_cpp
|
||||
assert "App.register_binary_sensor" in main_cpp
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ def test_button_is_setup(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/button/test_button.yaml")
|
||||
|
||||
# Then
|
||||
assert "new wake_on_lan::WakeOnLanButton();" in main_cpp
|
||||
assert "static wake_on_lan::WakeOnLanButton *const" in main_cpp
|
||||
assert ") wake_on_lan::WakeOnLanButton();" in main_cpp
|
||||
assert "App.register_button" in main_cpp
|
||||
assert "App.register_component" in main_cpp
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ def generate_main() -> Generator[Callable[[str | Path], str]]:
|
||||
CORE.config_path = Path(path)
|
||||
CORE.config = read_config({})
|
||||
generate_cpp_contents(CORE.config)
|
||||
return CORE.cpp_main_section
|
||||
return CORE.cpp_global_section + CORE.cpp_main_section
|
||||
|
||||
yield generator
|
||||
|
||||
|
||||
@@ -7,7 +7,11 @@ def test_deep_sleep_setup(generate_main):
|
||||
"""
|
||||
main_cpp = generate_main("tests/component_tests/deep_sleep/test_deep_sleep1.yaml")
|
||||
|
||||
assert "deepsleep = new deep_sleep::DeepSleepComponent();" in main_cpp
|
||||
assert (
|
||||
"static deep_sleep::DeepSleepComponent *const deepsleep = deepsleep_storage_.get();"
|
||||
in main_cpp
|
||||
)
|
||||
assert "new(deepsleep) deep_sleep::DeepSleepComponent();" in main_cpp
|
||||
assert "App.register_component_(deepsleep);" in main_cpp
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ def test_gpio_binary_sensor_basic_setup(
|
||||
"""
|
||||
main_cpp = generate_main("tests/component_tests/gpio/test_gpio_binary_sensor.yaml")
|
||||
|
||||
assert "new gpio::GPIOBinarySensor();" in main_cpp
|
||||
assert "static gpio::GPIOBinarySensor *const" in main_cpp
|
||||
assert ") gpio::GPIOBinarySensor();" in main_cpp
|
||||
assert "App.register_binary_sensor" in main_cpp
|
||||
# set_use_interrupt(true) should NOT be generated (uses C++ default)
|
||||
assert "bs_gpio->set_use_interrupt(true);" not in main_cpp
|
||||
|
||||
@@ -242,7 +242,11 @@ 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 (
|
||||
"cat_img = new image::Image(uint8_t_id, 32, 24, image::IMAGE_TYPE_RGB565, image::TRANSPARENCY_OPAQUE);"
|
||||
"static esphome::PlacementStorage<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
|
||||
)
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ def test_logger_pre_setup_before_other_components(generate_main):
|
||||
|
||||
# Find all "new " allocations (component creation)
|
||||
new_allocations = list(re.finditer(r"\bnew [\w:]+", main_cpp))
|
||||
# Find all "new(" allocations (component creation) and combine them
|
||||
new_allocations.extend(re.finditer(r"\bnew\([^)]+\) [\w:]+", main_cpp))
|
||||
# Sort allocations by position in the file
|
||||
new_allocations.sort(key=lambda m: m.start())
|
||||
assert len(new_allocations) > 0, "No component allocations found"
|
||||
|
||||
# Separate logger and non-logger allocations
|
||||
|
||||
@@ -119,7 +119,10 @@ def test_code_generation(
|
||||
|
||||
main_cpp = generate_main(component_fixture_path("mipi_dsi.yaml"))
|
||||
assert (
|
||||
"p4_nano = new mipi_dsi::MIPI_DSI(800, 1280, display::COLOR_BITNESS_565, 16);"
|
||||
"static mipi_dsi::MIPI_DSI *const p4_nano = p4_nano_storage_.get();" in main_cpp
|
||||
)
|
||||
assert (
|
||||
"new(p4_nano) mipi_dsi::MIPI_DSI(800, 1280, display::COLOR_BITNESS_565, 16);"
|
||||
in main_cpp
|
||||
)
|
||||
assert "set_init_sequence({224, 1, 0, 225, 1, 147, 226, 1," in main_cpp
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Tests for status_led."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_status_led_generation(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""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_;"
|
||||
in main_cpp
|
||||
)
|
||||
assert (
|
||||
"static status_led::StatusLED *const status_led_statusled_id = status_led_statusled_id_storage_.get();"
|
||||
in main_cpp
|
||||
)
|
||||
assert "new(status_led_statusled_id) status_led::StatusLED(" in main_cpp
|
||||
@@ -13,7 +13,8 @@ def test_text_is_setup(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
|
||||
|
||||
# Then
|
||||
assert "new template_::TemplateText();" in main_cpp
|
||||
assert "static template_::TemplateText *const" in main_cpp
|
||||
assert ") template_::TemplateText();" in main_cpp
|
||||
assert "App.register_text" in main_cpp
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ def test_text_sensor_is_setup(generate_main):
|
||||
main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml")
|
||||
|
||||
# Then
|
||||
assert "new template_::TemplateTextSensor();" in main_cpp
|
||||
assert "static template_::TemplateTextSensor *const" in main_cpp
|
||||
assert ") template_::TemplateTextSensor();" in main_cpp
|
||||
assert "App.register_text_sensor" in main_cpp
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user