From 0ba752e9470633cb3dbd4fb8703a404b6031d1a0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 18 Feb 2026 11:34:29 -0600 Subject: [PATCH] [core] Use placement new for global Application instance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the global `Application App` with placement-new construction in aligned .bss storage. This eliminates the global constructor and destructor chain that were: - Calling xSemaphoreCreateMutex() at static init time (via Scheduler's Mutex member) before app_main() runs - Redundantly zero-initializing all members that .bss already zeroes - Registering __cxa_atexit for ~Application() destructor chain (~Application, ~vector, ~Mutex) that never runs on embedded The storage is a char[] with a GCC asm label matching the mangled name of esphome::App. Other translation units see a typed extern Application (identical codegen, no indirection), while the defining TU sees a trivially-destructible char array — so the compiler never emits __cxa_atexit or the destructor chain. Construction happens in pre_setup() via placement new, which is always the first method called on App in the generated setup() function. --- esphome/core/application.cpp | 9 ++++++++- esphome/core/config.py | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 449acc64cf..aad71ff4d0 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -669,7 +669,14 @@ void Application::yield_with_select_(uint32_t delay_ms) { #endif } -Application App; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +// App storage — asm label shares the linker symbol with "extern Application App". +// char[] is trivially destructible, so no __cxa_atexit or destructor chain is emitted. +// Constructed via placement new in the generated setup(). +#ifndef __GXX_ABI_VERSION +#error "Application placement new requires Itanium C++ ABI (GCC/Clang)" +#endif +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +alignas(Application) char app_storage[sizeof(Application)] asm("_ZN7esphome3AppE"); #if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) void Application::setup_wake_loop_threadsafe_() { diff --git a/esphome/core/config.py b/esphome/core/config.py index 21ed8ced1a..a2b8746a62 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -512,6 +512,9 @@ async def to_code(config: ConfigType) -> None: cg.add_global(cg.RawExpression("using std::min")) cg.add_global(cg.RawExpression("using std::max")) + # Construct App via placement new — see application.cpp for storage details + cg.add_global(cg.RawExpression("#include ")) + cg.add(cg.RawExpression("new (&App) Application()")) cg.add( cg.App.pre_setup( config[CONF_NAME],