From cdf1adaa39ce07f64cdb6c75ce7ec8292c84df4e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 1 Apr 2026 22:20:56 -1000 Subject: [PATCH] [api] Eliminate nullptr branch from write_protobuf_messages loop Pre-compute the first message's header length before the loop to initialize write_start/write_end. The first loop iteration naturally skips memmove since src == write_end. Extract varint_encoded_length_16/8 and plaintext_header_length as reusable inline helpers from write_plaintext_header. --- .../api/api_frame_helper_plaintext.cpp | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index ff682b0185..4d8169e86d 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -227,14 +227,26 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { buffer->type = this->rx_header_parsed_type_; return APIError::OK; } +// Compute varint encoded length for a 16-bit value (1, 2, or 3 bytes). +ESPHOME_ALWAYS_INLINE static inline uint8_t varint_encoded_length_16(uint16_t value) { + return value < ProtoSize::VARINT_THRESHOLD_1_BYTE ? 1 : (value < ProtoSize::VARINT_THRESHOLD_2_BYTE ? 2 : 3); +} + +// Compute varint encoded length for an 8-bit value (1 or 2 bytes). +ESPHOME_ALWAYS_INLINE static inline uint8_t varint_encoded_length_8(uint8_t value) { + return value < ProtoSize::VARINT_THRESHOLD_1_BYTE ? 1 : 2; +} + +// Compute plaintext header length: indicator (1) + size varint + type varint. +ESPHOME_ALWAYS_INLINE static inline uint8_t plaintext_header_length(const MessageInfo &msg) { + return 1 + varint_encoded_length_16(msg.payload_size) + varint_encoded_length_8(msg.message_type); +} + // Write plaintext header into pre-allocated padding before payload. // Returns the total header length (indicator + varints). ESPHOME_ALWAYS_INLINE static inline uint8_t write_plaintext_header(uint8_t *buf_start, const MessageInfo &msg) { - // Calculate varint sizes for header layout using inline ternary to avoid varint_slow call overhead - uint8_t size_varint_len = msg.payload_size < ProtoSize::VARINT_THRESHOLD_1_BYTE - ? 1 - : (msg.payload_size < ProtoSize::VARINT_THRESHOLD_2_BYTE ? 2 : 3); - uint8_t type_varint_len = msg.message_type < ProtoSize::VARINT_THRESHOLD_1_BYTE ? 1 : 2; + uint8_t size_varint_len = varint_encoded_length_16(msg.payload_size); + uint8_t type_varint_len = varint_encoded_length_8(msg.message_type); uint8_t total_header_len = 1 + size_varint_len + type_varint_len; // Calculate where to start writing the header @@ -294,19 +306,19 @@ APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffe assert(!messages.empty()); #endif uint8_t *buffer_data = buffer.get_buffer()->data(); - uint8_t *write_start = nullptr; - uint8_t *write_end = nullptr; + + // Compute first message's write position from header length (cheap arithmetic, no buffer writes). + // This lets the loop body skip a nullptr check — first iteration has src == write_end naturally. + uint8_t *write_start = buffer_data + messages[0].offset + HEADER_PADDING - plaintext_header_length(messages[0]); + uint8_t *write_end = write_start; // Write headers and compact messages to close 0-3 byte varint padding gaps. + // First iteration: src == write_end so memmove is skipped. for (const auto &msg : messages) { uint8_t header_len = write_plaintext_header(buffer_data + msg.offset, msg); uint8_t *src = buffer_data + msg.offset + HEADER_PADDING - header_len; uint16_t msg_len = header_len + msg.payload_size; - if (write_end == nullptr) { - // First message: record start, no compaction needed - write_start = src; - write_end = src; - } else if (src != write_end) { + if (src != write_end) { memmove(write_end, src, msg_len); } write_end += msg_len;