From fe012272f88bd24b356a423b6d86212a73e3d788 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 28 Mar 2026 22:06:31 -1000 Subject: [PATCH 1/2] [api] Merge tag + value into single __restrict__ scope in encode_uint32/uint64/bool/fixed32 Each of these methods called encode_field_raw() then a value encoder, causing a store-load pair on pos_ between the tag and value writes. Inline the tag write into the same __restrict__ local scope as the value write so the compiler can emit tag + value with a single pos_ load at start and store at end. Verified on Xtensa: encode_uint32 now does one load + two writes + one store (was load-store-load-store). --- esphome/components/api/proto.h | 68 +++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 9db03d6289..f5c64583c9 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -317,21 +317,64 @@ class ProtoWriteBuffer { void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) { if (value == 0 && !force) return; - this->encode_field_raw(field_id, 0); // type 0: Varint - uint32 - this->encode_varint_raw(value); + // Inline tag + value under a single __restrict__ pos to avoid + // a store-load pair between encode_field_raw and encode_varint_raw. + uint32_t tag = (field_id << 3) | 0; // wire type 0: varint + this->debug_check_bounds_(2); // at least tag + 1 byte value + uint8_t *__restrict__ pos = this->pos_; + if (tag < 128) [[likely]] { + *pos++ = static_cast(tag); + } else { + this->encode_varint_raw_slow_(tag); + pos = this->pos_; + } + if (value < 128) [[likely]] { + *pos++ = static_cast(value); + this->pos_ = pos; + } else { + this->pos_ = pos; + this->encode_varint_raw_slow_(value); + } } void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) { if (value == 0 && !force) return; - this->encode_field_raw(field_id, 0); // type 0: Varint - uint64 - this->encode_varint_raw_64(value); + // Inline tag under same __restrict__ scope as varint64 to avoid + // a store-load pair between encode_field_raw and encode_varint_raw_64. + uint32_t tag = (field_id << 3) | 0; // wire type 0: varint + this->debug_check_bounds_(1); + uint8_t *__restrict__ pos = this->pos_; + if (tag < 128) [[likely]] { + *pos++ = static_cast(tag); + } else { + this->pos_ = pos; + this->encode_varint_raw_slow_(tag); + pos = this->pos_; + } + // Continue with varint64 in same pos scope + while (value > 0x7F) { + this->sync_debug_check_bounds_(pos, 1); + *pos++ = static_cast(value | 0x80); + value >>= 7; + } + this->sync_debug_check_bounds_(pos, 1); + *pos++ = static_cast(value); + this->pos_ = pos; } void encode_bool(uint32_t field_id, bool value, bool force = false) { if (!value && !force) return; - this->encode_field_raw(field_id, 0); // type 0: Varint - bool - this->debug_check_bounds_(1); + // Inline tag + bool byte under single __restrict__ scope + uint32_t tag = (field_id << 3) | 0; // wire type 0: varint + this->debug_check_bounds_(2); uint8_t *__restrict__ pos = this->pos_; + if (tag < 128) [[likely]] { + *pos++ = static_cast(tag); + } else { + this->pos_ = pos; + this->encode_varint_raw_slow_(tag); + pos = this->pos_; + } *pos++ = value ? 0x01 : 0x00; this->pos_ = pos; } @@ -339,11 +382,18 @@ class ProtoWriteBuffer { if (value == 0 && !force) return; - this->encode_field_raw(field_id, 5); // type 5: 32-bit fixed32 - this->debug_check_bounds_(4); + // Inline tag + fixed32 under single __restrict__ scope + uint32_t tag = (field_id << 3) | 5; // wire type 5: 32-bit + this->debug_check_bounds_(5); // tag + 4 bytes uint8_t *__restrict__ pos = this->pos_; + if (tag < 128) [[likely]] { + *pos++ = static_cast(tag); + } else { + this->pos_ = pos; + this->encode_varint_raw_slow_(tag); + pos = this->pos_; + } #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - // Protobuf fixed32 is little-endian, so direct copy works std::memcpy(pos, &value, 4); this->pos_ = pos + 4; #else From 35491f2649d96d5a1ee590c125d34d104daaadca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 28 Mar 2026 22:15:28 -1000 Subject: [PATCH 2/2] Revert "[api] Merge tag + value into single __restrict__ scope in encode_uint32/uint64/bool/fixed32" This reverts commit fe012272f88bd24b356a423b6d86212a73e3d788. --- esphome/components/api/proto.h | 68 +++++----------------------------- 1 file changed, 9 insertions(+), 59 deletions(-) diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index f5c64583c9..9db03d6289 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -317,64 +317,21 @@ class ProtoWriteBuffer { void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) { if (value == 0 && !force) return; - // Inline tag + value under a single __restrict__ pos to avoid - // a store-load pair between encode_field_raw and encode_varint_raw. - uint32_t tag = (field_id << 3) | 0; // wire type 0: varint - this->debug_check_bounds_(2); // at least tag + 1 byte value - uint8_t *__restrict__ pos = this->pos_; - if (tag < 128) [[likely]] { - *pos++ = static_cast(tag); - } else { - this->encode_varint_raw_slow_(tag); - pos = this->pos_; - } - if (value < 128) [[likely]] { - *pos++ = static_cast(value); - this->pos_ = pos; - } else { - this->pos_ = pos; - this->encode_varint_raw_slow_(value); - } + this->encode_field_raw(field_id, 0); // type 0: Varint - uint32 + this->encode_varint_raw(value); } void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) { if (value == 0 && !force) return; - // Inline tag under same __restrict__ scope as varint64 to avoid - // a store-load pair between encode_field_raw and encode_varint_raw_64. - uint32_t tag = (field_id << 3) | 0; // wire type 0: varint - this->debug_check_bounds_(1); - uint8_t *__restrict__ pos = this->pos_; - if (tag < 128) [[likely]] { - *pos++ = static_cast(tag); - } else { - this->pos_ = pos; - this->encode_varint_raw_slow_(tag); - pos = this->pos_; - } - // Continue with varint64 in same pos scope - while (value > 0x7F) { - this->sync_debug_check_bounds_(pos, 1); - *pos++ = static_cast(value | 0x80); - value >>= 7; - } - this->sync_debug_check_bounds_(pos, 1); - *pos++ = static_cast(value); - this->pos_ = pos; + this->encode_field_raw(field_id, 0); // type 0: Varint - uint64 + this->encode_varint_raw_64(value); } void encode_bool(uint32_t field_id, bool value, bool force = false) { if (!value && !force) return; - // Inline tag + bool byte under single __restrict__ scope - uint32_t tag = (field_id << 3) | 0; // wire type 0: varint - this->debug_check_bounds_(2); + this->encode_field_raw(field_id, 0); // type 0: Varint - bool + this->debug_check_bounds_(1); uint8_t *__restrict__ pos = this->pos_; - if (tag < 128) [[likely]] { - *pos++ = static_cast(tag); - } else { - this->pos_ = pos; - this->encode_varint_raw_slow_(tag); - pos = this->pos_; - } *pos++ = value ? 0x01 : 0x00; this->pos_ = pos; } @@ -382,18 +339,11 @@ class ProtoWriteBuffer { if (value == 0 && !force) return; - // Inline tag + fixed32 under single __restrict__ scope - uint32_t tag = (field_id << 3) | 5; // wire type 5: 32-bit - this->debug_check_bounds_(5); // tag + 4 bytes + this->encode_field_raw(field_id, 5); // type 5: 32-bit fixed32 + this->debug_check_bounds_(4); uint8_t *__restrict__ pos = this->pos_; - if (tag < 128) [[likely]] { - *pos++ = static_cast(tag); - } else { - this->pos_ = pos; - this->encode_varint_raw_slow_(tag); - pos = this->pos_; - } #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + // Protobuf fixed32 is little-endian, so direct copy works std::memcpy(pos, &value, 4); this->pos_ = pos + 4; #else