1 # The MIT License (MIT)
3 # Copyright (c) 2014-2020 Philippe Proulx <pproulx@efficios.com>
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be
14 # included in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 import barectf
.tsdl182gen
as barectf_tsdl182gen
25 import barectf
.template
as barectf_template
26 import barectf
.config
as barectf_config
27 import barectf
.version
as barectf_version
34 # A file generated by a `CodeGenerator` object.
36 # A generated file has a name (influenced by the configuration's
37 # file name prefix option) and contents.
39 def __init__(self
, name
, contents
):
41 self
._contents
= contents
52 # A barectf code generator.
54 # Build a code generator with a barectf configuration.
56 # A code generator can generate the TSDL `metadata` file and C source
59 def __init__(self
, configuration
):
60 self
._config
= configuration
61 self
._file
_name
_prefix
= configuration
.options
.code_generation_options
.file_name_prefix
62 self
._ccode
_gen
= _CCodeGenerator(configuration
)
63 self
._c
_headers
= None
64 self
._c
_sources
= None
65 self
._metadata
_stream
= None
68 def _barectf_header_name(self
):
69 return f
'{self._file_name_prefix}.h'
72 def _bitfield_header_name(self
):
73 return f
'{self._file_name_prefix}-bitfield.h'
75 def generate_c_headers(self
):
76 if self
._c
_headers
is None:
78 _GeneratedFile(self
._barectf
_header
_name
, self
._ccode
_gen
.generate_header()),
79 _GeneratedFile(self
._bitfield
_header
_name
, self
._ccode
_gen
.generate_bitfield_header()),
82 return self
._c
_headers
84 def generate_c_sources(self
):
85 if self
._c
_sources
is None:
87 _GeneratedFile(f
'{self._file_name_prefix}.c',
88 self
._ccode
_gen
.generate_c_src(self
._barectf
_header
_name
,
89 self
._bitfield
_header
_name
))
92 return self
._c
_sources
94 def generate_metadata_stream(self
):
95 if self
._metadata
_stream
is None:
96 self
._metadata
_stream
= _GeneratedFile('metadata',
97 barectf_tsdl182gen
._from
_config
(self
._config
))
99 return self
._metadata
_stream
102 # A tuple containing serialization and size computation function
103 # templates for a given operation.
104 _OpTemplates
= collections
.namedtuple('_OpTemplates', ['serialize', 'size'])
107 # Base class of any operation within source code.
111 # * An offset at which to start to write within the current byte.
115 # * A list of names which, when joined with `_`, form the generic C
116 # source variable name.
118 # * Serialization and size computation templates to generate the
119 # operation's source code for those functions.
121 def __init__(self
, offset_in_byte
, ft
, names
, templates
):
122 assert(offset_in_byte
>= 0 and offset_in_byte
< 8)
123 self
._offset
_in
_byte
= offset_in_byte
125 self
._names
= copy
.copy(names
)
126 self
._templates
= templates
129 def offset_in_byte(self
):
130 return self
._offset
_in
_byte
142 return self
._names
[-1]
144 def _render_template(self
, templ
, **kwargs
):
145 return templ
.render(op
=self
, root_ft_prefixes
=_RootFtPrefixes
,
146 root_ft_prefix_names
=_ROOT_FT_PREFIX_NAMES
, **kwargs
)
148 def serialize_str(self
, **kwargs
):
149 return self
._render
_template
(self
._templates
.serialize
, **kwargs
)
151 def size_str(self
, **kwargs
):
152 return self
._render
_template
(self
._templates
.size
, **kwargs
)
155 # An "align" operation.
157 def __init__(self
, offset_in_byte
, ft
, names
, templates
, value
):
158 super().__init
__(offset_in_byte
, ft
, names
, templates
)
166 # A "write" operation.
171 # A builder of a chain of operations.
173 # Such a builder is closely connected to a `_CCodeGenerator` object
174 # using it to find generic templates.
176 # Call append_root_ft() to make an operation builder append operations
177 # to itself for each member, recursively, of the structure field type.
179 # Get an operation builder's operations with its `ops` property.
181 def __init__(self
, cg
):
182 self
._last
_alignment
= None
183 self
._last
_bit
_array
_size
= None
186 self
._offset
_in
_byte
= 0
193 # Creates and appends the operations for the members, recursively,
194 # of the root structure field type `ft` named `name`.
196 # `spec_serialize_write_templates` is a mapping of first level
197 # member names to specialized serialization "write" templates.
198 def append_root_ft(self
, ft
, name
, spec_serialize_write_templates
=None):
202 if spec_serialize_write_templates
is None:
203 spec_serialize_write_templates
= {}
205 assert type(ft
) is barectf_config
.StructureFieldType
206 assert len(self
._names
) == 0
207 self
._append
_ft
(ft
, name
, spec_serialize_write_templates
)
209 # Creates and appends the operations of a given field type `ft`
212 # See append_root_ft() for `spec_serialize_write_templates`.
213 def _append_ft(self
, ft
, name
, spec_serialize_write_templates
):
215 return self
._names
[-1]
217 # Appends a "write" operation for the field type `ft`.
219 # This function considers `spec_serialize_write_templates` to
220 # override generic templates.
221 def append_write_op(ft
):
222 assert type(ft
) is not barectf_config
.StructureFieldType
223 offset_in_byte
= self
._offset
_in
_byte
225 if isinstance(ft
, barectf_config
._BitArrayFieldType
):
226 self
._offset
_in
_byte
+= ft
.size
227 self
._offset
_in
_byte
%= 8
229 serialize_write_templ
= None
231 if len(self
._names
) == 2:
232 serialize_write_templ
= spec_serialize_write_templates
.get(top_name())
234 if serialize_write_templ
is None:
235 if isinstance(ft
, barectf_config
._IntegerFieldType
):
236 serialize_write_templ
= self
._cg
._serialize
_write
_int
_statements
_templ
237 elif type(ft
) is barectf_config
.RealFieldType
:
238 serialize_write_templ
= self
._cg
._serialize
_write
_real
_statements
_templ
240 assert type(ft
) is barectf_config
.StringFieldType
241 serialize_write_templ
= self
._cg
._serialize
_write
_string
_statements
_templ
243 size_write_templ
= None
245 if isinstance(ft
, barectf_config
._BitArrayFieldType
):
246 size_write_templ
= self
._cg
._size
_write
_bit
_array
_statements
_templ
247 elif type(ft
) is barectf_config
.StringFieldType
:
248 size_write_templ
= self
._cg
._size
_write
_string
_statements
_templ
250 self
._ops
.append(_WriteOp(offset_in_byte
, ft
, self
._names
,
251 _OpTemplates(serialize_write_templ
, size_write_templ
)))
253 # Creates and appends an "align" operation for the field type
256 # This function updates the builder's state.
257 def try_append_align_op(alignment
, do_align
, ft
):
258 def align(v
, alignment
):
259 return (v
+ (alignment
- 1)) & -alignment
261 offset_in_byte
= self
._offset
_in
_byte
262 self
._offset
_in
_byte
= align(self
._offset
_in
_byte
, alignment
) % 8
264 if do_align
and alignment
> 1:
265 self
._ops
.append(_AlignOp(offset_in_byte
, ft
, self
._names
,
266 _OpTemplates(self
._cg
._serialize
_align
_statements
_templ
,
267 self
._cg
._size
_align
_statements
_templ
),
270 # Returns whether or not, considering the alignment requirement
271 # `align_req` and the builder's current state, we must create
272 # and append an "align" operation.
273 def must_align(align_req
):
274 return self
._last
_alignment
!= align_req
or self
._last
_bit
_array
_size
% align_req
!= 0
276 # push field type's name to the builder's name stack initially
277 self
._names
.append(name
)
279 if isinstance(ft
, (barectf_config
.StringFieldType
, barectf_config
._ArrayFieldType
)):
280 assert type(ft
) is barectf_config
.StringFieldType
or top_name() == 'uuid'
282 # strings and arrays are always byte-aligned
283 do_align
= must_align(8)
284 self
._last
_alignment
= 8
285 self
._last
_bit
_array
_size
= 8
286 try_append_align_op(8, do_align
, ft
)
289 do_align
= must_align(ft
.alignment
)
290 self
._last
_alignment
= ft
.alignment
292 if type(ft
) is barectf_config
.StructureFieldType
:
293 self
._last
_bit
_array
_size
= ft
.alignment
295 self
._last
_bit
_array
_size
= ft
.size
297 try_append_align_op(ft
.alignment
, do_align
, ft
)
299 if type(ft
) is barectf_config
.StructureFieldType
:
300 for member_name
, member
in ft
.members
.items():
301 self
._append
_ft
(member
.field_type
, member_name
, spec_serialize_write_templates
)
305 # exiting for this field type: pop its name
309 # The operations for an event.
311 # The available operations are:
313 # * Specific context operations.
314 # * Payload operations.
316 def __init__(self
, spec_ctx_ops
, payload_ops
):
317 self
._spec
_ctx
_ops
= copy
.copy(spec_ctx_ops
)
318 self
._payload
_ops
= copy
.copy(payload_ops
)
321 def spec_ctx_ops(self
):
322 return self
._spec
_ctx
_ops
325 def payload_ops(self
):
326 return self
._payload
_ops
329 # The operations for a stream.
331 # The available operations are:
333 # * Packet header operations.
334 # * Packet context operations.
335 # * Event header operations.
336 # * Event common context operations.
337 # * Event operations (`_EventOps`).
339 def __init__(self
, pkt_header_ops
, pkt_ctx_ops
, ev_header_ops
,
340 ev_common_ctx_ops
, ev_ops
):
341 self
._pkt
_header
_ops
= copy
.copy(pkt_header_ops
)
342 self
._pkt
_ctx
_ops
= copy
.copy(pkt_ctx_ops
)
343 self
._ev
_header
_ops
= copy
.copy(ev_header_ops
)
344 self
._ev
_common
_ctx
_ops
= copy
.copy(ev_common_ctx_ops
)
345 self
._ev
_ops
= copy
.copy(ev_ops
)
348 def pkt_header_ops(self
):
349 return self
._pkt
_header
_ops
352 def pkt_ctx_ops(self
):
353 return self
._pkt
_ctx
_ops
356 def ev_header_ops(self
):
357 return self
._ev
_header
_ops
360 def ev_common_ctx_ops(self
):
361 return self
._ev
_common
_ctx
_ops
368 # The C variable name prefixes for the six kinds of root field types.
369 class _RootFtPrefixes
:
378 # The human-readable names of the `_RootFtPrefixes` members.
379 _ROOT_FT_PREFIX_NAMES
= {
380 _RootFtPrefixes
.PH
: 'packet header',
381 _RootFtPrefixes
.PC
: 'packet context',
382 _RootFtPrefixes
.EH
: 'event header',
383 _RootFtPrefixes
.ECC
: 'event common context',
384 _RootFtPrefixes
.SC
: 'specific context',
385 _RootFtPrefixes
.P
: 'payload',
389 # A named function parameter for a given field type.
390 _FtParam
= collections
.namedtuple('_FtParam', ['ft', 'name'])
393 # A C code generator.
395 # Such a code generator can generate:
397 # * The bitfield header (generate_bitfield_header()).
398 # * The public header (generate_header()).
399 # * The source code (generate_c_src()).
400 class _CCodeGenerator
:
401 def __init__(self
, cfg
):
403 self
._iden
_prefix
= cfg
.options
.code_generation_options
.identifier_prefix
404 self
._saved
_serialization
_ops
= {}
405 self
._templ
_filters
= {
406 'ft_c_type': self
._ft
_c
_type
,
407 'open_func_params_str': self
._open
_func
_params
_str
,
408 'trace_func_params_str': self
._trace
_func
_params
_str
,
409 'serialize_ev_common_ctx_func_params_str': self
._serialize
_ev
_common
_ctx
_func
_params
_str
,
411 self
._func
_proto
_params
_templ
= self
._create
_template
('func-proto-params.j2')
412 self
._serialize
_align
_statements
_templ
= self
._create
_template
('serialize-align-statements.j2')
413 self
._serialize
_write
_int
_statements
_templ
= self
._create
_template
('serialize-write-int-statements.j2')
414 self
._serialize
_write
_real
_statements
_templ
= self
._create
_template
('serialize-write-real-statements.j2')
415 self
._serialize
_write
_string
_statements
_templ
= self
._create
_template
('serialize-write-string-statements.j2')
416 self
._serialize
_write
_magic
_statements
_templ
= self
._create
_template
('serialize-write-magic-statements.j2')
417 self
._serialize
_write
_uuid
_statements
_templ
= self
._create
_template
('serialize-write-uuid-statements.j2')
418 self
._serialize
_write
_stream
_type
_id
_statements
_templ
= self
._create
_template
('serialize-write-stream-type-id-statements.j2')
419 self
._serialize
_write
_time
_statements
_templ
= self
._create
_template
('serialize-write-time-statements.j2')
420 self
._serialize
_write
_packet
_size
_statements
_templ
= self
._create
_template
('serialize-write-packet-size-statements.j2')
421 self
._serialize
_write
_skip
_save
_statements
_templ
= self
._create
_template
('serialize-write-skip-save-statements.j2')
422 self
._serialize
_write
_ev
_type
_id
_statements
_templ
= self
._create
_template
('serialize-write-ev-type-id-statements.j2')
423 self
._size
_align
_statements
_templ
= self
._create
_template
('size-align-statements.j2')
424 self
._size
_write
_bit
_array
_statements
_templ
= self
._create
_template
('size-write-bit-array-statements.j2')
425 self
._size
_write
_string
_statements
_templ
= self
._create
_template
('size-write-string-statements.j2')
427 # Creates and returns a template named `name` which is a file
428 # template if `is_file_template` is `True`.
430 # `name` is the file name, including the `.j2` extension, within the
433 # Such a template has the filters custom filters
434 # `self._templ_filters`.
435 def _create_template_base(self
, name
: str, is_file_template
: bool):
436 return barectf_template
._Template
(f
'c/{name}', is_file_template
, self
._cfg
,
439 # Creates and returns a non-file template named `name`.
441 # See _create_template_base() for `name`.
442 def _create_template(self
, name
: str) -> barectf_template
._Template
:
443 return self
._create
_template
_base
(name
, False)
445 # Creates and returns a file template named `name`.
447 # See _create_template_base() for `name`.
448 def _create_file_template(self
, name
: str) -> barectf_template
._Template
:
449 return self
._create
_template
_base
(name
, True)
451 # Trace type of this code generator's barectf configuration.
453 def _trace_type(self
):
454 return self
._cfg
.trace
.type
456 # Returns the C type for the field type `ft`, returning a `const` C
457 # type if `is_const` is `True`.
458 def _ft_c_type(self
, ft
, is_const
=False):
459 const_beg_str
= 'const '
461 if isinstance(ft
, barectf_config
._IntegerFieldType
):
462 sign_prefix
= 'u' if isinstance(ft
, barectf_config
.UnsignedIntegerFieldType
) else ''
474 return f
'{const_beg_str if is_const else ""}{sign_prefix}int{sz}_t'
475 elif type(ft
) is barectf_config
.RealFieldType
:
476 if ft
.size
== 32 and ft
.alignment
== 32:
478 elif ft
.size
== 64 and ft
.alignment
== 64:
483 return f
'{const_beg_str if is_const else ""}{c_type}'
485 assert type(ft
) is barectf_config
.StringFieldType
486 return f
'const char *{" const" if is_const else ""}'
488 # Returns the function prototype parameters for the members of the
489 # root structure field type `root_ft`.
491 # Each parameter has the prefix `name_prefix` followed with `_`.
493 # Members of which the name is in `exclude_set` are excluded.
494 def _proto_params_str(self
, root_ft
, name_prefix
, const_params
, exclude_set
=None):
498 if exclude_set
is None:
503 for member_name
, member
in root_ft
.members
.items():
504 if member_name
in exclude_set
:
507 params
.append(_FtParam(member
.field_type
, member_name
))
509 return self
._func
_proto
_params
_templ
.render(params
=params
, prefix
=name_prefix
,
510 const_params
=const_params
)
512 # Returns the packet opening function prototype parameters for the
513 # stream type `stream_type`.
514 def _open_func_params_str(self
, stream_type
, const_params
):
516 parts
.append(self
._proto
_params
_str
(self
._trace
_type
._pkt
_header
_ft
, _RootFtPrefixes
.PH
,
517 const_params
, {'magic', 'stream_id', 'uuid'}))
526 parts
.append(self
._proto
_params
_str
(stream_type
._pkt
_ctx
_ft
, _RootFtPrefixes
.PC
,
527 const_params
, exclude_set
))
528 return ''.join(parts
)
530 # Returns the tracing function prototype parameters for the stream
531 # and event types `stream_ev_types`.
532 def _trace_func_params_str(self
, stream_ev_types
, const_params
):
533 stream_type
= stream_ev_types
[0]
534 ev_type
= stream_ev_types
[1]
537 if stream_type
._ev
_header
_ft
is not None:
538 parts
.append(self
._proto
_params
_str
(stream_type
._ev
_header
_ft
, _RootFtPrefixes
.EH
,
539 const_params
, {'id', 'timestamp'}))
541 if stream_type
.event_common_context_field_type
is not None:
542 parts
.append(self
._proto
_params
_str
(stream_type
.event_common_context_field_type
,
543 _RootFtPrefixes
.ECC
, const_params
))
545 if ev_type
.specific_context_field_type
is not None:
546 parts
.append(self
._proto
_params
_str
(ev_type
.specific_context_field_type
,
547 _RootFtPrefixes
.SC
, const_params
))
549 if ev_type
.payload_field_type
is not None:
550 parts
.append(self
._proto
_params
_str
(ev_type
.payload_field_type
, _RootFtPrefixes
.P
,
553 return ''.join(parts
)
555 # Returns the event header serialization function prototype
556 # parameters for the stream type `stream_type`.
557 def _serialize_ev_common_ctx_func_params_str(self
, stream_type
, const_params
):
558 return self
._proto
_params
_str
(stream_type
.event_common_context_field_type
,
559 _RootFtPrefixes
.ECC
, const_params
);
561 # Generates the bitfield header file contents.
562 def generate_bitfield_header(self
):
563 return self
._create
_file
_template
('bitfield.h.j2').render()
565 # Generates the public header file contents.
566 def generate_header(self
):
567 return self
._create
_file
_template
('barectf.h.j2').render(root_ft_prefixes
=_RootFtPrefixes
)
569 # Generates the source code file contents.
570 def generate_c_src(self
, header_file_name
, bitfield_header_file_name
):
571 # Creates and returns the operations for all the stream and for
573 def create_stream_ops():
576 for stream_type
in self
._trace
_type
.stream_types
:
577 pkt_header_ser_ops
= []
578 builder
= _OpsBuilder(self
)
579 pkt_header_ft
= self
._trace
_type
._pkt
_header
_ft
581 # packet header serialization operations
582 if pkt_header_ft
is not None:
583 spec_serialize_write_templates
= {
584 'magic': self
._serialize
_write
_magic
_statements
_templ
,
585 'uuid': self
._serialize
_write
_uuid
_statements
_templ
,
586 'stream_id': self
._serialize
_write
_stream
_type
_id
_statements
_templ
,
588 builder
.append_root_ft(pkt_header_ft
, _RootFtPrefixes
.PH
,
589 spec_serialize_write_templates
)
590 pkt_header_ser_ops
= copy
.copy(builder
.ops
)
592 # packet context serialization operations
593 first_op_index
= len(builder
.ops
)
594 spec_serialize_write_templates
= {
595 'timestamp_begin': self
._serialize
_write
_time
_statements
_templ
,
596 'packet_size': self
._serialize
_write
_packet
_size
_statements
_templ
,
597 'timestamp_end': self
._serialize
_write
_skip
_save
_statements
_templ
,
598 'events_discarded': self
._serialize
_write
_skip
_save
_statements
_templ
,
599 'content_size': self
._serialize
_write
_skip
_save
_statements
_templ
,
601 builder
.append_root_ft(stream_type
._pkt
_ctx
_ft
, _RootFtPrefixes
.PC
,
602 spec_serialize_write_templates
)
603 pkt_ctx_ser_ops
= copy
.copy(builder
.ops
[first_op_index
:])
605 # event header serialization operations
606 builder
= _OpsBuilder(self
)
607 ev_header_ser_ops
= []
609 if stream_type
._ev
_header
_ft
is not None:
610 spec_serialize_write_templates
= {
611 'timestamp': self
._serialize
_write
_time
_statements
_templ
,
612 'id': self
._serialize
_write
_ev
_type
_id
_statements
_templ
,
614 builder
.append_root_ft(stream_type
._ev
_header
_ft
, _RootFtPrefixes
.EH
,
615 spec_serialize_write_templates
)
616 ev_header_ser_ops
= copy
.copy(builder
.ops
)
618 # event common context serialization operations
619 ev_common_ctx_ser_ops
= []
621 if stream_type
.event_common_context_field_type
is not None:
622 first_op_index
= len(builder
.ops
)
623 builder
.append_root_ft(stream_type
.event_common_context_field_type
,
625 ev_common_ctx_ser_ops
= copy
.copy(builder
.ops
[first_op_index
:])
627 # serialization operations specific to each event type
630 for ev_type
in stream_type
.event_types
:
631 ev_builder
= copy
.copy(builder
)
633 # specific context serialization operations
634 spec_ctx_ser_ops
= []
636 if ev_type
.specific_context_field_type
is not None:
637 first_op_index
= len(ev_builder
.ops
)
638 ev_builder
.append_root_ft(ev_type
.specific_context_field_type
,
640 spec_ctx_ser_ops
= copy
.copy(ev_builder
.ops
[first_op_index
:])
642 # payload serialization operations
645 if ev_type
.payload_field_type
is not None:
646 first_op_index
= len(ev_builder
.ops
)
647 ev_builder
.append_root_ft(ev_type
.payload_field_type
, _RootFtPrefixes
.P
)
648 payload_ser_ops
= copy
.copy(ev_builder
.ops
[first_op_index
:])
650 ev_ser_ops
[ev_type
] = _EventOps(spec_ctx_ser_ops
, payload_ser_ops
)
652 stream_ser_ops
[stream_type
] = _StreamOps(pkt_header_ser_ops
, pkt_ctx_ser_ops
,
653 ev_header_ser_ops
, ev_common_ctx_ser_ops
,
656 return stream_ser_ops
658 # Returns the "write" operation for the packet context member
659 # named `member_name` within the stream type `stream_type`.
660 def stream_op_pkt_ctx_op(stream_type
, member_name
):
661 for op
in stream_ops
[stream_type
].pkt_ctx_ops
:
662 if op
.top_name
== member_name
and type(op
) is _WriteOp
:
665 stream_ops
= create_stream_ops()
666 return self
._create
_file
_template
('barectf.c.j2').render(header_file_name
=header_file_name
,
667 bitfield_header_file_name
=bitfield_header_file_name
,
668 root_ft_prefixes
=_RootFtPrefixes
,
669 root_ft_prefix_names
=_ROOT_FT_PREFIX_NAMES
,
670 stream_ops
=stream_ops
,
671 stream_op_pkt_ctx_op
=stream_op_pkt_ctx_op
)