mirror of
https://github.com/esphome/esphome.git
synced 2026-09-11 15:27:33 +00:00
[api] Key the generated decode switch on the wire tag on every target
The host no longer gets its own switch shape through USE_HOST; every build compiles the same switch on the field's wire tag.
This commit is contained in:
+319
-579
File diff suppressed because it is too large
Load Diff
@@ -170,20 +170,9 @@ class ProtoVarInt {
|
||||
class ProtoMessage;
|
||||
class ProtoSize;
|
||||
|
||||
// Switch key for generated decode_field() bodies. Embedded builds use compare chains
|
||||
// (-fno-jump-tables), so the full tag is one compare per field with no guard. The host gets a
|
||||
// jump table from the dense field number switch plus a tag compare that rejects the wrong wire type.
|
||||
#ifdef USE_HOST
|
||||
#define PROTO_DECODE_KEY(tag) ((tag) >> 3)
|
||||
#define PROTO_DECODE_CASE(field_id, wire_type) (field_id)
|
||||
#define PROTO_DECODE_GUARD(tag, field_id, wire_type) \
|
||||
if ((tag) != (((field_id) << 3) | (wire_type))) \
|
||||
return false
|
||||
#else
|
||||
#define PROTO_DECODE_KEY(tag) (tag)
|
||||
#define PROTO_DECODE_CASE(field_id, wire_type) (((field_id) << 3) | (wire_type))
|
||||
#define PROTO_DECODE_GUARD(tag, field_id, wire_type) (void) 0
|
||||
#endif
|
||||
/// Case label for decode_field(): the wire tag of a field, so a field that arrives with another wire
|
||||
/// type matches no case.
|
||||
constexpr uint32_t proto_tag(uint32_t field_id, uint32_t wire_type) { return (field_id << 3) | wire_type; }
|
||||
|
||||
/// One decoded field: the payload pointer and a scalar holding the varint or fixed32 value, or the
|
||||
/// length of a length-delimited field. The wire type in the tag says which applies; accessors do not check.
|
||||
|
||||
@@ -28,6 +28,11 @@ class WireType(IntEnum):
|
||||
END_GROUP = 4 # groups (deprecated)
|
||||
FIXED32 = 5 # fixed32, sfixed32, float
|
||||
|
||||
@property
|
||||
def cpp_name(self) -> str:
|
||||
"""The matching constant in proto.h."""
|
||||
return f"WIRE_TYPE_{self.name}"
|
||||
|
||||
|
||||
# Generate with
|
||||
# protoc --python_out=script/api_protobuf -I esphome/components/api/ api_options.proto
|
||||
@@ -230,10 +235,9 @@ class TypeInfo(ABC):
|
||||
return f"{self.cpp_type} {self.field_name}{{{self.default_value}}};"
|
||||
|
||||
def decode_case(self, body: str) -> str:
|
||||
"""Emit one decode_field() case, keyed through the PROTO_DECODE_* macros in proto.h."""
|
||||
wire_type = int(self.wire_type)
|
||||
return f"case PROTO_DECODE_CASE({self.number}, {wire_type}):\n" + indent(
|
||||
f"PROTO_DECODE_GUARD(tag, {self.number}, {wire_type});\n{body}\nbreak;"
|
||||
"""Emit one decode_field() case, keyed on the field's wire tag."""
|
||||
return f"case proto_tag({self.number}, {self.wire_type.cpp_name}):\n" + indent(
|
||||
f"{body}\nbreak;"
|
||||
)
|
||||
|
||||
# Decode expression per wire type; a decodable type sets exactly one.
|
||||
@@ -2685,7 +2689,7 @@ def build_message_type(
|
||||
if decode:
|
||||
o = f"bool {desc.name}::decode_field(uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) {{\n"
|
||||
o += " const ProtoFieldValue value(data, scalar);\n"
|
||||
o += " switch (PROTO_DECODE_KEY(tag)) {\n"
|
||||
o += " switch (tag) {\n"
|
||||
o += indent("\n".join(decode), " ") + "\n"
|
||||
o += " default: return false;\n"
|
||||
o += " }\n"
|
||||
|
||||
@@ -197,22 +197,22 @@ def _decode_case(field_type: int, number: int) -> str:
|
||||
@pytest.mark.parametrize(
|
||||
("field_type", "number", "wire_type", "accessor"),
|
||||
[
|
||||
(UINT32, 2, 0, "value.as_varint()"),
|
||||
(BOOL, 3, 0, "value.as_varint() != 0"),
|
||||
(STRING, 1, 2, "value.data()"),
|
||||
(FLOAT, 4, 5, "value.as_float()"),
|
||||
(FIXED32, 5, 5, "value.as_fixed32()"),
|
||||
(UINT32, 2, "WIRE_TYPE_VARINT", "value.as_varint()"),
|
||||
(BOOL, 3, "WIRE_TYPE_VARINT", "value.as_varint() != 0"),
|
||||
(STRING, 1, "WIRE_TYPE_LENGTH_DELIMITED", "value.data()"),
|
||||
(FLOAT, 4, "WIRE_TYPE_FIXED32", "value.as_float()"),
|
||||
(FIXED32, 5, "WIRE_TYPE_FIXED32", "value.as_fixed32()"),
|
||||
],
|
||||
)
|
||||
def test_decode_cases_carry_field_number_and_wire_type(
|
||||
field_type: int, number: int, wire_type: int, accessor: str
|
||||
field_type: int, number: int, wire_type: str, accessor: str
|
||||
) -> None:
|
||||
"""Each decoded field yields one case keyed on its number and declared wire type."""
|
||||
case = _decode_case(field_type, number)
|
||||
lines = case.splitlines()
|
||||
assert lines[0] == f"case PROTO_DECODE_CASE({number}, {wire_type}):", case
|
||||
assert lines[1].strip() == f"PROTO_DECODE_GUARD(tag, {number}, {wire_type});", case
|
||||
assert accessor in case, case
|
||||
assert lines[0] == f"case proto_tag({number}, {wire_type}):", case
|
||||
assert accessor in lines[1], case
|
||||
assert lines[-1].strip() == "break;", case
|
||||
|
||||
|
||||
def test_message_gets_a_single_decode_field_override() -> None:
|
||||
@@ -231,7 +231,11 @@ def test_message_gets_a_single_decode_field_override() -> None:
|
||||
)
|
||||
== 1
|
||||
)
|
||||
assert "switch (PROTO_DECODE_KEY(tag)) {" in cpp
|
||||
assert "switch (tag) {" in cpp
|
||||
assert "const ProtoFieldValue value(data, scalar);" in cpp
|
||||
for number, wire_type in ((1, 2), (2, 0), (3, 5)):
|
||||
assert f"case PROTO_DECODE_CASE({number}, {wire_type}):" in cpp, cpp
|
||||
for number, wire_type in (
|
||||
(1, "WIRE_TYPE_LENGTH_DELIMITED"),
|
||||
(2, "WIRE_TYPE_VARINT"),
|
||||
(3, "WIRE_TYPE_FIXED32"),
|
||||
):
|
||||
assert f"case proto_tag({number}, {wire_type}):" in cpp, cpp
|
||||
|
||||
Reference in New Issue
Block a user