From be9f12dcc7161c727df017eb2631eef85f471485 Mon Sep 17 00:00:00 2001 From: Philippe Proulx Date: Wed, 9 Sep 2020 16:50:51 -0400 Subject: [PATCH] Add user dynamic array field support This patch adds support for user dynamic array fields. The element field type of a user dynamic array field type can be any of the following: * Bit array field type. * String field type. * Static array field type. Note that it _cannot_ be another dynamic array field type. The new `barectf.DynamicArrayFieldType` represents a dynamic array field type. Its constructor accepts a length field type. As of this version, this length field type must be within the same immediate structure field type and before it. The YAML ways to specify a dynamic array field type are: barectf 2 configuration: class: array length: dynamic element-type: ... barectf 3 configuration: class: dynamic-array element-field-type: ... Note that in YAML, you don't specify the length field type: the parser automatically creates a 32-bit, byte-aligned unsigned integer field type before which, for a dynamic array field type named `a`, has the name `__a_len`. This also becomes part of the corresponding tracing function parameter's name. In the future, I can add a `length-field-type` property to a barectf 3 YAML dynamic array field type to point to an anterior unsigned integer field type using some kind of reference, for example: class: structure members: - my_length: uint16 - my_array: field-type: class: dynamic-array length-field-type-name: my_length element-field-type: ... This would make it possible for more than one dynamic array fields to use the same length field, for example: class: structure members: - my_length: uint16 - my_uint_array: field-type: class: dynamic-array length-field-type-name: my_length element-field-type: uint8 - my_string_array: field-type: class: dynamic-array length-field-type-name: my_length element-field-type: string The constructor of `barectf.StructureFieldType` calls _set_dyn_array_ft_length_ft_member_names() which, for each member having a dynamic array field type: * Sets its `_length_ft_member_name` attribute to the name of the structure field type member having its length field type. * Sets its length field type's `_is_len` attribute to `True`. I consider those as hacks, but considering the current constraints, it makes parts of the (barectf) code easier to implement and maintain. The C code generation approach is similar to the static array field case. The `*-write-static-array-statements.j2` templates are renamed to `*-write-array-statements.j2` and use the `length_src` variable as the loop's length's value. `*-write-static-array-statements.j2` and the new `*-write-dynamic-array-statements.j2` set `length_src` before including `*-write-array-statements.j2`. To make things easier, barectf now systematically generates alignment statements if the alignment is greater than one. This could be optimized again in the future, considering arrays this time. The `_WriteOp.offset_in_byte` optimization still exists, although as soon as it's not statically known, it's now `None` and `serialize-write-bit-array-statements.j2` uses the safe, dynamic `ctx->at % 8` expression. try_create_align_op() does this, more or less: If `self._offset_in_byte` is not currently known and the requested alignment is 8: Set `self._offset_in_byte` to 0. Else: If we're currently within an array operation: Reset `self._offset_in_byte`. Else: If `self._offset_in_byte` is currently known: Align `self._offset_in_byte` with the requested alignment. This ensures that each array field's element is aligned before being written. Signed-off-by: Philippe Proulx --- barectf/__init__.py | 1 + barectf/cgen.py | 130 ++++++++++-------- barectf/config.py | 30 ++++ barectf/config_parse_v2.py | 17 ++- barectf/config_parse_v3.py | 108 ++++++++++----- barectf/schemas/config/2/config.yaml | 2 +- barectf/schemas/config/2/field-type.yaml | 19 ++- barectf/schemas/config/3/field-type.yaml | 37 ++++- barectf/templates/c/barectf.c-macros.j2 | 2 +- barectf/templates/c/common.j2 | 8 ++ .../c/serialize-write-array-statements.j2 | 38 +++++ .../c/serialize-write-bit-array-statements.j2 | 3 +- ...erialize-write-dynamic-array-statements.j2 | 27 ++++ ...serialize-write-static-array-statements.j2 | 15 +- .../c/size-write-array-statements.j2 | 38 +++++ .../c/size-write-dynamic-array-statements.j2 | 27 ++++ .../c/size-write-static-array-statements.j2 | 15 +- barectf/templates/metadata/struct-ft.j2 | 9 +- barectf/tsdl182gen.py | 31 +++-- 19 files changed, 397 insertions(+), 160 deletions(-) create mode 100644 barectf/templates/c/serialize-write-array-statements.j2 create mode 100644 barectf/templates/c/serialize-write-dynamic-array-statements.j2 create mode 100644 barectf/templates/c/size-write-array-statements.j2 create mode 100644 barectf/templates/c/size-write-dynamic-array-statements.j2 diff --git a/barectf/__init__.py b/barectf/__init__.py index b33f04e..b47d71c 100644 --- a/barectf/__init__.py +++ b/barectf/__init__.py @@ -61,6 +61,7 @@ ConfigurationCodeGenerationOptions = barectf_config.ConfigurationCodeGenerationO ConfigurationOptions = barectf_config.ConfigurationOptions DEFAULT_FIELD_TYPE = barectf_config.DEFAULT_FIELD_TYPE DisplayBase = barectf_config.DisplayBase +DynamicArrayFieldType = barectf_config.DynamicArrayFieldType EnumerationFieldTypeMapping = barectf_config.EnumerationFieldTypeMapping EnumerationFieldTypeMappingRange = barectf_config.EnumerationFieldTypeMappingRange EnumerationFieldTypeMappings = barectf_config.EnumerationFieldTypeMappings diff --git a/barectf/cgen.py b/barectf/cgen.py index f634b12..1b0b124 100644 --- a/barectf/cgen.py +++ b/barectf/cgen.py @@ -106,22 +106,14 @@ class _CompoundOp(_Op): # Leaf operation (abstract class). class _LeafOp(_Op): - def __init__(self, offset_in_byte: Count, ft: barectf_config._FieldType, names: List[str], - 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 - - @property - def offset_in_byte(self) -> Count: - return self._offset_in_byte + pass # An "align" operation. class _AlignOp(_LeafOp): - def __init__(self, offset_in_byte: Count, ft: barectf_config._FieldType, names: List[str], - level: Count, templates: _OpTemplates, value: Alignment): - super().__init__(offset_in_byte, ft, names, level, templates) + def __init__(self, ft: barectf_config._FieldType, names: List[str], level: Count, + templates: _OpTemplates, value: Alignment): + super().__init__(ft, names, level, templates) self._value = value @property @@ -131,7 +123,15 @@ class _AlignOp(_LeafOp): # A "write" operation. class _WriteOp(_LeafOp): - pass + def __init__(self, ft: barectf_config._FieldType, names: List[str], level: Count, + templates: _OpTemplates, offset_in_byte: Optional[Count]): + super().__init__(ft, names, level, templates) + assert offset_in_byte is None or (offset_in_byte >= 0 and offset_in_byte < 8) + self._offset_in_byte = offset_in_byte + + @property + def offset_in_byte(self) -> Optional[Count]: + return self._offset_in_byte _SpecSerializeWriteTemplates = Mapping[str, barectf_template._Template] @@ -147,13 +147,16 @@ _SpecSerializeWriteTemplates = Mapping[str, barectf_template._Template] # and return it. class _OpBuilder: def __init__(self, cg: '_CodeGen'): - 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._offset_in_byte: Optional[Count] = None self._cg = cg + # Whether or not we're within an array operation. + @property + def _in_array(self): + return self._level > 0 + # Creates and returns an operation for the root structure field type # `ft` named `name`. # @@ -192,7 +195,7 @@ class _OpBuilder: assert type(ft) is not barectf_config.StructureFieldType offset_in_byte = self._offset_in_byte - if isinstance(ft, barectf_config._BitArrayFieldType): + if isinstance(ft, barectf_config._BitArrayFieldType) and self._offset_in_byte is not None: self._offset_in_byte = Count((self._offset_in_byte + ft.size) % 8) serialize_write_templ: Optional[barectf_template._Template] = None @@ -216,35 +219,33 @@ 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, self._level, - _OpTemplates(serialize_write_templ, size_write_templ)) + return _WriteOp(ft, self._names, self._level, + _OpTemplates(serialize_write_templ, size_write_templ), offset_in_byte) # Creates and returns an "align" operation for the field type # `ft` if needed. # # This function updates the builder's state. - def try_create_align_op(alignment: Alignment, do_align: bool, - ft: barectf_config._FieldType) -> Optional[_AlignOp]: + def try_create_align_op(alignment: Alignment, ft: barectf_config._FieldType) -> Optional[_AlignOp]: def align(v: Count, alignment: Alignment) -> Count: return Count((v + (alignment - 1)) & -alignment) - offset_in_byte = self._offset_in_byte - self._offset_in_byte = Count(align(self._offset_in_byte, alignment) % 8) + if self._offset_in_byte is None and alignment % 8 == 0: + self._offset_in_byte = Count(0) + else: + if self._in_array: + self._offset_in_byte = None + elif self._offset_in_byte is not None: + 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, self._level, + if alignment > 1: + return _AlignOp(ft, self._names, self._level, _OpTemplates(self._cg._serialize_align_statements_templ, self._cg._size_align_statements_templ), alignment) return None - # Returns whether or not, considering the alignment requirement - # `align_req` and the builder's current state, we must create - # and append an "align" operation. - 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)) @@ -255,39 +256,32 @@ class _OpBuilder: # operations to return ops: List[_Op] = [] - is_uuid_ft = type(ft) is barectf_config.StaticArrayFieldType and self._names == [_RootFtPrefixes.PH, 'uuid'] - - if type(ft) is barectf_config.StringFieldType or is_uuid_ft: + if type(ft) is barectf_config.StringFieldType or self._names == [_RootFtPrefixes.PH, 'uuid']: # 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) - op = try_create_align_op(Alignment(8), do_align, ft) + op = try_create_align_op(Alignment(8), ft) if op is not None: ops.append(op) ops.append(create_write_op(ft)) else: - do_align = must_align(ft.alignment) - self._last_alignment = ft.alignment - if ft_is_compound(ft): - # reset last bit array size - self._last_bit_array_size = typing.cast(Count, ft.alignment) - else: - assert isinstance(ft, barectf_config._BitArrayFieldType) - ft = typing.cast(barectf_config._BitArrayFieldType, ft) - self._last_bit_array_size = ft.size + self._offset_in_byte = None - init_align_op = try_create_align_op(ft.alignment, do_align, ft) + init_align_op = try_create_align_op(ft.alignment, ft) subops: List[_Op] = [] if type(ft) is barectf_config.StructureFieldType: ft = typing.cast(barectf_config.StructureFieldType, ft) if init_align_op is not None: - # append structure field's alignment as a suboperation + # Append structure field's alignment as a + # suboperation. + # + # This is not strictly needed (could be appended to + # `ops`), but the properties of `_StreamOps` and + # `_EvOps` offer a single (structure field type) + # operation. subops.append(init_align_op) # append suboperations for each member @@ -300,12 +294,12 @@ class _OpBuilder: _OpTemplates(self._cg._serialize_write_struct_statements_templ, self._cg._size_write_struct_statements_templ), subops)) - elif type(ft) is barectf_config.StaticArrayFieldType: - ft = typing.cast(barectf_config.StaticArrayFieldType, ft) + elif isinstance(ft, barectf_config._ArrayFieldType): + ft = typing.cast(barectf_config._ArrayFieldType, ft) + assert ft.alignment == 1 or init_align_op is not None if init_align_op is not None: - # append static array field's alignment as a suboperation - subops.append(init_align_op) + ops.append(init_align_op) # append element's suboperations self._level = Count(self._level + 1) @@ -314,11 +308,17 @@ class _OpBuilder: 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)) + # select the right templates + if type(ft) is barectf_config.StaticArrayFieldType: + templates = _OpTemplates(self._cg._serialize_write_static_array_statements_templ, + self._cg._size_write_static_array_statements_templ) + else: + assert type(ft) is barectf_config.DynamicArrayFieldType + templates = _OpTemplates(self._cg._serialize_write_dynamic_array_statements_templ, + self._cg._size_write_dynamic_array_statements_templ) + + # create array field's compound operation + ops.append(_CompoundOp(ft, self._names, self._level, templates, subops)) else: # leaf field: align + write if init_align_op is not None: @@ -504,6 +504,7 @@ class _CodeGen: 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_dynamic_array_statements_templ = self._create_template('serialize-write-dynamic-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') @@ -516,6 +517,7 @@ class _CodeGen: 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') + self._size_write_dynamic_array_statements_templ = self._create_template('size-write-dynamic-array-statements.j2') # Creates and returns a template named `name` which is a file # template if `is_file_template` is `True`. @@ -591,8 +593,8 @@ class _CodeGen: 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) + assert isinstance(ft, barectf_config._ArrayFieldType) + ft = typing.cast(barectf_config._ArrayFieldType, ft) return _PointerCType(self._ft_c_type(ft.element_field_type, True), is_const) # Returns the function prototype parameters for the members of the @@ -616,7 +618,13 @@ class _CodeGen: if member_name in exclude_set: continue - if only_dyn and not member.field_type.size_is_dynamic: + is_dyn = member.field_type.size_is_dynamic + + if isinstance(member.field_type, barectf_config.UnsignedIntegerFieldType): + ft = typing.cast(barectf_config.UnsignedIntegerFieldType, member.field_type) + is_dyn = is_dyn or ft._is_len + + if only_dyn and not is_dyn: continue params.append(_FtParam(member.field_type, member_name)) diff --git a/barectf/config.py b/barectf/config.py index 0d6417c..30fdf5a 100644 --- a/barectf/config.py +++ b/barectf/config.py @@ -222,6 +222,21 @@ class StaticArrayFieldType(_ArrayFieldType): return self._length +class DynamicArrayFieldType(_ArrayFieldType): + def __init__(self, length_field_type: UnsignedIntegerFieldType, element_field_type: _FieldType): + super().__init__(element_field_type) + self._length_field_type = length_field_type + self._length_ft_member_name = None + + @property + def length_field_type(self): + return self._length_field_type + + @property + def size_is_dynamic(self): + return True + + class StructureFieldTypeMember: def __init__(self, field_type: _FieldType): self._field_type = field_type @@ -262,6 +277,7 @@ class StructureFieldType(_FieldType): self._members = StructureFieldTypeMembers(members) self._set_alignment() + self._set_dyn_array_ft_length_ft_member_names() def _set_alignment(self): self._alignment: Alignment = self._minimum_alignment @@ -270,6 +286,20 @@ class StructureFieldType(_FieldType): if member.field_type.alignment > self._alignment: self._alignment = member.field_type.alignment + def _set_dyn_array_ft_length_ft_member_names(self): + for member in self._members.values(): + if type(member.field_type) is DynamicArrayFieldType: + # Find length field type member name within the same + # structure field type members. + for len_name, len_member in self._members.items(): + if member.field_type.length_field_type is len_member.field_type: + member.field_type._length_ft_member_name = len_name + len_member.field_type._is_len = True + break + + if member.field_type.alignment > self._alignment: + self._alignment = member.field_type.alignment + @property def minimum_alignment(self) -> Alignment: return self._minimum_alignment diff --git a/barectf/config_parse_v2.py b/barectf/config_parse_v2.py index 59f9733..668f0b9 100644 --- a/barectf/config_parse_v2.py +++ b/barectf/config_parse_v2.py @@ -80,7 +80,7 @@ class _Parser(config_parse_common._Parser): 'floating-point': self._conv_real_ft_node, 'str': self._conv_string_ft_node, 'string': self._conv_string_ft_node, - 'array': self._conv_static_array_ft_node, + 'array': self._conv_array_ft_node, 'struct': self._conv_struct_ft_node, 'structure': self._conv_struct_ft_node, } @@ -222,12 +222,15 @@ class _Parser(config_parse_common._Parser): # Converts a v2 array field type node to a v3 (static) array field # type node and returns it. - def _conv_static_array_ft_node(self, v2_ft_node: _MapNode) -> _MapNode: - # class renamed to `static-array` - v3_ft_node: _MapNode = collections.OrderedDict({'class': 'static-array'}) - - # copy `length` property - _copy_prop_if_exists(v3_ft_node, v2_ft_node, 'length') + def _conv_array_ft_node(self, v2_ft_node: _MapNode) -> _MapNode: + # class renamed to `static-array` or `dynamic-array` + is_dynamic = v2_ft_node['length'] == 'dynamic' + array_type = 'dynamic' if is_dynamic else 'static' + v3_ft_node: _MapNode = collections.OrderedDict({'class': f'{array_type}-array'}) + + # copy `length` property if it's a static array field type + if not is_dynamic: + _copy_prop_if_exists(v3_ft_node, v2_ft_node, 'length') # convert element field type v3_ft_node['element-field-type'] = self._conv_ft_node(v2_ft_node['element-type']) diff --git a/barectf/config_parse_v3.py b/barectf/config_parse_v3.py index 04cb23c..9920444 100644 --- a/barectf/config_parse_v3.py +++ b/barectf/config_parse_v3.py @@ -26,7 +26,7 @@ from barectf.config_parse_common import _ConfigurationParseError from barectf.config_parse_common import _append_error_ctx from barectf.config_parse_common import _MapNode import barectf.config as barectf_config -from barectf.config import _OptFt, _OptStructFt +from barectf.config import _OptStructFt import collections import uuid from barectf.typing import Count, Alignment, VersionNumber @@ -52,7 +52,8 @@ class _Parser(barectf_config_parse_common._Parser): ignore_include_not_found: bool): super().__init__(root_file, node, with_pkg_include_dir, inclusion_dirs, ignore_include_not_found, VersionNumber(3)) - self._ft_cls_name_to_create_method: Dict[str, Callable[[_MapNode], barectf_config._FieldType]] = { + self._ft_cls_name_to_create_method: Dict[str, Callable[[_MapNode], + List[barectf_config._FieldType]]] = { 'unsigned-integer': self._create_int_ft, 'signed-integer': self._create_int_ft, 'unsigned-enumeration': self._create_enum_ft, @@ -60,6 +61,7 @@ class _Parser(barectf_config_parse_common._Parser): 'real': self._create_real_ft, 'string': self._create_string_ft, 'static-array': self._create_static_array_ft, + 'dynamic-array': self._create_dynamic_array_ft, 'structure': self._create_struct_ft, } self._parse() @@ -155,16 +157,16 @@ class _Parser(barectf_config_parse_common._Parser): # Creates an integer field type from the unsigned/signed integer # field type node `ft_node`. - def _create_int_ft(self, ft_node: _MapNode) -> barectf_config._IntegerFieldType: + def _create_int_ft(self, ft_node: _MapNode) -> List[barectf_config._FieldType]: ft_type = { 'unsigned-integer': barectf_config.UnsignedIntegerFieldType, 'signed-integer': barectf_config.SignedIntegerFieldType, }[ft_node['class']] - return self._create_common_int_ft(ft_node, ft_type) + return [self._create_common_int_ft(ft_node, ft_type)] # Creates an enumeration field type from the unsigned/signed # enumeration field type node `ft_node`. - def _create_enum_ft(self, ft_node: _MapNode) -> barectf_config._EnumerationFieldType: + def _create_enum_ft(self, ft_node: _MapNode) -> List[barectf_config._FieldType]: ft_type = { 'unsigned-enumeration': barectf_config.UnsignedEnumerationFieldType, 'signed-enumeration': barectf_config.SignedEnumerationFieldType, @@ -185,32 +187,54 @@ class _Parser(barectf_config_parse_common._Parser): mappings[label] = barectf_config.EnumerationFieldTypeMapping(ranges) - return typing.cast(barectf_config._EnumerationFieldType, - self._create_common_int_ft(ft_node, ft_type, - barectf_config.EnumerationFieldTypeMappings(mappings))) + return [typing.cast(barectf_config._EnumerationFieldType, + self._create_common_int_ft(ft_node, ft_type, + barectf_config.EnumerationFieldTypeMappings(mappings)))] # Creates a real field type from the real field type node `ft_node`. - def _create_real_ft(self, ft_node: _MapNode) -> barectf_config.RealFieldType: - return typing.cast(barectf_config.RealFieldType, - self._create_common_bit_array_ft(ft_node, barectf_config.RealFieldType, - Alignment(8))) + def _create_real_ft(self, ft_node: _MapNode) -> List[barectf_config._FieldType]: + return [typing.cast(barectf_config.RealFieldType, + self._create_common_bit_array_ft(ft_node, barectf_config.RealFieldType, + Alignment(8)))] # Creates a string field type from the string field type node # `ft_node`. - def _create_string_ft(self, ft_node: _MapNode) -> barectf_config.StringFieldType: - return barectf_config.StringFieldType() + def _create_string_ft(self, ft_node: _MapNode) -> List[barectf_config._FieldType]: + return [barectf_config.StringFieldType()] - # Creates a static array field type from the static array field type - # node `ft_node`. - def _create_static_array_ft(self, ft_node: _MapNode) -> barectf_config.StaticArrayFieldType: + def _create_array_ft(self, ft_type, ft_node: _MapNode, **kwargs) -> barectf_config._ArrayFieldType: prop_name = 'element-field-type' try: - element_ft = self._create_ft(ft_node[prop_name]) + element_fts = self._create_fts(ft_node[prop_name]) except _ConfigurationParseError as exc: _append_error_ctx(exc, f'`{prop_name}` property') - return barectf_config.StaticArrayFieldType(ft_node['length'], element_ft) + if len(element_fts) != 1 or isinstance(element_fts[0], (barectf_config.StructureFieldType, + barectf_config.DynamicArrayFieldType)): + raise _ConfigurationParseError(f'`{prop_name}` property', + 'Nested structure and dynamic array field types are not supported') + + return ft_type(element_field_type=element_fts[0], **kwargs) + + # Creates a static array field type from the static array field type + # node `ft_node`. + def _create_static_array_ft(self, ft_node: _MapNode) -> List[barectf_config._FieldType]: + return [typing.cast(barectf_config.StaticArrayFieldType, + self._create_array_ft(barectf_config.StaticArrayFieldType, ft_node, + length=ft_node['length']))] + + # Creates a dynamic array field type from the dynamic array field + # type node `ft_node`. + def _create_dynamic_array_ft(self, ft_node: _MapNode) -> List[barectf_config._FieldType]: + # create length unsigned integer field type + len_ft = barectf_config.UnsignedIntegerFieldType(32, alignment=Alignment(8)) + return [ + len_ft, + typing.cast(barectf_config.DynamicArrayFieldType, + self._create_array_ft(barectf_config.DynamicArrayFieldType, ft_node, + length_field_type=len_ft)) + ] # Creates structure field type members from the structure field type # members node `members_node`. @@ -240,19 +264,29 @@ class _Parser(barectf_config_parse_common._Parser): 'Nested structure field types are not supported') try: - member_ft = self._create_ft(ft_node) + member_fts = self._create_fts(ft_node) except _ConfigurationParseError as exc: - exc._append_ctx(f'`{ft_prop_name}` property') + _append_error_ctx(exc, f'`{ft_prop_name}` property') except _ConfigurationParseError as exc: _append_error_ctx(exc, f'Structure field type member `{member_name}`') - members[member_name] = barectf_config.StructureFieldTypeMember(member_ft) + if len(member_fts) == 2: + # The only case where this happens is a dynamic array + # field type node which generates an unsigned integer + # field type for the length and the dynamic array field + # type itself. + assert type(member_fts[1]) is barectf_config.DynamicArrayFieldType + members[f'__{member_name}_len'] = barectf_config.StructureFieldTypeMember(member_fts[0]) + else: + assert len(member_fts) == 1 + + members[member_name] = barectf_config.StructureFieldTypeMember(member_fts[-1]) return barectf_config.StructureFieldTypeMembers(members) # Creates a structure field type from the structure field type node # `ft_node`. - def _create_struct_ft(self, ft_node: _MapNode) -> barectf_config.StructureFieldType: + def _create_struct_ft(self, ft_node: _MapNode) -> List[barectf_config._FieldType]: minimum_alignment = self._alignment_prop(ft_node, 'minimum-alignment') if minimum_alignment is None: @@ -265,32 +299,36 @@ class _Parser(barectf_config_parse_common._Parser): if members_node is not None: members = self._create_struct_ft_members(members_node, prop_name) - return barectf_config.StructureFieldType(minimum_alignment, members) + return [barectf_config.StructureFieldType(minimum_alignment, members)] - # Creates a field type from the field type node `ft_node`. - def _create_ft(self, ft_node: _MapNode) -> barectf_config._FieldType: + # Creates field types from the field type node `ft_node`. + def _create_fts(self, ft_node: _MapNode) -> List[barectf_config._FieldType]: return self._ft_cls_name_to_create_method[ft_node['class']](ft_node) - # Creates a field type from the field type node `parent_node[key]` + # Creates field types from the field type node `parent_node[key]` # if it exists. - def _try_create_ft(self, parent_node: _MapNode, key: str) -> _OptFt: + def _try_create_fts(self, parent_node: _MapNode, key: str) -> Optional[List[barectf_config._FieldType]]: if key not in parent_node: return None try: - return self._create_ft(parent_node[key]) + return self._create_fts(parent_node[key]) except _ConfigurationParseError as exc: _append_error_ctx(exc, f'`{key}` property') # satisfy static type checker (never reached) raise - # Like _try_create_ft(), but casts the result's type to - # `barectf_config.StructureFieldType` to satisfy static type - # checkers. + # Like _try_create_fts(), but casts the result's type (first and + # only element) to `barectf_config.StructureFieldType` to satisfy + # static type checkers. def _try_create_struct_ft(self, parent_node: _MapNode, key: str) -> _OptStructFt: - return typing.cast(barectf_config.StructureFieldType, - self._try_create_ft(parent_node, key)) + fts = self._try_create_fts(parent_node, key) + + if fts is None: + return None + + return typing.cast(barectf_config.StructureFieldType, fts[0]) # Returns the total number of members in the structure field type # node `ft_node` if it exists, otherwise 0. @@ -372,7 +410,7 @@ class _Parser(barectf_config_parse_common._Parser): return None assert type(ft_node) is collections.OrderedDict - return self._create_ft(ft_node) + return self._create_fts(ft_node)[0] def _create_stream_type(self, name: str, stream_type_node: _MapNode) -> barectf_config.StreamType: try: diff --git a/barectf/schemas/config/2/config.yaml b/barectf/schemas/config/2/config.yaml index 55f8dc0..e7522b6 100644 --- a/barectf/schemas/config/2/config.yaml +++ b/barectf/schemas/config/2/config.yaml @@ -66,7 +66,7 @@ definitions: const: 32 uuid: allOf: - - $ref: https://barectf.org/schemas/config/2/field-type.json#/definitions/static-array-ft + - $ref: https://barectf.org/schemas/config/2/field-type.json#/definitions/array-ft - properties: length: const: 16 diff --git a/barectf/schemas/config/2/field-type.yaml b/barectf/schemas/config/2/field-type.yaml index d34e558..2df2885 100644 --- a/barectf/schemas/config/2/field-type.yaml +++ b/barectf/schemas/config/2/field-type.yaml @@ -198,20 +198,25 @@ definitions: required: - class additionalProperties: false - static-array-ft-class-prop: + array-ft-class-prop: type: string const: array - static-array-ft: + array-ft: title: Static array field type object type: object properties: class: - $ref: '#/definitions/static-array-ft-class-prop' + $ref: '#/definitions/array-ft-class-prop' element-type: $ref: '#/definitions/ft' length: - type: integer - minimum: 0 + if: + type: integer + then: + minimum: 0 + else: + type: string + const: dynamic required: - class - element-type @@ -298,9 +303,9 @@ definitions: - if: properties: class: - $ref: '#/definitions/static-array-ft-class-prop' + $ref: '#/definitions/array-ft-class-prop' then: - $ref: '#/definitions/static-array-ft' + $ref: '#/definitions/array-ft' - if: properties: class: diff --git a/barectf/schemas/config/3/field-type.yaml b/barectf/schemas/config/3/field-type.yaml index 4ab690d..92c5502 100644 --- a/barectf/schemas/config/3/field-type.yaml +++ b/barectf/schemas/config/3/field-type.yaml @@ -180,29 +180,47 @@ definitions: properties: class: true additionalProperties: false + array-ft: + title: Array field type object + allOf: + - $ref: '#/definitions/ft-base' + - properties: + element-field-type: + $ref: '#/definitions/ft' + required: + - element-field-type static-array-ft-class-prop: type: string const: static-array static-array-ft: title: Static array field type object allOf: - - $ref: '#/definitions/ft-base' + - $ref: '#/definitions/array-ft' - properties: class: $ref: '#/definitions/static-array-ft-class-prop' - element-field-type: - $ref: '#/definitions/ft' length: type: integer minimum: 0 - required: - - element-field-type - - length properties: class: true element-field-type: true length: true additionalProperties: false + dynamic-array-ft-class-prop: + type: string + const: dynamic-array + dynamic-array-ft-class-prop: + title: Dynamic array field type object + allOf: + - $ref: '#/definitions/array-ft' + - properties: + class: + $ref: '#/definitions/dynamic-array-ft-class-prop' + properties: + class: true + element-field-type: true + additionalProperties: false struct-ft-class-prop: type: string enum: @@ -278,6 +296,7 @@ definitions: - str - string - static-array + - dynamic-array - struct - structure - if: @@ -322,6 +341,12 @@ definitions: $ref: '#/definitions/static-array-ft-class-prop' then: $ref: '#/definitions/static-array-ft' + - if: + properties: + class: + $ref: '#/definitions/dynamic-array-ft-class-prop' + then: + $ref: '#/definitions/dynamic-array-ft' - if: properties: class: diff --git a/barectf/templates/c/barectf.c-macros.j2 b/barectf/templates/c/barectf.c-macros.j2 index 4af1956..ca92b64 100644 --- a/barectf/templates/c/barectf.c-macros.j2 +++ b/barectf/templates/c/barectf.c-macros.j2 @@ -58,7 +58,7 @@ const int saved_in_tracing_section = ctx->in_tracing_section; {% macro ft_call_params(param_prefix, ft, only_dyn=false) %} {% if ft %} {% for member_name, member in ft.members.items() %} - {% if not only_dyn or member.field_type.size_is_dynamic %} + {% if not only_dyn or member.field_type.size_is_dynamic or member.field_type._is_len %} , {{ param_prefix }}_{{ member_name }} {%- endif %} {% endfor %} diff --git a/barectf/templates/c/common.j2 b/barectf/templates/c/common.j2 index 72d47c8..c22292e 100644 --- a/barectf/templates/c/common.j2 +++ b/barectf/templates/c/common.j2 @@ -69,3 +69,11 @@ {% macro const_str(is_const) %} {{ 'const ' if is_const else '' }} {%- endmacro %} + +{# + # Generates the length variable name of the dynamic field type + # operation `op`. + #} +{% macro dyn_array_ft_op_len_var_name(op) %} +{{ op.names[0] }}_{{ op.ft._length_ft_member_name }} +{%- endmacro %} diff --git a/barectf/templates/c/serialize-write-array-statements.j2 b/barectf/templates/c/serialize-write-array-statements.j2 new file mode 100644 index 0000000..c12c040 --- /dev/null +++ b/barectf/templates/c/serialize-write-array-statements.j2 @@ -0,0 +1,38 @@ +{# + # The MIT License (MIT) + # + # Copyright (c) 2020 Philippe Proulx + # + # 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) {{ length_src }}; ++{{ var_name }}) { +{% for subop in op.subops %} + {{ subop.serialize_str(stream_type=stream_type, ev_type=ev_type) | indent_tab(2) }} + +{% endfor %} + } +} diff --git a/barectf/templates/c/serialize-write-bit-array-statements.j2 b/barectf/templates/c/serialize-write-bit-array-statements.j2 index 9a2f164..bf7105c 100644 --- a/barectf/templates/c/serialize-write-bit-array-statements.j2 +++ b/barectf/templates/c/serialize-write-bit-array-statements.j2 @@ -35,7 +35,8 @@ {%- else %} {% set bo = 'le' if cfg.target_byte_order == barectf_config.ByteOrder.LITTLE_ENDIAN else 'be' %} {% set c_type_non_const = c_type | replace('const ', '') %} -bt_bitfield_write_{{ bo }}(&ctx->buf[_BITS_TO_BYTES(ctx->at)], {{ op.offset_in_byte }}, {{ op.ft.size }}, + {% set offset_in_byte = 'ctx->at % 8' if op.offset_in_byte == none else op.offset_in_byte %} +bt_bitfield_write_{{ bo }}(&ctx->buf[_BITS_TO_BYTES(ctx->at)], {{ offset_in_byte }}, {{ op.ft.size }}, {{ c_type_non_const }}, ({{ c_type_non_const }}) {{ src }}); {{ incr_at }}; {%- endif %} diff --git a/barectf/templates/c/serialize-write-dynamic-array-statements.j2 b/barectf/templates/c/serialize-write-dynamic-array-statements.j2 new file mode 100644 index 0000000..49a9d55 --- /dev/null +++ b/barectf/templates/c/serialize-write-dynamic-array-statements.j2 @@ -0,0 +1,27 @@ +{# + # The MIT License (MIT) + # + # Copyright (c) 2020 Philippe Proulx + # + # 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 length_src = c_common.dyn_array_ft_op_len_var_name(op) %} +{% include 'c/serialize-write-array-statements.j2' %} diff --git a/barectf/templates/c/serialize-write-static-array-statements.j2 b/barectf/templates/c/serialize-write-static-array-statements.j2 index 6d818e5..7cf5051 100644 --- a/barectf/templates/c/serialize-write-static-array-statements.j2 +++ b/barectf/templates/c/serialize-write-static-array-statements.j2 @@ -22,16 +22,5 @@ # 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 %} - } -} +{% set length_src %}{{ op.ft.length }}U{% endset %} +{% include 'c/serialize-write-array-statements.j2' %} diff --git a/barectf/templates/c/size-write-array-statements.j2 b/barectf/templates/c/size-write-array-statements.j2 new file mode 100644 index 0000000..c4ba116 --- /dev/null +++ b/barectf/templates/c/size-write-array-statements.j2 @@ -0,0 +1,38 @@ +{# + # The MIT License (MIT) + # + # Copyright (c) 2020 Philippe Proulx + # + # 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) {{ length_src }}; ++{{ var_name }}) { +{% for subop in op.subops %} + {{ subop.size_str(stream_type=stream_type, ev_type=ev_type) | indent_tab(2) }} + +{% endfor %} + } +} diff --git a/barectf/templates/c/size-write-dynamic-array-statements.j2 b/barectf/templates/c/size-write-dynamic-array-statements.j2 new file mode 100644 index 0000000..96ef970 --- /dev/null +++ b/barectf/templates/c/size-write-dynamic-array-statements.j2 @@ -0,0 +1,27 @@ +{# + # The MIT License (MIT) + # + # Copyright (c) 2020 Philippe Proulx + # + # 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 length_src = c_common.dyn_array_ft_op_len_var_name(op) %} +{% include 'c/size-write-array-statements.j2' %} diff --git a/barectf/templates/c/size-write-static-array-statements.j2 b/barectf/templates/c/size-write-static-array-statements.j2 index 4049589..3b41d9e 100644 --- a/barectf/templates/c/size-write-static-array-statements.j2 +++ b/barectf/templates/c/size-write-static-array-statements.j2 @@ -22,16 +22,5 @@ # 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 %} - } -} +{% set length_src %}{{ op.ft.length }}U{% endset %} +{% include 'c/size-write-array-statements.j2' %} diff --git a/barectf/templates/metadata/struct-ft.j2 b/barectf/templates/metadata/struct-ft.j2 index 50de1df..a2affa1 100644 --- a/barectf/templates/metadata/struct-ft.j2 +++ b/barectf/templates/metadata/struct-ft.j2 @@ -24,12 +24,7 @@ #} struct { {% for name, member in ft.members.items() %} - {# - # `chain` is a list of static array field types terminated with a - # non-array field type (the most nested). - #} - {% set chain = ft_chain(member.field_type) %} - {{ chain[-1] | ft_str | indent_tab }} {{ name }} - {%- for array_ft in chain[:-1] %}[{{ array_ft.length }}]{% endfor %}; + {{ member.field_type | deepest_ft | ft_str | indent_tab }} {{ name }} + {%- for len in member.field_type | ft_lengths(name) %}[{{ len }}]{% endfor %}; {% endfor %} } align({{ ft.minimum_alignment }}) diff --git a/barectf/tsdl182gen.py b/barectf/tsdl182gen.py index 01ee04b..e2c81c1 100644 --- a/barectf/tsdl182gen.py +++ b/barectf/tsdl182gen.py @@ -23,7 +23,7 @@ import barectf.config as barectf_config import barectf.template as barectf_template -from typing import List, Optional +from typing import List, Optional, Union import typing @@ -53,19 +53,32 @@ def _gen_str_ft(ft: barectf_config._FieldType) -> str: return _STR_FT_TEMPL.render(ft=ft) -def _ft_chain(ft: barectf_config._FieldType) -> List[barectf_config._FieldType]: - chain: List[barectf_config._FieldType] = [] +def _filt_ft_lengths(ft: barectf_config._FieldType, name: str) -> List[Union[str, int]]: + lengths: List[Union[str, int]] = [] - while isinstance(ft, barectf_config.StaticArrayFieldType): - chain.append(ft) + while isinstance(ft, barectf_config._ArrayFieldType): + if type(ft) is barectf_config.StaticArrayFieldType: + ft = typing.cast(barectf_config.StaticArrayFieldType, ft) + lengths.append(ft.length) + else: + assert type(ft) is barectf_config.DynamicArrayFieldType + ft = typing.cast(barectf_config.DynamicArrayFieldType, ft) + lengths.append(ft._length_ft_member_name) + + ft = ft.element_field_type + + return lengths + + +def _filt_deepest_ft(ft: barectf_config._FieldType) -> barectf_config._FieldType: + while isinstance(ft, barectf_config._ArrayFieldType): ft = ft.element_field_type - chain.append(ft) - return chain + return ft def _gen_struct_ft(ft: barectf_config._FieldType) -> str: - return _STRUCT_FT_TEMPL.render(ft=ft, ft_chain=_ft_chain) + return _STRUCT_FT_TEMPL.render(ft=ft) _FT_CLS_TO_GEN_FT_FUNC = { @@ -87,6 +100,8 @@ _TEMPL_FILTERS = { 'disp_base_int': _filt_disp_base_int, 'int_ft_str': _filt_int_ft_str, 'ft_str': _filt_ft_str, + 'ft_lengths': _filt_ft_lengths, + 'deepest_ft': _filt_deepest_ft, } -- 2.34.1