mirror of
https://github.com/esphome/esphome.git
synced 2026-09-17 18:18:43 +00:00
Merge branch 'api-sint32-short-varint' into peel_plus_short
This commit is contained in:
@@ -2252,7 +2252,7 @@ void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.write_raw_byte(8);
|
||||
buffer.encode_varint_raw_64(this->address);
|
||||
buffer.write_raw_byte(16);
|
||||
buffer.encode_varint_raw(encode_zigzag32(this->rssi));
|
||||
buffer.encode_varint_raw_short(encode_zigzag32(this->rssi));
|
||||
buffer.encode_uint32(3, this->address_type);
|
||||
buffer.write_raw_byte(34);
|
||||
buffer.encode_varint_raw(this->data_len);
|
||||
|
||||
@@ -11,13 +11,17 @@ static const char *const TAG = "api.proto";
|
||||
uint32_t ProtoSize::varint_slow(uint32_t value) { return varint_wide(value); }
|
||||
|
||||
void ProtoWriteBuffer::encode_varint_raw_slow_(uint32_t value) {
|
||||
// Use __restrict__ so the compiler knows pos doesn't alias this->
|
||||
// and can keep it in a register across the loop.
|
||||
uint8_t *__restrict__ pos = this->pos_;
|
||||
do {
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = static_cast<uint8_t>(value | 0x80);
|
||||
this->sync_debug_check_bounds_(pos, 1);
|
||||
*pos++ = static_cast<uint8_t>(value | 0x80);
|
||||
value >>= 7;
|
||||
} while (value > 0x7F);
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = static_cast<uint8_t>(value);
|
||||
this->sync_debug_check_bounds_(pos, 1);
|
||||
*pos++ = static_cast<uint8_t>(value);
|
||||
this->pos_ = pos;
|
||||
}
|
||||
|
||||
ProtoVarIntResult ProtoVarInt::parse_slow(const uint8_t *buffer, uint32_t len) {
|
||||
|
||||
@@ -202,19 +202,45 @@ class ProtoWriteBuffer {
|
||||
inline void ESPHOME_ALWAYS_INLINE encode_varint_raw(uint32_t value) {
|
||||
if (value < 128) [[likely]] {
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = static_cast<uint8_t>(value);
|
||||
uint8_t *__restrict__ pos = this->pos_;
|
||||
*pos++ = static_cast<uint8_t>(value);
|
||||
this->pos_ = pos;
|
||||
return;
|
||||
}
|
||||
this->encode_varint_raw_slow_(value);
|
||||
}
|
||||
/// Encode a varint that is expected to be 1-2 bytes (e.g. zigzag RSSI, small lengths).
|
||||
/// Inlines both the 1-byte and 2-byte paths; falls back to slow path for 3+ bytes.
|
||||
inline void ESPHOME_ALWAYS_INLINE encode_varint_raw_short(uint32_t value) {
|
||||
if (value < 128) [[likely]] {
|
||||
this->debug_check_bounds_(1);
|
||||
uint8_t *__restrict__ pos = this->pos_;
|
||||
*pos++ = static_cast<uint8_t>(value);
|
||||
this->pos_ = pos;
|
||||
return;
|
||||
}
|
||||
if (value < 16384) [[likely]] {
|
||||
this->debug_check_bounds_(2);
|
||||
uint8_t *__restrict__ pos = this->pos_;
|
||||
*pos++ = static_cast<uint8_t>(value | 0x80);
|
||||
*pos++ = static_cast<uint8_t>(value >> 7);
|
||||
this->pos_ = pos;
|
||||
return;
|
||||
}
|
||||
this->encode_varint_raw_slow_(value);
|
||||
}
|
||||
void encode_varint_raw_64(uint64_t value) {
|
||||
// Use __restrict__ so the compiler knows pos doesn't alias this->
|
||||
// and can keep it in a register across the loop.
|
||||
uint8_t *__restrict__ pos = this->pos_;
|
||||
while (value > 0x7F) {
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = static_cast<uint8_t>(value | 0x80);
|
||||
this->sync_debug_check_bounds_(pos, 1);
|
||||
*pos++ = static_cast<uint8_t>(value | 0x80);
|
||||
value >>= 7;
|
||||
}
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = static_cast<uint8_t>(value);
|
||||
this->sync_debug_check_bounds_(pos, 1);
|
||||
*pos++ = static_cast<uint8_t>(value);
|
||||
this->pos_ = pos;
|
||||
}
|
||||
/**
|
||||
* Encode a field key (tag/wire type combination).
|
||||
@@ -232,40 +258,52 @@ class ProtoWriteBuffer {
|
||||
/// Write a single precomputed tag byte. Tag must be < 128.
|
||||
inline void write_raw_byte(uint8_t b) ESPHOME_ALWAYS_INLINE {
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = b;
|
||||
uint8_t *__restrict__ pos = this->pos_;
|
||||
*pos++ = b;
|
||||
this->pos_ = pos;
|
||||
}
|
||||
/// Write raw bytes to the buffer (no tag, no length prefix).
|
||||
inline void encode_raw(const void *data, size_t len) ESPHOME_ALWAYS_INLINE {
|
||||
this->debug_check_bounds_(len);
|
||||
std::memcpy(this->pos_, data, len);
|
||||
this->pos_ += len;
|
||||
uint8_t *__restrict__ pos = this->pos_;
|
||||
std::memcpy(pos, data, len);
|
||||
this->pos_ = pos + len;
|
||||
}
|
||||
/// Write a precomputed tag byte + 32-bit value in one operation.
|
||||
/// Tag must be a single-byte varint (< 128). No zero check.
|
||||
inline void write_tag_and_fixed32(uint8_t tag, uint32_t value) ESPHOME_ALWAYS_INLINE {
|
||||
this->debug_check_bounds_(5);
|
||||
this->pos_[0] = tag;
|
||||
uint8_t *__restrict__ pos = this->pos_;
|
||||
pos[0] = tag;
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
std::memcpy(this->pos_ + 1, &value, 4);
|
||||
std::memcpy(pos + 1, &value, 4);
|
||||
#else
|
||||
this->pos_[1] = static_cast<uint8_t>(value & 0xFF);
|
||||
this->pos_[2] = static_cast<uint8_t>((value >> 8) & 0xFF);
|
||||
this->pos_[3] = static_cast<uint8_t>((value >> 16) & 0xFF);
|
||||
this->pos_[4] = static_cast<uint8_t>((value >> 24) & 0xFF);
|
||||
pos[1] = static_cast<uint8_t>(value & 0xFF);
|
||||
pos[2] = static_cast<uint8_t>((value >> 8) & 0xFF);
|
||||
pos[3] = static_cast<uint8_t>((value >> 16) & 0xFF);
|
||||
pos[4] = static_cast<uint8_t>((value >> 24) & 0xFF);
|
||||
#endif
|
||||
this->pos_ += 5;
|
||||
this->pos_ = pos + 5;
|
||||
}
|
||||
void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) {
|
||||
if (len == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field_id, 2); // type 2: Length-delimited string
|
||||
this->encode_varint_raw(len);
|
||||
// Direct memcpy into pre-sized buffer — avoids push_back() per-byte capacity checks
|
||||
// and vector::insert() iterator overhead. ~10-11x faster for 16-32 byte strings.
|
||||
this->debug_check_bounds_(len);
|
||||
std::memcpy(this->pos_, string, len);
|
||||
this->pos_ += len;
|
||||
// Inline the length varint + memcpy under a single __restrict__ pos
|
||||
// to avoid a store-load pair between encode_varint_raw and encode_raw.
|
||||
uint8_t *__restrict__ pos = this->pos_;
|
||||
if (len < 128) [[likely]] {
|
||||
this->debug_check_bounds_(1 + len);
|
||||
*pos++ = static_cast<uint8_t>(len);
|
||||
} else {
|
||||
// Length >= 128: use slow path for the length varint, then re-hoist pos
|
||||
this->encode_varint_raw_slow_(len);
|
||||
pos = this->pos_;
|
||||
this->debug_check_bounds_(len);
|
||||
}
|
||||
std::memcpy(pos, string, len);
|
||||
this->pos_ = pos + len;
|
||||
}
|
||||
void encode_string(uint32_t field_id, const std::string &value, bool force = false) {
|
||||
this->encode_string(field_id, value.data(), value.size(), force);
|
||||
@@ -293,7 +331,9 @@ class ProtoWriteBuffer {
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0); // type 0: Varint - bool
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = value ? 0x01 : 0x00;
|
||||
uint8_t *__restrict__ pos = this->pos_;
|
||||
*pos++ = value ? 0x01 : 0x00;
|
||||
this->pos_ = pos;
|
||||
}
|
||||
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
@@ -301,15 +341,17 @@ class ProtoWriteBuffer {
|
||||
|
||||
this->encode_field_raw(field_id, 5); // type 5: 32-bit fixed32
|
||||
this->debug_check_bounds_(4);
|
||||
uint8_t *__restrict__ pos = this->pos_;
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
// Protobuf fixed32 is little-endian, so direct copy works
|
||||
std::memcpy(this->pos_, &value, 4);
|
||||
this->pos_ += 4;
|
||||
std::memcpy(pos, &value, 4);
|
||||
this->pos_ = pos + 4;
|
||||
#else
|
||||
*this->pos_++ = (value >> 0) & 0xFF;
|
||||
*this->pos_++ = (value >> 8) & 0xFF;
|
||||
*this->pos_++ = (value >> 16) & 0xFF;
|
||||
*this->pos_++ = (value >> 24) & 0xFF;
|
||||
*pos++ = (value >> 0) & 0xFF;
|
||||
*pos++ = (value >> 8) & 0xFF;
|
||||
*pos++ = (value >> 16) & 0xFF;
|
||||
*pos++ = (value >> 24) & 0xFF;
|
||||
this->pos_ = pos;
|
||||
#endif
|
||||
}
|
||||
// NOTE: Wire type 1 (64-bit fixed: double, fixed64, sfixed64) is intentionally
|
||||
@@ -366,9 +408,16 @@ class ProtoWriteBuffer {
|
||||
|
||||
#ifdef ESPHOME_DEBUG_API
|
||||
void debug_check_bounds_(size_t bytes, const char *caller = __builtin_FUNCTION());
|
||||
/// Sync pos_ from a local pointer, then check bounds. For use in __restrict__ loops
|
||||
/// where pos_ is hoisted into a local but debug checks need the current position.
|
||||
void sync_debug_check_bounds_(uint8_t *pos, size_t bytes, const char *caller = __builtin_FUNCTION()) {
|
||||
this->pos_ = pos;
|
||||
this->debug_check_bounds_(bytes, caller);
|
||||
}
|
||||
void debug_check_encode_size_(uint32_t field_id, uint32_t expected, ptrdiff_t actual);
|
||||
#else
|
||||
void debug_check_bounds_([[maybe_unused]] size_t bytes) {}
|
||||
void sync_debug_check_bounds_(uint8_t *pos, [[maybe_unused]] size_t bytes) {}
|
||||
#endif
|
||||
|
||||
APIBuffer *buffer_;
|
||||
@@ -531,6 +580,17 @@ class ProtoSize {
|
||||
return varint_wide(value);
|
||||
return varint_slow(value);
|
||||
}
|
||||
/// Size of a varint expected to be 1-2 bytes (e.g. zigzag RSSI, small lengths).
|
||||
/// Inlines both checks; falls back to slow path for 3+ bytes.
|
||||
static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE varint_short(uint32_t value) {
|
||||
if (value < VARINT_THRESHOLD_1_BYTE) [[likely]]
|
||||
return 1;
|
||||
if (value < VARINT_THRESHOLD_2_BYTE) [[likely]]
|
||||
return 2;
|
||||
if (__builtin_is_constant_evaluated())
|
||||
return varint_wide(value);
|
||||
return varint_slow(value);
|
||||
}
|
||||
|
||||
private:
|
||||
// Slow path for varint >= 128, outlined to keep fast path small
|
||||
@@ -645,10 +705,10 @@ class ProtoSize {
|
||||
return value ? field_id_size + 4 : 0;
|
||||
}
|
||||
static constexpr uint32_t calc_sint32(uint32_t field_id_size, int32_t value) {
|
||||
return value ? field_id_size + varint(encode_zigzag32(value)) : 0;
|
||||
return value ? field_id_size + varint_short(encode_zigzag32(value)) : 0;
|
||||
}
|
||||
static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE calc_sint32_force(uint32_t field_id_size, int32_t value) {
|
||||
return field_id_size + varint(encode_zigzag32(value));
|
||||
return field_id_size + varint_short(encode_zigzag32(value));
|
||||
}
|
||||
static constexpr uint32_t calc_int64(uint32_t field_id_size, int64_t value) {
|
||||
return value ? field_id_size + varint(value) : 0;
|
||||
|
||||
@@ -229,7 +229,7 @@ class TypeInfo(ABC):
|
||||
RAW_ENCODE_MAP: dict[str, str] = {
|
||||
"encode_uint32": "buffer.encode_varint_raw({value});",
|
||||
"encode_uint64": "buffer.encode_varint_raw_64({value});",
|
||||
"encode_sint32": "buffer.encode_varint_raw(encode_zigzag32({value}));",
|
||||
"encode_sint32": "buffer.encode_varint_raw_short(encode_zigzag32({value}));",
|
||||
"encode_sint64": "buffer.encode_varint_raw_64(encode_zigzag64({value}));",
|
||||
"encode_int64": "buffer.encode_varint_raw_64(static_cast<uint64_t>({value}));",
|
||||
"encode_bool": "buffer.write_raw_byte({value} ? 0x01 : 0x00);",
|
||||
|
||||
Reference in New Issue
Block a user