From 9fd745cb09d7a0b3445ad590eab03d2f84018c88 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Mar 2026 11:16:33 -1000 Subject: [PATCH] [api] Add out-of-line write_raw_ wrappers calling write_raw_inline_ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hot paths use write_raw_inline_ (ALWAYS_INLINE fast path). Cold paths (handshake, error handling) use write_raw_ which is an out-of-line wrapper that calls write_raw_inline_ — same logic, no code duplication, but the compiler won't expand the fast path at each cold call site. --- esphome/components/api/api_frame_helper.cpp | 8 ++++++++ esphome/components/api/api_frame_helper.h | 20 ++++++++++++------- .../components/api/api_frame_helper_noise.cpp | 9 ++++----- .../api/api_frame_helper_plaintext.cpp | 8 ++++---- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index c18bba0bee8..b784e46c4ca 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -112,6 +112,14 @@ APIError APIFrameHelper::drain_overflow_and_handle_errors_() { return APIError::OK; } +// Out-of-line wrappers — same logic as write_raw_inline_ but not force-inlined. +// Used by cold callers (handshake, error handling) to avoid code bloat. +APIError APIFrameHelper::write_raw_(const void *data, uint16_t len) { return this->write_raw_inline_(data, len); } + +APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) { + return this->write_raw_inline_(iov, iovcnt, total_write_len); +} + // Single-buffer slow path: wraps data in iovec and delegates to the iovec slow path. APIError APIFrameHelper::write_raw_slow_(const void *data, uint16_t len, ssize_t sent) { struct iovec iov = {const_cast(data), len}; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 172cb3e5fd6..dba3344c6bc 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -190,9 +190,14 @@ class APIFrameHelper { // Returns OK for transient errors (WOULD_BLOCK), SOCKET_WRITE_FAILED for hard errors. APIError drain_overflow_and_handle_errors_(); - // Write a single contiguous buffer to the socket (inlined fast path) - inline APIError ESPHOME_ALWAYS_INLINE write_raw_(const void *data, uint16_t len) { - // Fast path: no overflow backlog and full write succeeds + // Out-of-line write methods — used by cold paths (handshake, error handling) + APIError write_raw_(const void *data, uint16_t len); + APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len); + + // Inlined write methods — used by hot paths (write_protobuf_packet, write_protobuf_messages) + // These inline the fast path (overflow empty + full write) and tail-call the out-of-line + // slow path only on failure/partial write. + inline APIError ESPHOME_ALWAYS_INLINE write_raw_inline_(const void *data, uint16_t len) { ssize_t sent = -1; if (this->overflow_buf_.empty()) [[likely]] { sent = this->socket_->write(data, len); @@ -201,9 +206,8 @@ class APIFrameHelper { } return this->write_raw_slow_(data, len, sent); } - // Write multiple iovec buffers to the socket (inlined fast path) - inline APIError ESPHOME_ALWAYS_INLINE write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) { - // Fast path: no overflow backlog and full writev succeeds + inline APIError ESPHOME_ALWAYS_INLINE write_raw_inline_(const struct iovec *iov, int iovcnt, + uint16_t total_write_len) { ssize_t sent = -1; if (this->overflow_buf_.empty()) [[likely]] { sent = this->socket_->writev(iov, iovcnt); @@ -212,7 +216,9 @@ class APIFrameHelper { } return this->write_raw_slow_(iov, iovcnt, total_write_len, sent); } - // Slow paths (out-of-line): handle partial writes, errors, overflow buffering + + private: + // Slow path: handle partial writes, errors, overflow buffering APIError write_raw_slow_(const void *data, uint16_t len, ssize_t sent); APIError write_raw_slow_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, ssize_t sent); diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index d7348ae669c..3467566efe5 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -504,7 +504,7 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuff aerr = this->encrypt_noise_message_(buf_start, msg, iov); if (aerr != APIError::OK) return aerr; - return this->write_raw_(iov.iov_base, static_cast(iov.iov_len)); + return this->write_raw_inline_(iov.iov_base, static_cast(iov.iov_len)); } APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, std::span messages) { @@ -530,7 +530,7 @@ APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, s total_write_len += iov.iov_len; } - return this->write_raw_(iovs.data(), iovs.size(), total_write_len); + return this->write_raw_inline_(iovs.data(), iovs.size(), total_write_len); } APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) { @@ -540,16 +540,15 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) { header[2] = (uint8_t) len; if (len == 0) { - return this->write_raw_slow_(header, 3, -1); + return this->write_raw_(header, 3); } - // Handshake path — use iovec slow path directly to avoid inlining write_raw_ fast path struct iovec iov[2]; iov[0].iov_base = header; iov[0].iov_len = 3; iov[1].iov_base = const_cast(data); iov[1].iov_len = len; - return this->write_raw_slow_(iov, 2, 3 + len, -1); + return this->write_raw_(iov, 2, 3 + len); } /** Initiate the data structures for the handshake. diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index 9a134039e73..b208dcd4557 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -219,11 +219,11 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { "Bad indicator byte"; char msg[INDICATOR_MSG_SIZE]; memcpy_P(msg, MSG_PROGMEM, INDICATOR_MSG_SIZE); - this->write_raw_slow_(msg, INDICATOR_MSG_SIZE, -1); + this->write_raw_(msg, INDICATOR_MSG_SIZE); #else static const char MSG[] = "\x00" "Bad indicator byte"; - this->write_raw_slow_(MSG, INDICATOR_MSG_SIZE, -1); + this->write_raw_(MSG, INDICATOR_MSG_SIZE); #endif } return aerr; @@ -292,7 +292,7 @@ APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWrite uint8_t *msg_start = write_plaintext_header(buffer_data, msg, frame_header_padding_); uint8_t msg_header_len = static_cast(buffer_data + frame_header_padding_ - msg_start); uint16_t msg_len = static_cast(msg_header_len + msg.payload_size); - return this->write_raw_(msg_start, msg_len); + return this->write_raw_inline_(msg_start, msg_len); } APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, @@ -318,7 +318,7 @@ APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffe total_write_len += msg_len; } - return this->write_raw_(iovs.data(), iovs.size(), total_write_len); + return this->write_raw_inline_(iovs.data(), iovs.size(), total_write_len); } } // namespace esphome::api