mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
Scan large tables instead of storing two-byte offsets
This commit is contained in:
+20
-16
@@ -6,7 +6,6 @@
|
||||
#include <cstdint>
|
||||
#include <new>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "esphome/core/hal.h" // For PROGMEM definition
|
||||
|
||||
@@ -82,8 +81,9 @@ template<FixedString... Strs> struct ProgmemStringTable {
|
||||
static constexpr size_t BLOB_SIZE = (0 + ... + (Strs.size() + 1));
|
||||
/// Length of the longest string, for sizing a RAM copy on ESP8266
|
||||
static constexpr size_t MAX_LENGTH = std::max({Strs.size()...});
|
||||
/// Offsets are one byte while the blob fits in 255 bytes and two bytes beyond that.
|
||||
using offset_t = std::conditional_t<(BLOB_SIZE <= UINT8_MAX), uint8_t, uint16_t>;
|
||||
/// One-byte offsets address up to 255 bytes; a larger blob has no offset table and is scanned instead,
|
||||
/// which costs less flash than wider offsets for the rarely read tables that get this big.
|
||||
static constexpr bool INDEXED = BLOB_SIZE <= UINT8_MAX;
|
||||
|
||||
/// Generate packed string blob at compile time
|
||||
static constexpr auto make_blob() {
|
||||
@@ -101,23 +101,24 @@ template<FixedString... Strs> struct ProgmemStringTable {
|
||||
static constexpr auto make_offsets() {
|
||||
static_assert(COUNT > 0, "PROGMEM_STRING_TABLE must contain at least one string");
|
||||
static_assert(COUNT <= 255, "PROGMEM_STRING_TABLE supports at most 255 strings with uint8_t indices");
|
||||
static_assert(BLOB_SIZE <= UINT16_MAX, "PROGMEM_STRING_TABLE blob exceeds 65535 bytes; use fewer/shorter strings");
|
||||
std::array<offset_t, COUNT> result{};
|
||||
size_t pos = 0, idx = 0;
|
||||
((result[idx++] = static_cast<offset_t>(pos), pos += Strs.size() + 1), ...);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Read one offset from the table, which may live in flash
|
||||
static offset_t read_offset(const offset_t *offset) {
|
||||
if constexpr (sizeof(offset_t) == 1) {
|
||||
return progmem_read_byte(offset);
|
||||
if constexpr (INDEXED) {
|
||||
std::array<uint8_t, COUNT> result{};
|
||||
size_t pos = 0, idx = 0;
|
||||
((result[idx++] = static_cast<uint8_t>(pos), pos += Strs.size() + 1), ...);
|
||||
return result;
|
||||
} else {
|
||||
return progmem_read_uint16(offset);
|
||||
return std::array<uint8_t, 0>{};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Skip idx strings in a packed NUL-separated blob that may live in flash.
|
||||
inline const char *progmem_skip_strings(const char *blob, uint8_t idx) {
|
||||
while (idx-- > 0)
|
||||
blob += ESPHOME_strlen_P(blob) + 1;
|
||||
return blob;
|
||||
}
|
||||
|
||||
// Forward declaration for LogString (defined in log.h)
|
||||
struct LogString;
|
||||
|
||||
@@ -138,7 +139,10 @@ struct LogString;
|
||||
idx = fallback; \
|
||||
/* std::launder is used here to prevent the inter-procedural analysis that */ \
|
||||
/* causes the false positive that the string is not null terminated */ \
|
||||
return std::launder(&BLOB[Table::read_offset(&OFFSETS[idx])]); \
|
||||
if constexpr (Table::INDEXED) \
|
||||
return std::launder(&BLOB[::esphome::progmem_read_byte(&OFFSETS[idx])]); \
|
||||
else \
|
||||
return std::launder(::esphome::progmem_skip_strings(BLOB.data(), idx)); \
|
||||
} \
|
||||
static ::ProgmemStr get_progmem_str(uint8_t idx, uint8_t fallback) { \
|
||||
return reinterpret_cast<::ProgmemStr>(get_(idx, fallback)); \
|
||||
|
||||
@@ -9,14 +9,14 @@ namespace esphome::core::testing {
|
||||
|
||||
PROGMEM_STRING_TABLE(SmallStrings, "OFF", "HEAT", "COOL", "UNKNOWN");
|
||||
|
||||
// Eight strings of 40 characters: a 328-byte blob, past what one-byte offsets can address.
|
||||
// Eight strings of 40 characters: a 328-byte blob, past what one-byte offsets can address, so it is scanned.
|
||||
PROGMEM_STRING_TABLE(LargeStrings, "0123456789012345678901234567890123456789",
|
||||
"1123456789012345678901234567890123456789", "2123456789012345678901234567890123456789",
|
||||
"3123456789012345678901234567890123456789", "4123456789012345678901234567890123456789",
|
||||
"5123456789012345678901234567890123456789", "6123456789012345678901234567890123456789",
|
||||
"7123456789012345678901234567890123456789");
|
||||
|
||||
// Five 50-character strings pack into exactly 255 bytes; one more byte needs two-byte offsets.
|
||||
// Five 50-character strings pack into exactly 255 bytes; one more byte switches to scanning.
|
||||
PROGMEM_STRING_TABLE(Exactly255, "01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
@@ -28,29 +28,29 @@ PROGMEM_STRING_TABLE(Exactly256, "0123456789012345678901234567890123456789012345
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789x");
|
||||
|
||||
TEST(ProgmemStringTable, OffsetWidthSwitchesAfter255Bytes) {
|
||||
TEST(ProgmemStringTable, ScansInsteadOfIndexingAfter255Bytes) {
|
||||
static_assert(Exactly255::BLOB_SIZE == 255);
|
||||
static_assert(sizeof(Exactly255::OFFSETS[0]) == 1);
|
||||
static_assert(Exactly255::Table::INDEXED);
|
||||
static_assert(Exactly256::BLOB_SIZE == 256);
|
||||
static_assert(sizeof(Exactly256::OFFSETS[0]) == 2);
|
||||
static_assert(!Exactly256::Table::INDEXED);
|
||||
static_assert(Exactly256::OFFSETS.empty());
|
||||
EXPECT_EQ(Exactly255::OFFSETS[4], 204);
|
||||
EXPECT_EQ(Exactly256::OFFSETS[4], 204);
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(Exactly256::get_progmem_str(4, 0)),
|
||||
"01234567890123456789012345678901234567890123456789x");
|
||||
}
|
||||
|
||||
TEST(ProgmemStringTable, SmallTableKeepsOneByteOffsets) {
|
||||
static_assert(SmallStrings::BLOB_SIZE <= 255);
|
||||
static_assert(sizeof(SmallStrings::OFFSETS[0]) == 1);
|
||||
static_assert(SmallStrings::Table::INDEXED);
|
||||
static_assert(SmallStrings::MAX_LENGTH == 7);
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(SmallStrings::get_log_str(0, SmallStrings::LAST_INDEX)), "OFF");
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(SmallStrings::get_log_str(2, SmallStrings::LAST_INDEX)), "COOL");
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(SmallStrings::get_log_str(200, SmallStrings::LAST_INDEX)), "UNKNOWN");
|
||||
}
|
||||
|
||||
TEST(ProgmemStringTable, LargeTableUsesTwoByteOffsets) {
|
||||
TEST(ProgmemStringTable, LargeTableIsScanned) {
|
||||
static_assert(LargeStrings::BLOB_SIZE > 255);
|
||||
static_assert(sizeof(LargeStrings::OFFSETS[0]) == 2);
|
||||
static_assert(!LargeStrings::Table::INDEXED);
|
||||
static_assert(LargeStrings::MAX_LENGTH == 40);
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(LargeStrings::get_progmem_str(0, LargeStrings::LAST_INDEX)),
|
||||
"0123456789012345678901234567890123456789");
|
||||
@@ -58,7 +58,8 @@ TEST(ProgmemStringTable, LargeTableUsesTwoByteOffsets) {
|
||||
"7123456789012345678901234567890123456789");
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(LargeStrings::get_progmem_str(8, LargeStrings::LAST_INDEX)),
|
||||
"7123456789012345678901234567890123456789");
|
||||
EXPECT_EQ(LargeStrings::OFFSETS[7], 7 * 41);
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(LargeStrings::get_progmem_str(3, LargeStrings::LAST_INDEX)),
|
||||
"3123456789012345678901234567890123456789");
|
||||
}
|
||||
|
||||
} // namespace esphome::core::testing
|
||||
|
||||
Reference in New Issue
Block a user