From 8a8540b247bd0de99b4d832dadffc4b60381a6cb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 5 Apr 2026 12:33:32 -1000 Subject: [PATCH] Use if-else chains instead of switch for string lookups Switch statements generate CSWTCH jump tables in RAM on ESP8266. PROGMEM_STRING_TABLE causes LoadStoreError from flash cache conflicts in API subscribe paths. If-else with LOG_STR avoids both: strings stay in flash via PSTR, and comparison branches don't need a data table. Zero RAM overhead confirmed via nm. --- esphome/components/esp8266/crash_handler.cpp | 117 +++++++++---------- 1 file changed, 56 insertions(+), 61 deletions(-) diff --git a/esphome/components/esp8266/crash_handler.cpp b/esphome/components/esp8266/crash_handler.cpp index 7e0ddbd2de..3e62859bcb 100644 --- a/esphome/components/esp8266/crash_handler.cpp +++ b/esphome/components/esp8266/crash_handler.cpp @@ -6,7 +6,6 @@ #include "crash_handler.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/progmem.h" #include @@ -101,71 +100,67 @@ void crash_handler_read_and_clear() { // Xtensa exception cause names (shared with ESP32, same ISA). // Keep in sync with Xtensa ISA reference manual Table 4-64. -// Split into two PROGMEM_STRING_TABLEs to stay under 255-byte blob limit. -// clang-format off -PROGMEM_STRING_TABLE(ExcCauseLow, // Causes 0-9 - "IllegalInstruction", // 0 - "Syscall", // 1 - "InstructionFetchError", // 2 - "LoadStoreError", // 3 - "Level1Interrupt", // 4 - "Alloca", // 5 - "IntegerDivideByZero", // 6 - "PCValue", // 7 - "Privileged", // 8 - "LoadStoreAlignment" // 9 -); -PROGMEM_STRING_TABLE(ExcCauseHigh, // Causes 12-29 (offset by 12) - "InstrPDAddrError", // 12 - "LoadStorePIFDataError", // 13 - "InstrPIFAddrError", // 14 - "LoadStorePIFAddrError", // 15 - "InstTLBMiss", // 16 - "InstTLBMultiHit", // 17 - "InstFetchPrivilege", // 18 - "", // 19 (unused) - "InstrFetchProhibited", // 20 - "", // 21 (unused) - "", // 22 (unused) - "", // 23 (unused) - "LoadStoreTLBMiss", // 24 - "LoadStoreTLBMultihit", // 25 - "LoadStorePrivilege", // 26 - "", // 27 (unused) - "LoadProhibited", // 28 - "StoreProhibited" // 29 -); -// clang-format on - -static constexpr uint32_t EXC_CAUSE_HIGH_BASE = 12; // First cause code in ExcCauseHigh table - +// Uses if-else instead of switch to avoid CSWTCH jump tables (RAM on ESP8266). +// PROGMEM_STRING_TABLE also crashes due to flash cache conflicts in API paths. +// LOG_STR strings are in flash via PSTR; if-else generates comparison branches only. static const LogString *get_exception_cause(uint32_t cause) { - if (cause < ExcCauseLow::COUNT) - return ExcCauseLow::get_log_str(cause, ExcCauseLow::LAST_INDEX); - if (cause >= EXC_CAUSE_HIGH_BASE && cause < EXC_CAUSE_HIGH_BASE + ExcCauseHigh::COUNT) { - const LogString *str = ExcCauseHigh::get_log_str(cause - EXC_CAUSE_HIGH_BASE, ExcCauseHigh::LAST_INDEX); - // Empty strings are gap entries — return nullptr so caller knows cause is unknown - if (LOG_STR_ARG(str)[0] == '\0') - return nullptr; - return str; - } + if (cause == 0) + return LOG_STR("IllegalInstruction"); + if (cause == 1) + return LOG_STR("Syscall"); + if (cause == 2) + return LOG_STR("InstructionFetchError"); + if (cause == 3) + return LOG_STR("LoadStoreError"); + if (cause == 4) + return LOG_STR("Level1Interrupt"); + if (cause == 5) + return LOG_STR("Alloca"); + if (cause == 6) + return LOG_STR("IntegerDivideByZero"); + if (cause == 7) + return LOG_STR("PCValue"); + if (cause == 8) + return LOG_STR("Privileged"); + if (cause == 9) + return LOG_STR("LoadStoreAlignment"); + if (cause == 12) + return LOG_STR("InstrPDAddrError"); + if (cause == 13) + return LOG_STR("LoadStorePIFDataError"); + if (cause == 14) + return LOG_STR("InstrPIFAddrError"); + if (cause == 15) + return LOG_STR("LoadStorePIFAddrError"); + if (cause == 16) + return LOG_STR("InstTLBMiss"); + if (cause == 17) + return LOG_STR("InstTLBMultiHit"); + if (cause == 18) + return LOG_STR("InstFetchPrivilege"); + if (cause == 20) + return LOG_STR("InstrFetchProhibited"); + if (cause == 24) + return LOG_STR("LoadStoreTLBMiss"); + if (cause == 25) + return LOG_STR("LoadStoreTLBMultihit"); + if (cause == 26) + return LOG_STR("LoadStorePrivilege"); + if (cause == 28) + return LOG_STR("LoadProhibited"); + if (cause == 29) + return LOG_STR("StoreProhibited"); return nullptr; } -// clang-format off -PROGMEM_STRING_TABLE(ResetReasonStrings, - "Unknown", // 0 = fallback - "Hardware Watchdog", // 1 = REASON_WDT_RST - "Exception", // 2 = REASON_EXCEPTION_RST - "Software Watchdog" // 3 = REASON_SOFT_WDT_RST -); -// clang-format on -static_assert(REASON_WDT_RST == 1, "Reset reason enum values must match table indices"); -static_assert(REASON_EXCEPTION_RST == 2, "Reset reason enum values must match table indices"); -static_assert(REASON_SOFT_WDT_RST == 3, "Reset reason enum values must match table indices"); - static const LogString *get_reset_reason(uint32_t reason) { - return ResetReasonStrings::get_log_str(static_cast(reason), 0); + if (reason == REASON_WDT_RST) + return LOG_STR("Hardware Watchdog"); + if (reason == REASON_EXCEPTION_RST) + return LOG_STR("Exception"); + if (reason == REASON_SOFT_WDT_RST) + return LOG_STR("Software Watchdog"); + return LOG_STR("Unknown"); } // Read backtrace from RTC user memory into caller-provided buffer.