From: Petteri Aimonen Date: Sun, 4 Jan 2015 09:36:42 +0000 (+0200) Subject: Add int_size option for generator. X-Git-Tag: 5.0.2~186^2~173 X-Git-Url: https://gerrit.automotivelinux.org/gerrit/gitweb?a=commitdiff_plain;h=50c67ecec4895f65ba684e4b46b4b70980a5be6a;p=apps%2Fagl-service-can-low-level.git Add int_size option for generator. This allows overriding the integer field types to e.g. uint8_t for saving RAM. Update issue 139 Status: FixedInGit --- diff --git a/docs/reference.rst b/docs/reference.rst index 8018bdb9..e16500ad 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -76,6 +76,8 @@ The generator behaviour can be adjusted using these options, defined in the max_size Allocated size for *bytes* and *string* fields. max_count Allocated number of entries in arrays (*repeated* fields). +int_size Override the integer type of a field. + (To use e.g. uint8_t to save RAM.) type Type of the generated field. Default value is *FT_DEFAULT*, which selects automatically. You can use *FT_CALLBACK*, *FT_POINTER*, @@ -88,6 +90,7 @@ long_names Prefix the enum name to the enum value in packed_struct Make the generated structures packed. NOTE: This cannot be used on CPUs that break on unaligned accesses to variables. +skip_message Skip the whole message from generation. ============================ ================================================ These options can be defined for the .proto files before they are converted diff --git a/generator/nanopb_generator.py b/generator/nanopb_generator.py index 6dcc911f..85cb413b 100755 --- a/generator/nanopb_generator.py +++ b/generator/nanopb_generator.py @@ -44,22 +44,30 @@ except: import time import os.path -# Values are tuple (c type, pb type, encoded size) +# Values are tuple (c type, pb type, encoded size, int_size_allowed) FieldD = descriptor.FieldDescriptorProto datatypes = { - FieldD.TYPE_BOOL: ('bool', 'BOOL', 1), - FieldD.TYPE_DOUBLE: ('double', 'DOUBLE', 8), - FieldD.TYPE_FIXED32: ('uint32_t', 'FIXED32', 4), - FieldD.TYPE_FIXED64: ('uint64_t', 'FIXED64', 8), - FieldD.TYPE_FLOAT: ('float', 'FLOAT', 4), - FieldD.TYPE_INT32: ('int32_t', 'INT32', 10), - FieldD.TYPE_INT64: ('int64_t', 'INT64', 10), - FieldD.TYPE_SFIXED32: ('int32_t', 'SFIXED32', 4), - FieldD.TYPE_SFIXED64: ('int64_t', 'SFIXED64', 8), - FieldD.TYPE_SINT32: ('int32_t', 'SINT32', 5), - FieldD.TYPE_SINT64: ('int64_t', 'SINT64', 10), - FieldD.TYPE_UINT32: ('uint32_t', 'UINT32', 5), - FieldD.TYPE_UINT64: ('uint64_t', 'UINT64', 10) + FieldD.TYPE_BOOL: ('bool', 'BOOL', 1, False), + FieldD.TYPE_DOUBLE: ('double', 'DOUBLE', 8, False), + FieldD.TYPE_FIXED32: ('uint32_t', 'FIXED32', 4, False), + FieldD.TYPE_FIXED64: ('uint64_t', 'FIXED64', 8, False), + FieldD.TYPE_FLOAT: ('float', 'FLOAT', 4, False), + FieldD.TYPE_INT32: ('int32_t', 'INT32', 10, True), + FieldD.TYPE_INT64: ('int64_t', 'INT64', 10, True), + FieldD.TYPE_SFIXED32: ('int32_t', 'SFIXED32', 4, False), + FieldD.TYPE_SFIXED64: ('int64_t', 'SFIXED64', 8, False), + FieldD.TYPE_SINT32: ('int32_t', 'SINT32', 5, True), + FieldD.TYPE_SINT64: ('int64_t', 'SINT64', 10, True), + FieldD.TYPE_UINT32: ('uint32_t', 'UINT32', 5, True), + FieldD.TYPE_UINT64: ('uint64_t', 'UINT64', 10, True) +} + +# Integer size overrides (from .proto settings) +intsizes = { + nanopb_pb2.IS_8: 'int8_t', + nanopb_pb2.IS_16: 'int16_t', + nanopb_pb2.IS_32: 'int32_t', + nanopb_pb2.IS_64: 'int64_t', } class Names: @@ -226,7 +234,13 @@ class Field: # Decide the C data type to use in the struct. if datatypes.has_key(desc.type): - self.ctype, self.pbtype, self.enc_size = datatypes[desc.type] + self.ctype, self.pbtype, self.enc_size, isa = datatypes[desc.type] + + # Override the field size if user wants to use smaller integers + if isa and field_options.int_size != nanopb_pb2.IS_DEFAULT: + self.ctype = intsizes[field_options.int_size] + if desc.type == FieldD.TYPE_UINT32 or desc.type == FieldD.TYPE_UINT64: + self.ctype = 'u' + self.ctype; elif desc.type == FieldD.TYPE_ENUM: self.pbtype = 'ENUM' self.ctype = names_from_type_name(desc.type_name) diff --git a/generator/proto/nanopb.proto b/generator/proto/nanopb.proto index 0716be4b..1bde5967 100644 --- a/generator/proto/nanopb.proto +++ b/generator/proto/nanopb.proto @@ -18,6 +18,14 @@ enum FieldType { FT_IGNORE = 3; // Ignore the field completely. } +enum IntSize { + IS_DEFAULT = 0; // Default, 32/64bit based on type in .proto + IS_8 = 1; + IS_16 = 2; + IS_32 = 3; + IS_64 = 4; +} + // This is the inner options message, which basically defines options for // a field. When it is used in message or file scope, it applies to all // fields. @@ -28,6 +36,10 @@ message NanoPBOptions { // Allocated number of entries in arrays ('repeated' fields) optional int32 max_count = 2; + // Size of integer fields. Can save some memory if you don't need + // full 32 bits for the value. + optional IntSize int_size = 7 [default = IS_DEFAULT]; + // Force type of field (callback or static allocation) optional FieldType type = 3 [default = FT_DEFAULT]; diff --git a/pb_decode.c b/pb_decode.c index 367f073b..b5ec1ef0 100644 --- a/pb_decode.c +++ b/pb_decode.c @@ -1095,6 +1095,8 @@ static bool checkreturn pb_dec_uvarint(pb_istream_t *stream, const pb_field_t *f switch (field->data_size) { + case 1: *(uint8_t*)dest = (uint8_t)value; break; + case 2: *(uint16_t*)dest = (uint16_t)value; break; case 4: *(uint32_t*)dest = (uint32_t)value; break; case 8: *(uint64_t*)dest = value; break; default: PB_RETURN_ERROR(stream, "invalid data_size"); @@ -1111,6 +1113,8 @@ static bool checkreturn pb_dec_svarint(pb_istream_t *stream, const pb_field_t *f switch (field->data_size) { + case 1: *(int8_t*)dest = (int8_t)value; break; + case 2: *(int16_t*)dest = (int16_t)value; break; case 4: *(int32_t*)dest = (int32_t)value; break; case 8: *(int64_t*)dest = value; break; default: PB_RETURN_ERROR(stream, "invalid data_size"); diff --git a/pb_encode.c b/pb_encode.c index 5318361e..cef98861 100644 --- a/pb_encode.c +++ b/pb_encode.c @@ -567,7 +567,7 @@ static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_t *fi int64_t value = 0; /* Cases 1 and 2 are for compilers that have smaller types for bool - * or enums. */ + * or enums, and for int_size option. */ switch (field->data_size) { case 1: value = *(const int8_t*)src; break; @@ -586,6 +586,8 @@ static bool checkreturn pb_enc_uvarint(pb_ostream_t *stream, const pb_field_t *f switch (field->data_size) { + case 1: value = *(const uint8_t*)src; break; + case 2: value = *(const uint16_t*)src; break; case 4: value = *(const uint32_t*)src; break; case 8: value = *(const uint64_t*)src; break; default: PB_RETURN_ERROR(stream, "invalid data_size"); @@ -600,6 +602,8 @@ static bool checkreturn pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *f switch (field->data_size) { + case 1: value = *(const int8_t*)src; break; + case 2: value = *(const int16_t*)src; break; case 4: value = *(const int32_t*)src; break; case 8: value = *(const int64_t*)src; break; default: PB_RETURN_ERROR(stream, "invalid data_size"); diff --git a/tests/intsizes/SConscript b/tests/intsizes/SConscript new file mode 100644 index 00000000..a90680bc --- /dev/null +++ b/tests/intsizes/SConscript @@ -0,0 +1,12 @@ +# Test that the int_size option in .proto works. + +Import('env') + +env.NanopbProto('intsizes') + +p = env.Program(["intsizes_unittests.c", + "intsizes.pb.c", + "$COMMON/pb_encode.o", + "$COMMON/pb_decode.o", + "$COMMON/pb_common.o"]) +env.RunTest(p) diff --git a/tests/intsizes/intsizes.proto b/tests/intsizes/intsizes.proto new file mode 100644 index 00000000..236bf183 --- /dev/null +++ b/tests/intsizes/intsizes.proto @@ -0,0 +1,39 @@ +/* Test the integer size overriding in nanopb options. + * This allows to use 8- and 16-bit integer variables, which are not supported + * directly by Google Protobuf. + * + * The int_size setting will override the number of bits, but keep the type + * otherwise. E.g. uint32 + IS_8 => uint8_t + */ + +import 'nanopb.proto'; + +message IntSizes { + required int32 req_int8 = 1 [(nanopb).int_size = IS_8]; + required uint32 req_uint8 = 2 [(nanopb).int_size = IS_8]; + required sint32 req_sint8 = 3 [(nanopb).int_size = IS_8]; + required int32 req_int16 = 4 [(nanopb).int_size = IS_16]; + required uint32 req_uint16 = 5 [(nanopb).int_size = IS_16]; + required sint32 req_sint16 = 6 [(nanopb).int_size = IS_16]; + required int32 req_int32 = 7 [(nanopb).int_size = IS_32]; + required uint32 req_uint32 = 8 [(nanopb).int_size = IS_32]; + required sint32 req_sint32 = 9 [(nanopb).int_size = IS_32]; + required int32 req_int64 = 10 [(nanopb).int_size = IS_64]; + required uint32 req_uint64 = 11 [(nanopb).int_size = IS_64]; + required sint32 req_sint64 = 12 [(nanopb).int_size = IS_64]; +} + +message DefaultSizes { + required int32 req_int8 = 1 ; + required uint32 req_uint8 = 2 ; + required sint32 req_sint8 = 3 ; + required int32 req_int16 = 4 ; + required uint32 req_uint16 = 5 ; + required sint32 req_sint16 = 6 ; + required int32 req_int32 = 7 ; + required uint32 req_uint32 = 8 ; + required sint32 req_sint32 = 9 ; + required int64 req_int64 = 10; + required uint64 req_uint64 = 11; + required sint64 req_sint64 = 12; +} diff --git a/tests/intsizes/intsizes_unittests.c b/tests/intsizes/intsizes_unittests.c new file mode 100644 index 00000000..29cc7ab0 --- /dev/null +++ b/tests/intsizes/intsizes_unittests.c @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include "unittests.h" +#include "intsizes.pb.h" + +#define S(x) pb_istream_from_buffer((uint8_t*)x, sizeof(x) - 1) + +/* This is a macro instead of function in order to get the actual values + * into the TEST() lines in output */ +#define TEST_ROUNDTRIP(int8, uint8, sint8, \ + int16, uint16, sint16, \ + int32, uint32, sint32, \ + int64, uint64, sint64, expected_result) \ +{ \ + uint8_t buffer1[128], buffer2[128]; \ + size_t msgsize; \ + DefaultSizes msg1 = DefaultSizes_init_zero; \ + IntSizes msg2 = IntSizes_init_zero; \ + \ + msg1.req_int8 = int8; \ + msg1.req_uint8 = uint8; \ + msg1.req_sint8 = sint8; \ + msg1.req_int16 = int16; \ + msg1.req_uint16 = uint16; \ + msg1.req_sint16 = sint16; \ + msg1.req_int32 = int32; \ + msg1.req_uint32 = uint32; \ + msg1.req_sint32 = sint32; \ + msg1.req_int64 = int64; \ + msg1.req_uint64 = uint64; \ + msg1.req_sint64 = sint64; \ + \ + { \ + pb_ostream_t s = pb_ostream_from_buffer(buffer1, sizeof(buffer1)); \ + TEST(pb_encode(&s, DefaultSizes_fields, &msg1)); \ + msgsize = s.bytes_written; \ + } \ + \ + { \ + pb_istream_t s = pb_istream_from_buffer(buffer1, msgsize); \ + TEST(pb_decode(&s, IntSizes_fields, &msg2) == expected_result); \ + if (expected_result) \ + { \ + TEST(msg2.req_int8 == int8); \ + TEST(msg2.req_uint8 == uint8); \ + TEST(msg2.req_sint8 == sint8); \ + TEST(msg2.req_int16 == int16); \ + TEST(msg2.req_uint16 == uint16); \ + TEST(msg2.req_sint16 == sint16); \ + TEST(msg2.req_int32 == int32); \ + TEST(msg2.req_uint32 == uint32); \ + TEST(msg2.req_sint32 == sint32); \ + TEST(msg2.req_int64 == int64); \ + TEST(msg2.req_uint64 == uint64); \ + TEST(msg2.req_sint64 == sint64); \ + } \ + } \ + \ + if (expected_result) \ + { \ + pb_ostream_t s = pb_ostream_from_buffer(buffer2, sizeof(buffer2)); \ + TEST(pb_encode(&s, IntSizes_fields, &msg2)); \ + TEST(s.bytes_written == msgsize); \ + TEST(memcmp(buffer1, buffer2, msgsize) == 0); \ + } \ +} + +int main() +{ + int status = 0; + + { + IntSizes msg = IntSizes_init_zero; + + COMMENT("Test field sizes"); + TEST(sizeof(msg.req_int8) == 1); + TEST(sizeof(msg.req_uint8) == 1); + TEST(sizeof(msg.req_sint8) == 1); + TEST(sizeof(msg.req_int16) == 2); + TEST(sizeof(msg.req_uint16) == 2); + TEST(sizeof(msg.req_sint16) == 2); + TEST(sizeof(msg.req_int32) == 4); + TEST(sizeof(msg.req_uint32) == 4); + TEST(sizeof(msg.req_sint32) == 4); + TEST(sizeof(msg.req_int64) == 8); + TEST(sizeof(msg.req_uint64) == 8); + TEST(sizeof(msg.req_sint64) == 8); + } + + COMMENT("Test roundtrip at maximum value"); + TEST_ROUNDTRIP(127, 255, 127, + 32767, 65535, 32767, + INT32_MAX, UINT32_MAX, INT32_MAX, + INT64_MAX, UINT64_MAX, INT64_MAX, true); + + COMMENT("Test roundtrip at minimum value"); + TEST_ROUNDTRIP(-128, 0, -128, + -32768, 0, -32768, + INT32_MIN, 0, INT32_MIN, + INT64_MIN, 0, INT64_MIN, true); + + if (status != 0) + fprintf(stdout, "\n\nSome tests FAILED!\n"); + + return status; +} \ No newline at end of file