ebc85e131843ef5cf1bd8a1d30e404de6e1b4dc6
[deliverable/barectf.git] / barectf / cgen.py
1 # The MIT License (MIT)
2 #
3 # Copyright (c) 2014-2020 Philippe Proulx <pproulx@efficios.com>
4 #
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:
12 #
13 # The above copyright notice and this permission notice shall be
14 # included in all copies or substantial portions of the Software.
15 #
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.
23
24 import barectf.template as barectf_template
25 import barectf.config as barectf_config
26 import collections
27 import copy
28
29
30 # A tuple containing serialization and size computation function
31 # templates for a given operation.
32 _OpTemplates = collections.namedtuple('_OpTemplates', ['serialize', 'size'])
33
34
35 # Base class of any operation within source code.
36 #
37 # Any operation has:
38 #
39 # * An offset at which to start to write within the current byte.
40 #
41 # * A field type.
42 #
43 # * A list of names which, when joined with `_`, form the generic C
44 # source variable name.
45 #
46 # * Serialization and size computation templates to generate the
47 # operation's source code for those functions.
48 class _Op:
49 def __init__(self, offset_in_byte, ft, names, templates):
50 assert(offset_in_byte >= 0 and offset_in_byte < 8)
51 self._offset_in_byte = offset_in_byte
52 self._ft = ft
53 self._names = copy.copy(names)
54 self._templates = templates
55
56 @property
57 def offset_in_byte(self):
58 return self._offset_in_byte
59
60 @property
61 def ft(self):
62 return self._ft
63
64 @property
65 def names(self):
66 return self._names
67
68 @property
69 def top_name(self):
70 return self._names[-1]
71
72 def _render_template(self, templ, **kwargs):
73 return templ.render(op=self, root_ft_prefixes=_RootFtPrefixes,
74 root_ft_prefix_names=_ROOT_FT_PREFIX_NAMES, **kwargs)
75
76 def serialize_str(self, **kwargs):
77 return self._render_template(self._templates.serialize, **kwargs)
78
79 def size_str(self, **kwargs):
80 return self._render_template(self._templates.size, **kwargs)
81
82
83 # An "align" operation.
84 class _AlignOp(_Op):
85 def __init__(self, offset_in_byte, ft, names, templates, value):
86 super().__init__(offset_in_byte, ft, names, templates)
87 self._value = value
88
89 @property
90 def value(self):
91 return self._value
92
93
94 # A "write" operation.
95 class _WriteOp(_Op):
96 pass
97
98
99 # A builder of a chain of operations.
100 #
101 # Such a builder is closely connected to a `_CodeGen` object using it to
102 # find generic templates.
103 #
104 # Call append_root_ft() to make an operation builder append operations
105 # to itself for each member, recursively, of the structure field type.
106 #
107 # Get an operation builder's operations with its `ops` property.
108 class _OpsBuilder:
109 def __init__(self, cg):
110 self._last_alignment = None
111 self._last_bit_array_size = None
112 self._ops = []
113 self._names = []
114 self._offset_in_byte = 0
115 self._cg = cg
116
117 @property
118 def ops(self):
119 return self._ops
120
121 # Creates and appends the operations for the members, recursively,
122 # of the root structure field type `ft` named `name`.
123 #
124 # `spec_serialize_write_templates` is a mapping of first level
125 # member names to specialized serialization "write" templates.
126 def append_root_ft(self, ft, name, spec_serialize_write_templates=None):
127 if ft is None:
128 return
129
130 if spec_serialize_write_templates is None:
131 spec_serialize_write_templates = {}
132
133 assert type(ft) is barectf_config.StructureFieldType
134 assert len(self._names) == 0
135 self._append_ft(ft, name, spec_serialize_write_templates)
136
137 # Creates and appends the operations of a given field type `ft`
138 # named `name`.
139 #
140 # See append_root_ft() for `spec_serialize_write_templates`.
141 def _append_ft(self, ft, name, spec_serialize_write_templates):
142 def top_name():
143 return self._names[-1]
144
145 # Appends a "write" operation for the field type `ft`.
146 #
147 # This function considers `spec_serialize_write_templates` to
148 # override generic templates.
149 def append_write_op(ft):
150 assert type(ft) is not barectf_config.StructureFieldType
151 offset_in_byte = self._offset_in_byte
152
153 if isinstance(ft, barectf_config._BitArrayFieldType):
154 self._offset_in_byte += ft.size
155 self._offset_in_byte %= 8
156
157 serialize_write_templ = None
158
159 if len(self._names) == 2:
160 serialize_write_templ = spec_serialize_write_templates.get(top_name())
161
162 if serialize_write_templ is None:
163 if isinstance(ft, barectf_config._IntegerFieldType):
164 serialize_write_templ = self._cg._serialize_write_int_statements_templ
165 elif type(ft) is barectf_config.RealFieldType:
166 serialize_write_templ = self._cg._serialize_write_real_statements_templ
167 else:
168 assert type(ft) is barectf_config.StringFieldType
169 serialize_write_templ = self._cg._serialize_write_string_statements_templ
170
171 size_write_templ = None
172
173 if isinstance(ft, barectf_config._BitArrayFieldType):
174 size_write_templ = self._cg._size_write_bit_array_statements_templ
175 elif type(ft) is barectf_config.StringFieldType:
176 size_write_templ = self._cg._size_write_string_statements_templ
177
178 self._ops.append(_WriteOp(offset_in_byte, ft, self._names,
179 _OpTemplates(serialize_write_templ, size_write_templ)))
180
181 # Creates and appends an "align" operation for the field type
182 # `ft` if needed.
183 #
184 # This function updates the builder's state.
185 def try_append_align_op(alignment, do_align, ft):
186 def align(v, alignment):
187 return (v + (alignment - 1)) & -alignment
188
189 offset_in_byte = self._offset_in_byte
190 self._offset_in_byte = align(self._offset_in_byte, alignment) % 8
191
192 if do_align and alignment > 1:
193 self._ops.append(_AlignOp(offset_in_byte, ft, self._names,
194 _OpTemplates(self._cg._serialize_align_statements_templ,
195 self._cg._size_align_statements_templ),
196 alignment))
197
198 # Returns whether or not, considering the alignment requirement
199 # `align_req` and the builder's current state, we must create
200 # and append an "align" operation.
201 def must_align(align_req):
202 return self._last_alignment != align_req or self._last_bit_array_size % align_req != 0
203
204 # push field type's name to the builder's name stack initially
205 self._names.append(name)
206
207 if isinstance(ft, (barectf_config.StringFieldType, barectf_config._ArrayFieldType)):
208 assert type(ft) is barectf_config.StringFieldType or top_name() == 'uuid'
209
210 # strings and arrays are always byte-aligned
211 do_align = must_align(8)
212 self._last_alignment = 8
213 self._last_bit_array_size = 8
214 try_append_align_op(8, do_align, ft)
215 append_write_op(ft)
216 else:
217 do_align = must_align(ft.alignment)
218 self._last_alignment = ft.alignment
219
220 if type(ft) is barectf_config.StructureFieldType:
221 self._last_bit_array_size = ft.alignment
222 else:
223 self._last_bit_array_size = ft.size
224
225 try_append_align_op(ft.alignment, do_align, ft)
226
227 if type(ft) is barectf_config.StructureFieldType:
228 for member_name, member in ft.members.items():
229 self._append_ft(member.field_type, member_name, spec_serialize_write_templates)
230 else:
231 append_write_op(ft)
232
233 # exiting for this field type: pop its name
234 del self._names[-1]
235
236
237 # The operations for an event.
238 #
239 # The available operations are:
240 #
241 # * Specific context operations.
242 # * Payload operations.
243 class _EventOps:
244 def __init__(self, spec_ctx_ops, payload_ops):
245 self._spec_ctx_ops = copy.copy(spec_ctx_ops)
246 self._payload_ops = copy.copy(payload_ops)
247
248 @property
249 def spec_ctx_ops(self):
250 return self._spec_ctx_ops
251
252 @property
253 def payload_ops(self):
254 return self._payload_ops
255
256
257 # The operations for a stream.
258 #
259 # The available operations are:
260 #
261 # * Packet header operations.
262 # * Packet context operations.
263 # * Event header operations.
264 # * Event common context operations.
265 # * Event operations (`_EventOps`).
266 class _StreamOps:
267 def __init__(self, pkt_header_ops, pkt_ctx_ops, ev_header_ops,
268 ev_common_ctx_ops, ev_ops):
269 self._pkt_header_ops = copy.copy(pkt_header_ops)
270 self._pkt_ctx_ops = copy.copy(pkt_ctx_ops)
271 self._ev_header_ops = copy.copy(ev_header_ops)
272 self._ev_common_ctx_ops = copy.copy(ev_common_ctx_ops)
273 self._ev_ops = copy.copy(ev_ops)
274
275 @property
276 def pkt_header_ops(self):
277 return self._pkt_header_ops
278
279 @property
280 def pkt_ctx_ops(self):
281 return self._pkt_ctx_ops
282
283 @property
284 def ev_header_ops(self):
285 return self._ev_header_ops
286
287 @property
288 def ev_common_ctx_ops(self):
289 return self._ev_common_ctx_ops
290
291 @property
292 def ev_ops(self):
293 return self._ev_ops
294
295
296 # The C variable name prefixes for the six kinds of root field types.
297 class _RootFtPrefixes:
298 PH = 'ph'
299 PC = 'pc'
300 EH = 'eh'
301 ECC = 'ecc'
302 SC = 'sc'
303 P = 'p'
304
305
306 # The human-readable names of the `_RootFtPrefixes` members.
307 _ROOT_FT_PREFIX_NAMES = {
308 _RootFtPrefixes.PH: 'packet header',
309 _RootFtPrefixes.PC: 'packet context',
310 _RootFtPrefixes.EH: 'event header',
311 _RootFtPrefixes.ECC: 'event common context',
312 _RootFtPrefixes.SC: 'specific context',
313 _RootFtPrefixes.P: 'payload',
314 }
315
316
317 # A named function parameter for a given field type.
318 _FtParam = collections.namedtuple('_FtParam', ['ft', 'name'])
319
320
321 # A C code generator.
322 #
323 # Such a code generator can generate:
324 #
325 # * The bitfield header (gen_bitfield_header()).
326 # * The public header (gen_header()).
327 # * The source code (gen_src()).
328 class _CodeGen:
329 def __init__(self, cfg):
330 self._cfg = cfg
331 self._iden_prefix = cfg.options.code_generation_options.identifier_prefix
332 self._saved_serialization_ops = {}
333 self._templ_filters = {
334 'ft_c_type': self._ft_c_type,
335 'open_func_params_str': self._open_func_params_str,
336 'trace_func_params_str': self._trace_func_params_str,
337 'serialize_ev_common_ctx_func_params_str': self._serialize_ev_common_ctx_func_params_str,
338 }
339 self._func_proto_params_templ = self._create_template('func-proto-params.j2')
340 self._serialize_align_statements_templ = self._create_template('serialize-align-statements.j2')
341 self._serialize_write_int_statements_templ = self._create_template('serialize-write-int-statements.j2')
342 self._serialize_write_real_statements_templ = self._create_template('serialize-write-real-statements.j2')
343 self._serialize_write_string_statements_templ = self._create_template('serialize-write-string-statements.j2')
344 self._serialize_write_magic_statements_templ = self._create_template('serialize-write-magic-statements.j2')
345 self._serialize_write_uuid_statements_templ = self._create_template('serialize-write-uuid-statements.j2')
346 self._serialize_write_stream_type_id_statements_templ = self._create_template('serialize-write-stream-type-id-statements.j2')
347 self._serialize_write_time_statements_templ = self._create_template('serialize-write-time-statements.j2')
348 self._serialize_write_packet_size_statements_templ = self._create_template('serialize-write-packet-size-statements.j2')
349 self._serialize_write_skip_save_statements_templ = self._create_template('serialize-write-skip-save-statements.j2')
350 self._serialize_write_ev_type_id_statements_templ = self._create_template('serialize-write-ev-type-id-statements.j2')
351 self._size_align_statements_templ = self._create_template('size-align-statements.j2')
352 self._size_write_bit_array_statements_templ = self._create_template('size-write-bit-array-statements.j2')
353 self._size_write_string_statements_templ = self._create_template('size-write-string-statements.j2')
354
355 # Creates and returns a template named `name` which is a file
356 # template if `is_file_template` is `True`.
357 #
358 # `name` is the file name, including the `.j2` extension, within the
359 # `c` directory.
360 #
361 # Such a template has the filters custom filters
362 # `self._templ_filters`.
363 def _create_template_base(self, name: str, is_file_template: bool):
364 return barectf_template._Template(f'c/{name}', is_file_template, self._cfg,
365 self._templ_filters)
366
367 # Creates and returns a non-file template named `name`.
368 #
369 # See _create_template_base() for `name`.
370 def _create_template(self, name: str) -> barectf_template._Template:
371 return self._create_template_base(name, False)
372
373 # Creates and returns a file template named `name`.
374 #
375 # See _create_template_base() for `name`.
376 def _create_file_template(self, name: str) -> barectf_template._Template:
377 return self._create_template_base(name, True)
378
379 # Trace type of this code generator's barectf configuration.
380 @property
381 def _trace_type(self):
382 return self._cfg.trace.type
383
384 # Returns the C type for the field type `ft`, returning a `const` C
385 # type if `is_const` is `True`.
386 def _ft_c_type(self, ft, is_const=False):
387 const_beg_str = 'const '
388
389 if isinstance(ft, barectf_config._IntegerFieldType):
390 sign_prefix = 'u' if isinstance(ft, barectf_config.UnsignedIntegerFieldType) else ''
391
392 if ft.size <= 8:
393 sz = 8
394 elif ft.size <= 16:
395 sz = 16
396 elif ft.size <= 32:
397 sz = 32
398 else:
399 assert ft.size <= 64
400 sz = 64
401
402 return f'{const_beg_str if is_const else ""}{sign_prefix}int{sz}_t'
403 elif type(ft) is barectf_config.RealFieldType:
404 if ft.size == 32 and ft.alignment == 32:
405 c_type = 'float'
406 elif ft.size == 64 and ft.alignment == 64:
407 c_type = 'double'
408 else:
409 c_type = 'uint64_t'
410
411 return f'{const_beg_str if is_const else ""}{c_type}'
412 else:
413 assert type(ft) is barectf_config.StringFieldType
414 return f'const char *{" const" if is_const else ""}'
415
416 # Returns the function prototype parameters for the members of the
417 # root structure field type `root_ft`.
418 #
419 # Each parameter has the prefix `name_prefix` followed with `_`.
420 #
421 # Members of which the name is in `exclude_set` are excluded.
422 def _proto_params_str(self, root_ft, name_prefix, const_params, exclude_set=None,
423 only_dyn=False):
424 if root_ft is None:
425 return
426
427 if exclude_set is None:
428 exclude_set = set()
429
430 params = []
431
432 for member_name, member in root_ft.members.items():
433 if member_name in exclude_set:
434 continue
435
436 if only_dyn and not member.field_type.size_is_dynamic:
437 continue
438
439 params.append(_FtParam(member.field_type, member_name))
440
441 return self._func_proto_params_templ.render(params=params, prefix=name_prefix,
442 const_params=const_params)
443
444 # Returns the packet opening function prototype parameters for the
445 # stream type `stream_type`.
446 def _open_func_params_str(self, stream_type, const_params):
447 parts = []
448 parts.append(self._proto_params_str(self._trace_type._pkt_header_ft, _RootFtPrefixes.PH,
449 const_params, {'magic', 'stream_id', 'uuid'}))
450
451 exclude_set = {
452 'timestamp_begin',
453 'timestamp_end',
454 'packet_size',
455 'content_size',
456 'events_discarded',
457 }
458 parts.append(self._proto_params_str(stream_type._pkt_ctx_ft, _RootFtPrefixes.PC,
459 const_params, exclude_set))
460 return ''.join(parts)
461
462 # Returns the tracing function prototype parameters for the stream
463 # and event types `stream_ev_types`.
464 def _trace_func_params_str(self, stream_ev_types, const_params, only_dyn=False):
465 stream_type = stream_ev_types[0]
466 ev_type = stream_ev_types[1]
467 parts = []
468
469 if stream_type._ev_header_ft is not None:
470 parts.append(self._proto_params_str(stream_type._ev_header_ft, _RootFtPrefixes.EH,
471 const_params, {'id', 'timestamp'},
472 only_dyn=only_dyn))
473
474 if stream_type.event_common_context_field_type is not None:
475 parts.append(self._proto_params_str(stream_type.event_common_context_field_type,
476 _RootFtPrefixes.ECC, const_params,
477 only_dyn=only_dyn))
478
479 if ev_type.specific_context_field_type is not None:
480 parts.append(self._proto_params_str(ev_type.specific_context_field_type,
481 _RootFtPrefixes.SC, const_params,
482 only_dyn=only_dyn))
483
484 if ev_type.payload_field_type is not None:
485 parts.append(self._proto_params_str(ev_type.payload_field_type, _RootFtPrefixes.P,
486 const_params, only_dyn=only_dyn))
487
488 return ''.join(parts)
489
490 # Returns the event header serialization function prototype
491 # parameters for the stream type `stream_type`.
492 def _serialize_ev_common_ctx_func_params_str(self, stream_type, const_params):
493 return self._proto_params_str(stream_type.event_common_context_field_type,
494 _RootFtPrefixes.ECC, const_params);
495
496 # Generates the bitfield header file contents.
497 def gen_bitfield_header(self):
498 return self._create_file_template('bitfield.h.j2').render()
499
500 # Generates the public header file contents.
501 def gen_header(self):
502 return self._create_file_template('barectf.h.j2').render(root_ft_prefixes=_RootFtPrefixes)
503
504 # Generates the source code file contents.
505 def gen_src(self, header_file_name, bitfield_header_file_name):
506 # Creates and returns the operations for all the stream and for
507 # all their events.
508 def create_stream_ops():
509 stream_ser_ops = {}
510
511 for stream_type in self._trace_type.stream_types:
512 pkt_header_ser_ops = []
513 builder = _OpsBuilder(self)
514 pkt_header_ft = self._trace_type._pkt_header_ft
515
516 # packet header serialization operations
517 if pkt_header_ft is not None:
518 spec_serialize_write_templates = {
519 'magic': self._serialize_write_magic_statements_templ,
520 'uuid': self._serialize_write_uuid_statements_templ,
521 'stream_id': self._serialize_write_stream_type_id_statements_templ,
522 }
523 builder.append_root_ft(pkt_header_ft, _RootFtPrefixes.PH,
524 spec_serialize_write_templates)
525 pkt_header_ser_ops = copy.copy(builder.ops)
526
527 # packet context serialization operations
528 first_op_index = len(builder.ops)
529 spec_serialize_write_templates = {
530 'timestamp_begin': self._serialize_write_time_statements_templ,
531 'packet_size': self._serialize_write_packet_size_statements_templ,
532 'timestamp_end': self._serialize_write_skip_save_statements_templ,
533 'events_discarded': self._serialize_write_skip_save_statements_templ,
534 'content_size': self._serialize_write_skip_save_statements_templ,
535 }
536 builder.append_root_ft(stream_type._pkt_ctx_ft, _RootFtPrefixes.PC,
537 spec_serialize_write_templates)
538 pkt_ctx_ser_ops = copy.copy(builder.ops[first_op_index:])
539
540 # event header serialization operations
541 builder = _OpsBuilder(self)
542 ev_header_ser_ops = []
543
544 if stream_type._ev_header_ft is not None:
545 spec_serialize_write_templates = {
546 'timestamp': self._serialize_write_time_statements_templ,
547 'id': self._serialize_write_ev_type_id_statements_templ,
548 }
549 builder.append_root_ft(stream_type._ev_header_ft, _RootFtPrefixes.EH,
550 spec_serialize_write_templates)
551 ev_header_ser_ops = copy.copy(builder.ops)
552
553 # event common context serialization operations
554 ev_common_ctx_ser_ops = []
555
556 if stream_type.event_common_context_field_type is not None:
557 first_op_index = len(builder.ops)
558 builder.append_root_ft(stream_type.event_common_context_field_type,
559 _RootFtPrefixes.ECC)
560 ev_common_ctx_ser_ops = copy.copy(builder.ops[first_op_index:])
561
562 # serialization operations specific to each event type
563 ev_ser_ops = {}
564
565 for ev_type in stream_type.event_types:
566 ev_builder = copy.copy(builder)
567
568 # specific context serialization operations
569 spec_ctx_ser_ops = []
570
571 if ev_type.specific_context_field_type is not None:
572 first_op_index = len(ev_builder.ops)
573 ev_builder.append_root_ft(ev_type.specific_context_field_type,
574 _RootFtPrefixes.SC)
575 spec_ctx_ser_ops = copy.copy(ev_builder.ops[first_op_index:])
576
577 # payload serialization operations
578 payload_ser_ops = []
579
580 if ev_type.payload_field_type is not None:
581 first_op_index = len(ev_builder.ops)
582 ev_builder.append_root_ft(ev_type.payload_field_type, _RootFtPrefixes.P)
583 payload_ser_ops = copy.copy(ev_builder.ops[first_op_index:])
584
585 ev_ser_ops[ev_type] = _EventOps(spec_ctx_ser_ops, payload_ser_ops)
586
587 stream_ser_ops[stream_type] = _StreamOps(pkt_header_ser_ops, pkt_ctx_ser_ops,
588 ev_header_ser_ops, ev_common_ctx_ser_ops,
589 ev_ser_ops)
590
591 return stream_ser_ops
592
593 # Returns the "write" operation for the packet context member
594 # named `member_name` within the stream type `stream_type`.
595 def stream_op_pkt_ctx_op(stream_type, member_name):
596 for op in stream_ops[stream_type].pkt_ctx_ops:
597 if op.top_name == member_name and type(op) is _WriteOp:
598 return op
599
600 stream_ops = create_stream_ops()
601 return self._create_file_template('barectf.c.j2').render(header_file_name=header_file_name,
602 bitfield_header_file_name=bitfield_header_file_name,
603 root_ft_prefixes=_RootFtPrefixes,
604 root_ft_prefix_names=_ROOT_FT_PREFIX_NAMES,
605 stream_ops=stream_ops,
606 stream_op_pkt_ctx_op=stream_op_pkt_ctx_op)
This page took 0.04192 seconds and 3 git commands to generate.