Add user static array field support (with recursion)
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Wed, 9 Sep 2020 15:00:12 +0000 (11:00 -0400)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Wed, 9 Sep 2020 15:50:46 +0000 (11:50 -0400)
This patch adds support for user static array fields.

The element field type of a user static array field type can be any of
the following:

* Bit array field type.
* String field type.
* Static array field type.

The generated serializing C code does not assume anything about the
memory layout of a user array: it iterates each item to serialize them
individually.

For example, consider the following TSDL field type:

    class: static-array
    length: 3
    element-field-type:
      class: unsigned-integer
      size: 16

The tracing function's parameter's C type for the corresponding field is
`const uint16_t *`. Assuming the parameter's name is `a`, the
serialization function accesses `a[0]`, `a[1]`, and `a[2]`; it doesn't
copy large blocks of memory as is. This is because we don't know the
target architecture's alignment constraints and general memory layout.

For:

    class: static-array
    length: 3
    element-field-type:
      class: string

the parameter's C type is `const char * const *`.

Note that a pointed C type is always `const`, so the `barectf.h` user
might need to cast accordingly if her own C types miss such `const`
qualifiers. Here's another example:

    class: static-array
    length: 2
    element-field-type:
      class: static-array
      length: 3
      element-field-type:
        class: unsigned-integer
        size: 8

This becomes `const uint8_t * const *`.

As of this patch, a tracing function uses `uint32_t` as the loop index
type. I believe this will be enough for barectf's use cases. It could
(should) also be based on the static array field type's length.

Each loop is isolated within its own C scope. The loop index variable
names are `i`, then `j`, then `k`, then `k1`, then `k2`, and so on.

Notable changes
===============
`cgen.py`:
    * Add a level property to operation objects.

      This is the static array nesting level.

    * _OpBuilder._build_for_ft(): handle user static array field types.

      A user static array field type leads to an "align" operation
      (possibly) and its element field type's operation, all within a
      compound operation.

      The name of the element field type's operation is the C array
      subscript with the right index variable name, for example `[i]`.

      The packet header's UUID field type still has its special case.

    * Add _loop_var_name() function which returns the name of a loop
      index variable name based on a level (0 is `i`, 1 is `j`, and so
      on).

    * Add general template filter `loop_var_name` which is
      _loop_var_name().

    * Add general template filter `op_src_var_name` which returns an
      operation's source variable name.

      This used to be the op_src() macro in `c/common.j2`, but with
      array subscript names, it's too ugly for the Jinja 2 language.

    * _CodeGen._ft_c_type(): handle static array field types (return a
      `_PointerCType` object).

`config_parse_v3.py`:
    Accept user static array field types.

Existing templates:
    Remove op_src(); use the `op_src_var_name` filter instead.

`serialize-write-static-array-statements.j2`,
`size-write-static-array-statements.j2`:
    New templates to serialize and compute the size of static array
    fields.

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
barectf/cgen.py
barectf/config_parse_v3.py
barectf/templates/c/barectf.c.j2
barectf/templates/c/common.j2
barectf/templates/c/serialize-write-int-statements.j2
barectf/templates/c/serialize-write-real-statements.j2
barectf/templates/c/serialize-write-skip-save-statements.j2
barectf/templates/c/serialize-write-static-array-statements.j2 [new file with mode: 0644]
barectf/templates/c/serialize-write-string-statements.j2
barectf/templates/c/size-write-static-array-statements.j2 [new file with mode: 0644]
barectf/templates/c/size-write-string-statements.j2

index 5d2e9c3604f387cb4f4a734e774dec1b46188614..f634b1282837460832088f2d8eebe69d17f455bd 100644 (file)
@@ -45,12 +45,16 @@ _OpTemplates = collections.namedtuple('_OpTemplates', ['serialize', 'size'])
 # * A list of names which, when joined with `_`, form the generic
 #   C source variable name.
 #
+# * A level: how deep this operation is within the operation tree.
+#
 # * Serialization and size computation templates to generate the
 #   operation's source code for those functions.
 class _Op:
-    def __init__(self, ft: barectf_config._FieldType, names: List[str], templates: _OpTemplates):
+    def __init__(self, ft: barectf_config._FieldType, names: List[str], level: Count,
+                 templates: _OpTemplates):
         self._ft = ft
         self._names = copy.copy(names)
+        self._level = level
         self._templates = templates
 
     @property
@@ -61,6 +65,10 @@ class _Op:
     def names(self) -> List[str]:
         return self._names
 
+    @property
+    def level(self) -> Count:
+        return self._level
+
     @property
     def top_name(self) -> str:
         return self._names[-1]
@@ -86,9 +94,9 @@ class _Op:
 #
 # The templates of a compound operation handles its suboperations.
 class _CompoundOp(_Op):
-    def __init__(self, ft: barectf_config._FieldType, names: List[str], templates: _OpTemplates,
-                 subops: List[Any] = None):
-        super().__init__(ft, names, templates)
+    def __init__(self, ft: barectf_config._FieldType, names: List[str], level: Count,
+                 templates: _OpTemplates, subops: List[Any] = None):
+        super().__init__(ft, names, level, templates)
         self._subops = subops
 
     @property
@@ -99,8 +107,8 @@ class _CompoundOp(_Op):
 # Leaf operation (abstract class).
 class _LeafOp(_Op):
     def __init__(self, offset_in_byte: Count, ft: barectf_config._FieldType, names: List[str],
-                 templates: _OpTemplates):
-        super().__init__(ft, names, templates)
+                 level: Count, templates: _OpTemplates):
+        super().__init__(ft, names, level, templates)
         assert offset_in_byte >= 0 and offset_in_byte < 8
         self._offset_in_byte = offset_in_byte
 
@@ -112,8 +120,8 @@ class _LeafOp(_Op):
 # An "align" operation.
 class _AlignOp(_LeafOp):
     def __init__(self, offset_in_byte: Count, ft: barectf_config._FieldType, names: List[str],
-                 templates: _OpTemplates, value: Alignment):
-        super().__init__(offset_in_byte, ft, names, templates)
+                 level: Count, templates: _OpTemplates, value: Alignment):
+        super().__init__(offset_in_byte, ft, names, level, templates)
         self._value = value
 
     @property
@@ -142,6 +150,7 @@ class _OpBuilder:
         self._last_alignment: Optional[Alignment] = None
         self._last_bit_array_size: Optional[Count] = None
         self._names: List[str] = []
+        self._level = Count(0)
         self._offset_in_byte = Count(0)
         self._cg = cg
 
@@ -159,6 +168,7 @@ class _OpBuilder:
 
         assert type(ft) is barectf_config.StructureFieldType
         assert len(self._names) == 0
+        assert self._level == 0
         ops = self._build_for_ft(ft, name, spec_serialize_write_templates)
         assert len(ops) == 1
         assert type(ops[0]) is _CompoundOp
@@ -206,7 +216,7 @@ class _OpBuilder:
             elif type(ft) is barectf_config.StringFieldType:
                 size_write_templ = self._cg._size_write_string_statements_templ
 
-            return _WriteOp(offset_in_byte, ft, self._names,
+            return _WriteOp(offset_in_byte, ft, self._names, self._level,
                             _OpTemplates(serialize_write_templ, size_write_templ))
 
         # Creates and returns an "align" operation for the field type
@@ -222,7 +232,7 @@ class _OpBuilder:
             self._offset_in_byte = Count(align(self._offset_in_byte, alignment) % 8)
 
             if do_align and alignment > 1:
-                return _AlignOp(offset_in_byte, ft, self._names,
+                return _AlignOp(offset_in_byte, ft, self._names, self._level,
                                 _OpTemplates(self._cg._serialize_align_statements_templ,
                                              self._cg._size_align_statements_templ),
                                 alignment)
@@ -235,16 +245,20 @@ class _OpBuilder:
         def must_align(align_req: Alignment) -> bool:
             return self._last_alignment != align_req or typing.cast(Count, self._last_bit_array_size) % align_req != 0
 
+        # Returns whether or not `ft` is a compound field type.
+        def ft_is_compound(ft: barectf_config._FieldType) -> bool:
+            return isinstance(ft, (barectf_config.StructureFieldType, barectf_config.StaticArrayFieldType))
+
         # push field type's name to the builder's name stack initially
         self._names.append(name)
 
         # operations to return
         ops: List[_Op] = []
 
-        if isinstance(ft, (barectf_config.StringFieldType, barectf_config._ArrayFieldType)):
-            assert type(ft) is barectf_config.StringFieldType or top_name() == 'uuid'
+        is_uuid_ft = type(ft) is barectf_config.StaticArrayFieldType and self._names == [_RootFtPrefixes.PH, 'uuid']
 
-            # strings and arrays are always byte-aligned
+        if type(ft) is barectf_config.StringFieldType or is_uuid_ft:
+            # strings and UUID array are always byte-aligned
             do_align = must_align(Alignment(8))
             self._last_alignment = Alignment(8)
             self._last_bit_array_size = Count(8)
@@ -258,7 +272,7 @@ class _OpBuilder:
             do_align = must_align(ft.alignment)
             self._last_alignment = ft.alignment
 
-            if type(ft) is barectf_config.StructureFieldType:
+            if ft_is_compound(ft):
                 # reset last bit array size
                 self._last_bit_array_size = typing.cast(Count, ft.alignment)
             else:
@@ -267,10 +281,10 @@ class _OpBuilder:
                 self._last_bit_array_size = ft.size
 
             init_align_op = try_create_align_op(ft.alignment, do_align, ft)
+            subops: List[_Op] = []
 
             if type(ft) is barectf_config.StructureFieldType:
                 ft = typing.cast(barectf_config.StructureFieldType, ft)
-                subops: List[_Op] = []
 
                 if init_align_op is not None:
                     # append structure field's alignment as a suboperation
@@ -281,11 +295,30 @@ class _OpBuilder:
                     subops += self._build_for_ft(member.field_type, member_name,
                                                  spec_serialize_write_templates)
 
-                # create structre field's compound operation
-                ops.append(_CompoundOp(ft, self._names,
+                # create structure field's compound operation
+                ops.append(_CompoundOp(ft, self._names, self._level,
                                        _OpTemplates(self._cg._serialize_write_struct_statements_templ,
                                                     self._cg._size_write_struct_statements_templ),
-                                        subops))
+                                       subops))
+            elif type(ft) is barectf_config.StaticArrayFieldType:
+                ft = typing.cast(barectf_config.StaticArrayFieldType, ft)
+
+                if init_align_op is not None:
+                    # append static array field's alignment as a suboperation
+                    subops.append(init_align_op)
+
+                # append element's suboperations
+                self._level = Count(self._level + 1)
+                subops += self._build_for_ft(ft.element_field_type,
+                                             f'[{_loop_var_name(Count(self._level - 1))}]',
+                                             spec_serialize_write_templates)
+                self._level = Count(self._level - 1)
+
+                # create static array field's compound operation
+                ops.append(_CompoundOp(ft, self._names, self._level,
+                                       _OpTemplates(self._cg._serialize_write_static_array_statements_templ,
+                                                    self._cg._size_write_static_array_statements_templ),
+                                       subops))
             else:
                 # leaf field: align + write
                 if init_align_op is not None:
@@ -437,6 +470,14 @@ class _PointerCType(_CType):
         return s
 
 
+# Returns the name of a loop variable given a nesting level `level`.
+def _loop_var_name(level: Count) -> str:
+    if level < 3:
+        return 'ijk'[level]
+
+    return f'k{level - 2}'
+
+
 # A C code generator.
 #
 # Such a code generator can generate:
@@ -453,6 +494,8 @@ class _CodeGen:
             'open_func_params_str': self._open_func_params_str,
             'trace_func_params_str': self._trace_func_params_str,
             'serialize_ev_common_ctx_func_params_str': self._serialize_ev_common_ctx_func_params_str,
+            'loop_var_name': _loop_var_name,
+            'op_src_var_name': self._op_src_var_name,
         }
         self._func_proto_params_templ = self._create_template('func-proto-params.j2')
         self._serialize_align_statements_templ = self._create_template('serialize-align-statements.j2')
@@ -460,6 +503,7 @@ class _CodeGen:
         self._serialize_write_real_statements_templ = self._create_template('serialize-write-real-statements.j2')
         self._serialize_write_string_statements_templ = self._create_template('serialize-write-string-statements.j2')
         self._serialize_write_struct_statements_templ = self._create_template('serialize-write-struct-statements.j2')
+        self._serialize_write_static_array_statements_templ = self._create_template('serialize-write-static-array-statements.j2')
         self._serialize_write_magic_statements_templ = self._create_template('serialize-write-magic-statements.j2')
         self._serialize_write_uuid_statements_templ = self._create_template('serialize-write-uuid-statements.j2')
         self._serialize_write_stream_type_id_statements_templ = self._create_template('serialize-write-stream-type-id-statements.j2')
@@ -471,6 +515,7 @@ class _CodeGen:
         self._size_write_bit_array_statements_templ = self._create_template('size-write-bit-array-statements.j2')
         self._size_write_string_statements_templ = self._create_template('size-write-string-statements.j2')
         self._size_write_struct_statements_templ = self._create_template('size-write-struct-statements.j2')
+        self._size_write_static_array_statements_templ = self._create_template('size-write-static-array-statements.j2')
 
     # Creates and returns a template named `name` which is a file
     # template if `is_file_template` is `True`.
@@ -502,6 +547,18 @@ class _CodeGen:
     def _trace_type(self) -> barectf_config.TraceType:
         return self._cfg.trace.type
 
+    # Returns the name of a source variable for the operation `op`.
+    def _op_src_var_name(self, op: _LeafOp) -> str:
+        s = ''
+
+        for index, name in enumerate(op.names):
+            if index > 0 and not name.startswith('['):
+                s += '_'
+
+            s += name
+
+        return s
+
     # Returns the C type for the field type `ft`, making it `const` if
     # `is_const` is `True`.
     def _ft_c_type(self, ft: barectf_config._FieldType, is_const: bool = False):
@@ -531,9 +588,12 @@ class _CodeGen:
                 s = 'uint64_t'
 
             return _ArithCType(s, is_const)
-        else:
-            assert type(ft) is barectf_config.StringFieldType
+        elif type(ft) is barectf_config.StringFieldType:
             return _PointerCType(_ArithCType('char', True), is_const)
+        else:
+            assert type(ft) is barectf_config.StaticArrayFieldType
+            ft = typing.cast(barectf_config.StaticArrayFieldType, ft)
+            return _PointerCType(self._ft_c_type(ft.element_field_type, True), is_const)
 
     # Returns the function prototype parameters for the members of the
     # root structure field type `root_ft`.
index ad21cc836d055ae9c09ed95c3da0d06b6299909e..04cb23c445d00e94dc5a834d60d14105460f706c 100644 (file)
@@ -235,9 +235,9 @@ class _Parser(barectf_config_parse_common._Parser):
             ft_node = member_node[ft_prop_name]
 
             try:
-                if ft_node['class'] in ['structure', 'static-array']:
+                if ft_node['class'] in ['structure']:
                     raise _ConfigurationParseError(f'`{ft_prop_name}` property',
-                                                   'Nested structure and static array field types are not supported')
+                                                   'Nested structure field types are not supported')
 
                 try:
                     member_ft = self._create_ft(ft_node)
index 4c1a2c03606e3410b1460c1c1a17be46ce3bfc7b..7ad777a11d18882c16349ee4968210ce1d072002 100644 (file)
@@ -330,7 +330,7 @@ end:
                {% set op = stream_op_pkt_ctx_op(stream_type, name) %}
 
        /* Go back to `timestamp_end` field offset */
-       ctx->at = sctx->off_{{ c_common.op_src(op) }};
+       ctx->at = sctx->off_{{ op | op_src_var_name }};
 
                {% set src = 'ts' %}
                {% filter indent_tab(indent_first=true) %}
@@ -343,7 +343,7 @@ end:
                {% set op = stream_op_pkt_ctx_op(stream_type, name) %}
 
        /* Go back to `content_size` field offset */
-       ctx->at = sctx->off_{{ c_common.op_src(op) }};
+       ctx->at = sctx->off_{{ op | op_src_var_name }};
 
                {% set src %}ctx->{{ name }}{% endset %}
                {% filter indent_tab(indent_first=true) %}
@@ -356,7 +356,7 @@ end:
                {% set op = stream_op_pkt_ctx_op(stream_type, name) %}
 
        /* Go back to `events_discarded` field offset */
-       ctx->at = sctx->off_{{ c_common.op_src(op) }};
+       ctx->at = sctx->off_{{ op | op_src_var_name }};
 
                {% set src %}ctx->{{ name }}{% endset %}
                {% filter indent_tab(indent_first=true) %}
index 96e8dfb0e4e7fe53a6ecd1d0d3cf9f03c94555b7..72d47c8070308762a7d70a4483a1c411b74aa1b8 100644 (file)
 {{ common.prefix }}{{ stream_type.name }}_trace_{{ ev_type.name }}
 {%- endmacro %}
 
-{#
- # Generates the name of a source variable from the names of the
- # operation `op`.
- #
- # Example:
- #
- #     p_msg_id
- #}
-{% macro op_src(op) %}
-{{ op.names | join('_') }}
-{%- endmacro %}
-
 {#
  # Generates:
  #
index e163143c33b9ff674f91301db87786b7645f6591..38e43d91c5f611dd00a11c3e50e1af6a749ed852 100644 (file)
@@ -24,7 +24,7 @@
  #}
 {% import 'c/common.j2' as c_common %}
 {% set c_type = op.ft | ft_c_type %}
-{% set src = c_common.op_src(op) %}
+{% set src = op | op_src_var_name %}
 {% include 'c/serialize-write-statements-comment.j2' %}
 
 {% include 'c/serialize-write-bit-array-statements.j2' %}
index ba4cd363ca6e886fb3b2de994939dadcadbaf576..cd0f8551593b53678cfbf8d7bf128f09a7a3dea6 100644 (file)
@@ -31,7 +31,7 @@
 {
        union _{{ union_name }} {{ union_name }};
 
-       {{ union_nameĀ }}.f = {{ c_common.op_src(op) }};
+       {{ union_nameĀ }}.f = {{ op | op_src_var_name }};
 {% filter indent_tab(indent_first=true) %}
        {% include 'c/serialize-write-bit-array-statements.j2' %}
 
index 1cb6de8ef8b0e64745588a0e15818b1e72e55f8e..755b2eb07112361cb04b96fd8d4b4960373a957f 100644 (file)
@@ -24,5 +24,5 @@
  #}
 {% import 'c/common.j2' as c_common %}
 /* Do not write `{{ op.top_name }}` field; save its offset */
-sctx->off_{{ c_common.op_src(op) }} = ctx->at;
+sctx->off_{{ op | op_src_var_name }} = ctx->at;
 ctx->at += {{ op.ft.size }};
diff --git a/barectf/templates/c/serialize-write-static-array-statements.j2 b/barectf/templates/c/serialize-write-static-array-statements.j2
new file mode 100644 (file)
index 0000000..6d818e5
--- /dev/null
@@ -0,0 +1,37 @@
+{#
+ # The MIT License (MIT)
+ #
+ # Copyright (c) 2020 Philippe Proulx <pproulx@efficios.com>
+ #
+ # Permission is hereby granted, free of charge, to any person obtaining
+ # a copy of this software and associated documentation files (the
+ # "Software"), to deal in the Software without restriction, including
+ # without limitation the rights to use, copy, modify, merge, publish,
+ # distribute, sublicense, and/or sell copies of the Software, and to
+ # permit persons to whom the Software is furnished to do so, subject to
+ # the following conditions:
+ #
+ # The above copyright notice and this permission notice shall be
+ # included in all copies or substantial portions of the Software.
+ #
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ #}
+{% import 'c/common.j2' as c_common %}
+{% set var_name = op.level | loop_var_name %}
+{% include 'c/serialize-write-statements-comment.j2' %}
+
+{
+       uint32_t {{ var_name }};
+
+       for ({{ var_name }} = 0; {{ var_name }} < (uint32_t) {{ op.ft.length }}; ++{{ var_name }}) {
+{% for subop in op.subops %}
+               {{ subop.serialize_str(stream_type=stream_type, ev_type=ev_type) | indent_tab(2) }}
+{% endfor %}
+       }
+}
index 03c76a216514827a50ecbcea4745a225a72cb4be..4331dbfed4131ce946d31006ddf731db0ee01a54 100644 (file)
@@ -25,4 +25,4 @@
 {% import 'c/common.j2' as c_common %}
 {% include 'c/serialize-write-statements-comment.j2' %}
 
-_write_c_str(ctx, {{ c_common.op_src(op) }});
+_write_c_str(ctx, {{ op | op_src_var_name }});
diff --git a/barectf/templates/c/size-write-static-array-statements.j2 b/barectf/templates/c/size-write-static-array-statements.j2
new file mode 100644 (file)
index 0000000..4049589
--- /dev/null
@@ -0,0 +1,37 @@
+{#
+ # The MIT License (MIT)
+ #
+ # Copyright (c) 2020 Philippe Proulx <pproulx@efficios.com>
+ #
+ # Permission is hereby granted, free of charge, to any person obtaining
+ # a copy of this software and associated documentation files (the
+ # "Software"), to deal in the Software without restriction, including
+ # without limitation the rights to use, copy, modify, merge, publish,
+ # distribute, sublicense, and/or sell copies of the Software, and to
+ # permit persons to whom the Software is furnished to do so, subject to
+ # the following conditions:
+ #
+ # The above copyright notice and this permission notice shall be
+ # included in all copies or substantial portions of the Software.
+ #
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ #}
+{% import 'c/common.j2' as c_common %}
+{% set var_name = op.level | loop_var_name %}
+{% include 'c/serialize-write-statements-comment.j2' %}
+
+{
+       uint32_t {{ var_name }};
+
+       for ({{ var_name }} = 0; {{ var_name }} < (uint32_t) {{ op.ft.length }}; ++{{ var_name }}) {
+{% for subop in op.subops %}
+               {{ subop.size_str(stream_type=stream_type, ev_type=ev_type) | indent_tab(2) }}
+{% endfor %}
+       }
+}
index 155438d25af81cb0f2766d30c0fc5eff945fa396..7aefd330d5282309b60b9cca59b1839e3745a792 100644 (file)
@@ -24,4 +24,4 @@
  #}
 {% import 'c/common.j2' as c_common %}
 /* Add `{{ op.top_name }}` string field's size */
-at += _BYTES_TO_BITS(strlen({{ c_common.op_src(op) }}) + 1);
+at += _BYTES_TO_BITS(strlen({{ op | op_src_var_name }}) + 1);
This page took 0.032126 seconds and 4 git commands to generate.