From 4a3a7877569f4d7780df4f8484c74b475da3ce2c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 7 Sep 2026 13:39:44 +0200 Subject: [PATCH] [api] Return the varint parse result as a struct again The out parameter form regressed the host, where the 16 byte result already travels in registers, by about 20 percent on the direct varint parse benchmarks. The void decode_field and low word bool changes stay. --- esphome/components/api/proto.cpp | 26 +++++++++++--------------- esphome/components/api/proto.h | 15 +++++---------- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/esphome/components/api/proto.cpp b/esphome/components/api/proto.cpp index cb3813ef87..7a638680be 100644 --- a/esphome/components/api/proto.cpp +++ b/esphome/components/api/proto.cpp @@ -20,7 +20,7 @@ void ProtoWriteBuffer::encode_varint_raw_slow_(uint32_t value) { *this->pos_++ = static_cast(value); } -uint32_t ProtoVarInt::parse_slow(const uint8_t *buffer, uint32_t len, proto_varint_value_t &value) { +ProtoVarIntResult ProtoVarInt::parse_slow(const uint8_t *buffer, uint32_t len) { // Multi-byte varint: first byte already checked to have high bit set uint32_t result32 = buffer[0] & 0x7F; #ifdef USE_API_VARINT64 @@ -32,30 +32,28 @@ uint32_t ProtoVarInt::parse_slow(const uint8_t *buffer, uint32_t len, proto_vari uint8_t val = buffer[i]; result32 |= uint32_t(val & 0x7F) << (i * 7); if ((val & 0x80) == 0) { - value = result32; - return i + 1; + return {result32, i + 1}; } } #ifdef USE_API_VARINT64 - return parse_wide(buffer, len, result32, value); + return parse_wide(buffer, len, result32); #else - return PROTO_VARINT_PARSE_FAILED; + return {0, PROTO_VARINT_PARSE_FAILED}; #endif } #ifdef USE_API_VARINT64 -uint32_t ProtoVarInt::parse_wide(const uint8_t *buffer, uint32_t len, uint32_t result32, uint64_t &value) { +ProtoVarIntResult ProtoVarInt::parse_wide(const uint8_t *buffer, uint32_t len, uint32_t result32) { uint64_t result64 = result32; uint32_t limit = std::min(len, uint32_t(10)); for (uint32_t i = 4; i < limit; i++) { uint8_t val = buffer[i]; result64 |= uint64_t(val & 0x7F) << (i * 7); if ((val & 0x80) == 0) { - value = result64; - return i + 1; + return {result64, i + 1}; } } - return PROTO_VARINT_PARSE_FAILED; + return {0, PROTO_VARINT_PARSE_FAILED}; } #endif @@ -224,13 +222,11 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) { value = *ptr++; return true; } - // Separate out variable so the fast path above keeps `value` in a register - proto_varint_value_t wide; - uint32_t consumed = ProtoVarInt::parse_slow(ptr, end - ptr, wide); - if (consumed == PROTO_VARINT_PARSE_FAILED) + auto res = ProtoVarInt::parse_non_empty(ptr, end - ptr); + if (!res.has_value()) return false; - value = wide; - ptr += consumed; + value = res.value; + ptr += res.consumed; return true; }; diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 1d88088536..e9f855785e 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -145,9 +145,7 @@ class ProtoVarInt { // (booleans, small enums, field tags, small message sizes/types). if ((buffer[0] & 0x80) == 0) [[likely]] return {buffer[0], 1}; - ProtoVarIntResult res; - res.consumed = parse_slow(buffer, len, res.value); - return res; + return parse_slow(buffer, len); } /// Parse a varint from buffer (safe for empty buffers). @@ -158,16 +156,13 @@ class ProtoVarInt { return parse_non_empty(buffer, len); } - /// Multi-byte varint (first byte has the high bit set), outlined to keep the fast path small. - /// Writes the value and returns the bytes consumed, PROTO_VARINT_PARSE_FAILED when truncated. - static uint32_t parse_slow(const uint8_t *buffer, uint32_t len, proto_varint_value_t &value) - __attribute__((noinline)); - protected: + // Slow path for multi-byte varints (>= 128), outlined to keep fast path small + static ProtoVarIntResult parse_slow(const uint8_t *buffer, uint32_t len) __attribute__((noinline)); + #ifdef USE_API_VARINT64 /// Continue parsing varint bytes 4-9 with 64-bit arithmetic. - static uint32_t parse_wide(const uint8_t *buffer, uint32_t len, uint32_t result32, uint64_t &value) - __attribute__((noinline)); + static ProtoVarIntResult parse_wide(const uint8_t *buffer, uint32_t len, uint32_t result32) __attribute__((noinline)); #endif };