Commit | Line | Data |
---|---|---|
74c0b9b3 OD |
1 | #!/usr/bin/env python3 |
2 | # | |
3 | # SPDX-License-Identifier: MIT | |
4 | # | |
5 | # Copyright (c) 2023 EfficiOS, Inc. | |
6 | # | |
7 | # Author: Olivier Dion <odion@efficios.com> | |
8 | # | |
c75d6f3f | 9 | # Auto-generate lttng-ust tracepoints for a public API. |
74c0b9b3 OD |
10 | # |
11 | # Require: python-clang (libclang) | |
12 | ||
13 | import argparse | |
14 | import re | |
15 | import os | |
16 | import subprocess | |
17 | ||
18 | from string import Template | |
19 | ||
20 | import clang.cindex | |
21 | ||
22 | COMMON_PREFIX = None | |
23 | IGNORE = set() | |
24 | PROVIDER = None | |
25 | ||
26 | # LTTNG_UST_TP_ARGS is limited to 10 arguments. Since we introduce two | |
27 | # arguments of our own (thread-id and local-id), the maximum is 8. | |
28 | # | |
29 | # If a function has more arguments than this limit, all arguments -- at the | |
30 | # exception of the IDs -- will be passed through a data structure instead. | |
31 | MAX_TP_ARGS_COUNT = 8 | |
32 | ||
c75d6f3f | 33 | # Compatibility layer for Python3 < 3.9 |
2d9baab4 OD |
34 | def remove_prefix(string, prefix): |
35 | if string.startswith(prefix): | |
36 | return string[len(prefix):] | |
37 | return string | |
38 | ||
74c0b9b3 OD |
39 | class EnumValue: |
40 | ||
41 | def __init__(self, ev): | |
42 | self.name = ev.spelling | |
43 | self.value = ev.enum_value | |
44 | ||
45 | class EnumType: | |
46 | ||
47 | def __init__(self, en, name=None): | |
48 | self.name = name or en.spelling | |
49 | self.values = [EnumValue(ev) for ev in en.get_children()] | |
50 | self.en = en | |
51 | ||
52 | class Typedef: | |
53 | ||
54 | def __init__(self, spelling, value): | |
55 | self.spelling = spelling | |
56 | self.value = value | |
57 | ||
58 | class ArgumentType: | |
59 | ||
60 | integer_set = { | |
61 | clang.cindex.TypeKind.UCHAR, | |
62 | clang.cindex.TypeKind.USHORT, | |
63 | clang.cindex.TypeKind.UINT, | |
64 | clang.cindex.TypeKind.ULONG, | |
65 | clang.cindex.TypeKind.ULONGLONG, | |
66 | clang.cindex.TypeKind.SHORT, | |
67 | clang.cindex.TypeKind.INT, | |
68 | clang.cindex.TypeKind.LONG, | |
69 | clang.cindex.TypeKind.LONGLONG, | |
70 | } | |
71 | ||
72 | float_set = { | |
73 | clang.cindex.TypeKind.FLOAT, | |
74 | clang.cindex.TypeKind.DOUBLE, | |
75 | } | |
76 | ||
77 | address_set = { | |
78 | clang.cindex.TypeKind.POINTER, | |
79 | clang.cindex.TypeKind.INCOMPLETEARRAY, | |
80 | } | |
81 | ||
82 | def __init__(self, arg, name_prefix="", expr_prefix=""): | |
83 | self.type = arg.type | |
84 | self.arg = arg | |
85 | self.const = "" | |
86 | self.name_prefix = name_prefix | |
87 | self.expr_prefix = expr_prefix | |
88 | ||
89 | if self.kind() == clang.cindex.TypeKind.POINTER: | |
90 | if self.type.get_pointee().is_const_qualified(): | |
91 | self.const = "const " | |
92 | elif self.type.is_const_qualified(): | |
93 | self.const = "const " | |
94 | ||
95 | def name(self): | |
96 | return self.arg.spelling | |
97 | ||
98 | def type_name(self): | |
99 | if self.kind() == clang.cindex.TypeKind.INCOMPLETEARRAY: | |
100 | return self.const + re.sub(r"\[[0-9]*\]", "*", self.type.spelling) | |
101 | if self.kind() == clang.cindex.TypeKind.POINTER: | |
102 | return f"{self.const}void *" | |
103 | return self.const + self.type.spelling | |
104 | ||
105 | def kind(self): | |
106 | return self.type.get_canonical().kind | |
107 | ||
108 | def to_lttng_field(self): | |
109 | if self.name() == "reserved": | |
110 | return "" | |
111 | elif self.kind() in ArgumentType.address_set: | |
112 | return f"lttng_ust_field_integer_hex(uintptr_t, {self.name_prefix}{self.name()}, (uintptr_t){self.expr_prefix}{self.name()})" | |
113 | elif self.kind() in ArgumentType.integer_set: | |
114 | return f"lttng_ust_field_integer({self.type_name()}, {self.name_prefix}{self.name()}, {self.expr_prefix}{self.name()})" | |
115 | elif self.kind() in ArgumentType.float_set: | |
116 | return f"lttng_ust_field_float({self.type_name()}, {self.name_prefix}{self.name()}, {self.expr_prefix}{self.name()})" | |
117 | elif self.kind() == clang.cindex.TypeKind.ENUM: | |
2d9baab4 | 118 | enum_name = remove_prefix(self.type_name(), "enum ") |
74c0b9b3 OD |
119 | return f"lttng_ust_field_enum({PROVIDER}, {enum_name}, int, {self.name_prefix}{self.name()}, {self.expr_prefix}{self.name()})" |
120 | elif self.kind() == clang.cindex.TypeKind.RECORD: | |
121 | return [ | |
122 | ArgumentType(field, f"{self.name()}_", f"{self.expr_prefix}{self.name()}.").to_lttng_field() | |
123 | for field in self.type.get_canonical().get_fields() | |
124 | ] | |
125 | else: | |
126 | raise Exception("Unsupported kind: %s" % self.kind()) | |
127 | ||
128 | class FunctionType: | |
129 | ||
130 | struct_tpl = Template(""" | |
131 | $name { | |
132 | $fields | |
133 | }; | |
134 | """) | |
135 | ||
136 | def __init__(self, fn): | |
137 | self.name = fn.spelling | |
138 | self.args = [ArgumentType(arg) for arg in fn.get_arguments()] | |
139 | self.fn = fn | |
140 | ||
141 | def tp_args(self): | |
142 | if len(self.args) == 0: | |
143 | return "" | |
144 | elif len(self.args) > MAX_TP_ARGS_COUNT: | |
145 | return ",\n " + f"{self.arguments_struct_name()} *, lttng_args" | |
146 | else: | |
147 | return ",\n " + ",\n ".join([f"{arg.type_name()}, {arg.name()}" | |
148 | for arg in self.args]) | |
149 | ||
150 | def tp_fields(self): | |
151 | if len(self.args) == 0: | |
152 | return "" | |
153 | elif len(self.args) > MAX_TP_ARGS_COUNT: | |
154 | packed_args = [ArgumentType(arg.arg, "", "lttng_args->") for arg in self.args] | |
155 | return "\n ".join(flatten([arg.to_lttng_field() | |
156 | for arg in packed_args])) | |
157 | else: | |
158 | return "\n ".join(flatten([arg.to_lttng_field() | |
159 | for arg in self.args])) | |
160 | def get_return_type_name(self): | |
161 | return self.fn.type.get_result().spelling | |
162 | ||
163 | def ctor_params(self): | |
164 | if len(self.args) == 0: | |
165 | return "" | |
166 | elif len(self.args) > MAX_TP_ARGS_COUNT: | |
167 | return ", <tng_args" | |
168 | else: | |
169 | return ", " + ", ".join(arg.name() for arg in self.args) | |
170 | ||
171 | def arguments_struct_variable(self): | |
172 | if len(self.args) > MAX_TP_ARGS_COUNT: | |
173 | return "%s lttng_args = {%s};" % (self.arguments_struct_name(), | |
174 | ", ".join([arg.name() for arg in self.args])) | |
175 | else: | |
176 | return f"/* {self.arguments_struct_name()} lttng_args */" | |
177 | ||
178 | ||
179 | def arguments_struct_name(self): | |
180 | return f"struct lttng_arguments_of_{self.name}" | |
181 | ||
182 | def arguments_struct(self): | |
183 | if len(self.args) > MAX_TP_ARGS_COUNT: | |
184 | return self.struct_tpl.substitute(name=self.arguments_struct_name(), | |
185 | fields="\n ".join([ | |
186 | f"{arg.type_name()} {arg.name()};" | |
187 | for arg in self.args | |
188 | ])) | |
189 | else: | |
190 | return "" | |
191 | ||
192 | def flatten(lst): | |
193 | new_lst = [] | |
194 | for e in lst: | |
195 | if isinstance(e, list): | |
196 | for e in flatten(e): | |
197 | new_lst.append(e) | |
198 | else: | |
199 | new_lst.append(e) | |
200 | return new_lst | |
201 | ||
202 | def list_function_declarations(root): | |
203 | return [ child | |
204 | for child in root.get_children() | |
205 | if child.kind == clang.cindex.CursorKind.FUNCTION_DECL ] | |
206 | ||
207 | def list_enum_declarations(root): | |
208 | return [ | |
209 | child | |
210 | for child in root.get_children() | |
211 | if child.kind == clang.cindex.CursorKind.ENUM_DECL | |
212 | ] | |
213 | ||
214 | def list_typedef_enums(root): | |
215 | enums = [] | |
216 | for child in root.get_children(): | |
217 | if child.kind == clang.cindex.CursorKind.TYPEDEF_DECL: | |
218 | maybe_enum = child.underlying_typedef_type.get_declaration() | |
219 | if maybe_enum.kind == clang.cindex.CursorKind.ENUM_DECL: | |
220 | enums.append(Typedef(child.spelling, maybe_enum)) | |
221 | return enums | |
222 | ||
223 | def search_header_in(name, paths): | |
224 | for path in paths.split(":"): | |
225 | for dirpath, _, files in os.walk(path, followlinks=True): | |
226 | for file in files: | |
227 | if file == name: | |
228 | return os.path.join(dirpath, file) | |
229 | return None | |
230 | ||
231 | def search_c_header(name): | |
232 | return search_header_in(name, os.environ["C_INCLUDE_PATH"]) | |
233 | ||
234 | def search_cxx_header(name): | |
235 | return search_header_in(name, os.environ["CPLUS_INCLUDE_PATH"]) | |
236 | ||
237 | def get_system_include_paths(): | |
238 | ||
239 | clang_args = ["clang", "-v", "-c", "-xc", "/dev/null", "-o", "/dev/null"] | |
240 | paths = [] | |
241 | ||
242 | with subprocess.Popen(clang_args, stderr=subprocess.PIPE, | |
243 | encoding="ascii") as proc: | |
244 | start_sys_search = False | |
245 | for line in proc.stderr: | |
246 | if start_sys_search: | |
247 | if line == "End of search list.\n": | |
248 | break | |
249 | paths.append("-isystem") | |
250 | paths.append(line.strip()) | |
251 | elif line == "#include <...> search starts here:\n": | |
252 | start_sys_search = True | |
253 | ||
254 | return paths | |
255 | ||
256 | def parse_header(header_file, includes, defines, | |
257 | required_c_headers, required_cxx_headers): | |
258 | ||
259 | args = get_system_include_paths() | |
260 | ||
261 | if includes: | |
262 | for inc in includes: | |
263 | args.append("-I") | |
264 | args.append(inc) | |
265 | ||
266 | if defines: | |
267 | for d in defines: | |
268 | args.append("-D") | |
269 | args.append(d) | |
270 | ||
271 | for header in required_c_headers: | |
272 | found = search_c_header(header) | |
273 | if found: | |
274 | args.append("-I") | |
275 | args.append(os.path.dirname(found)) | |
276 | ||
277 | for header in required_cxx_headers: | |
278 | found = search_cxx_header(header) | |
279 | if found: | |
280 | args.append("-I") | |
281 | args.append(os.path.dirname(found)) | |
282 | ||
283 | tu = clang.cindex.Index.create().parse(header_file, args=args) | |
284 | ||
285 | for d in tu.diagnostics: | |
286 | print(f"WARNING: {d}") | |
287 | ||
288 | return tu.cursor | |
289 | ||
290 | def list_functions(root): | |
291 | return [ | |
292 | FunctionType(fn) | |
293 | for fn in list_function_declarations(root) | |
294 | if fn.spelling.startswith(COMMON_PREFIX) and fn.spelling not in IGNORE | |
295 | ] | |
296 | ||
297 | def list_enums(root): | |
298 | ||
299 | enums = [ | |
300 | en | |
301 | for en in list_enum_declarations(root) | |
302 | if en.spelling.startswith(COMMON_PREFIX) and en.spelling not in IGNORE | |
303 | ] | |
304 | ||
305 | typedef_enums = [ | |
306 | typedef | |
307 | for typedef in list_typedef_enums(root) | |
308 | if typedef.spelling.startswith(COMMON_PREFIX) and | |
309 | typedef.spelling not in IGNORE and | |
310 | typedef.value.get_definition() not in enums | |
311 | ] | |
312 | ||
313 | all_enums = ([ EnumType(e) for e in enums ] + | |
314 | [ EnumType(td.value, td.spelling) for td in typedef_enums]) | |
315 | ||
316 | return all_enums | |
317 | ||
318 | def generate_tracepoint_definitions(function_declarations, enum_declarations, | |
319 | api_file, output_defs, output_interface, | |
320 | header_guard): | |
321 | defs_tpl = Template("""/* Auto-generated file! */ | |
322 | #undef LTTNG_UST_TRACEPOINT_PROVIDER | |
323 | #define LTTNG_UST_TRACEPOINT_PROVIDER $provider | |
324 | ||
325 | #undef LTTNG_UST_TRACEPOINT_INCLUDE | |
326 | #define LTTNG_UST_TRACEPOINT_INCLUDE "$output_defs" | |
327 | ||
328 | #if !defined($header_guard) | |
329 | #include <$api_file> | |
330 | $pass_by_struct | |
331 | #endif | |
332 | ||
333 | #if !defined($header_guard) || defined(LTTNG_UST_TRACEPOINT_HEADER_MULTI_READ) | |
334 | #define $header_guard | |
335 | ||
336 | #include <lttng/tracepoint.h> | |
337 | ||
338 | $enum_definitions | |
339 | $tracepoint_definitions | |
340 | ||
341 | #endif /* $header_guard */ | |
342 | ||
343 | #include <lttng/tracepoint-event.h> | |
344 | """) | |
345 | ||
346 | interface_tpl = Template("""/* Auto-generated file! */ | |
347 | #ifndef ${header_guard}_IMPL | |
348 | #define ${header_guard}_IMPL | |
349 | ||
350 | #include "${output_defs}" | |
351 | ||
352 | #endif /* ${header_guard}_IMPL */ | |
353 | """) | |
354 | ||
355 | tp_tpl = Template(""" | |
356 | LTTNG_UST_TRACEPOINT_EVENT( | |
357 | $provider, | |
358 | ${name}_entry, | |
359 | LTTNG_UST_TP_ARGS( | |
360 | uint64_t, lttng_thread_id, | |
361 | uint64_t, lttng_local_id$tp_args | |
362 | ), | |
363 | LTTNG_UST_TP_FIELDS( | |
364 | lttng_ust_field_integer(uint64_t, lttng_thread_id, lttng_thread_id) | |
365 | lttng_ust_field_integer(uint64_t, lttng_local_id, lttng_local_id) | |
366 | $tp_fields | |
367 | ) | |
368 | ) | |
369 | """) | |
370 | ||
371 | tp_ret_tpl = Template(""" | |
372 | LTTNG_UST_TRACEPOINT_EVENT( | |
373 | $provider, | |
374 | ${name}_exit, | |
375 | LTTNG_UST_TP_ARGS( | |
376 | uint64_t, lttng_thread_id, | |
377 | uint64_t, lttng_local_id, | |
378 | int, lttng_has_ret, | |
379 | $ret_type, lttng_ret | |
380 | ), | |
381 | LTTNG_UST_TP_FIELDS( | |
382 | lttng_ust_field_integer(uint64_t, lttng_thread_id, lttng_thread_id) | |
383 | lttng_ust_field_integer(uint64_t, lttng_local_id, lttng_local_id) | |
384 | lttng_ust_field_integer(int, lttng_has_ret, lttng_has_ret) | |
385 | lttng_ust_field_integer($ret_type, lttng_ret, lttng_ret) | |
386 | ) | |
387 | ) | |
388 | """) | |
389 | ||
390 | tp_void_tpl = Template(""" | |
391 | LTTNG_UST_TRACEPOINT_EVENT( | |
392 | $provider, | |
393 | ${name}_exit, | |
394 | LTTNG_UST_TP_ARGS( | |
395 | uint64_t, lttng_thread_id, | |
396 | uint64_t, lttng_local_id, | |
397 | int, lttng_has_ret | |
398 | ), | |
399 | LTTNG_UST_TP_FIELDS( | |
400 | lttng_ust_field_integer(uint64_t, lttng_thread_id, lttng_thread_id) | |
401 | lttng_ust_field_integer(uint64_t, lttng_local_id, lttng_local_id) | |
402 | lttng_ust_field_integer(int, lttng_has_ret, lttng_has_ret) | |
403 | ) | |
404 | ) | |
405 | """) | |
406 | enum_tpl = Template(""" | |
407 | LTTNG_UST_TRACEPOINT_ENUM($provider, $name, | |
408 | LTTNG_UST_TP_ENUM_VALUES( | |
409 | $values | |
410 | ) | |
411 | ) | |
412 | """) | |
413 | with open(output_defs, "w") as output: | |
414 | definitions = [] | |
415 | for fn in function_declarations: | |
416 | ret_type = fn.get_return_type_name() | |
417 | definitions.append(tp_tpl.substitute(provider=PROVIDER, | |
418 | name=fn.name, | |
419 | tp_args=fn.tp_args(), | |
420 | tp_fields=fn.tp_fields())) | |
421 | if ret_type == "void": | |
422 | tpl = tp_void_tpl | |
423 | else: | |
424 | tpl = tp_ret_tpl | |
425 | ||
426 | definitions.append(tpl.substitute(provider=PROVIDER, | |
427 | name=fn.name, | |
428 | ret_type=ret_type)) | |
429 | ||
430 | tracepoint_definitions = "\n".join(definitions) | |
431 | ||
432 | enum_definitions = "\n".join([ | |
433 | enum_tpl.substitute(provider=PROVIDER, | |
434 | name=en.name, | |
435 | values="\n ".join([f'lttng_ust_field_enum_value("{ev.name}", {ev.value})' | |
436 | for ev in en.values])) | |
437 | for en in enum_declarations | |
438 | ]) | |
439 | ||
440 | output.write(defs_tpl.substitute(provider=PROVIDER, | |
441 | output_defs=output_defs, | |
442 | header_guard=header_guard, | |
443 | tracepoint_definitions=tracepoint_definitions, | |
444 | enum_definitions=enum_definitions, | |
445 | api_file=api_file, | |
446 | pass_by_struct="".join([fn.arguments_struct() | |
447 | for fn in function_declarations]))) | |
448 | with open(output_interface, "w") as output: | |
449 | output.write(interface_tpl.substitute(header_guard=header_guard, | |
450 | output_defs=output_defs,)) | |
451 | ||
452 | def generate_tracepoint_classes(function_declarations, api_file, output_path, header_guard, namespace): | |
453 | global_tpl = Template("""/* Auto-generated file! */ | |
454 | #include <atomic> | |
455 | #include <cstdint> | |
456 | #include <$api_file> | |
457 | namespace $namespace { | |
458 | struct unique_id { | |
459 | uint64_t thread_id; | |
460 | uint64_t local_id; | |
461 | }; | |
462 | ||
463 | class id_generator { | |
464 | static std::atomic<uint64_t> _thread_counter; | |
465 | uint64_t _thread_id; | |
466 | uint64_t _local_id; | |
467 | public: | |
468 | id_generator() { | |
469 | _thread_id = _thread_counter++; | |
470 | _local_id = 0; | |
471 | } | |
472 | ||
473 | unique_id next_id() { | |
474 | return { | |
475 | .thread_id = _thread_id, | |
476 | .local_id = _local_id++, | |
477 | }; | |
478 | } | |
479 | }; | |
480 | ||
481 | extern thread_local id_generator generator; | |
482 | ||
483 | template<typename RetType> | |
484 | class base_api_object { | |
485 | protected: | |
486 | unique_id _id; | |
487 | int _has_ret; | |
488 | RetType _ret; | |
489 | public: | |
490 | void generate_id() { | |
491 | _id = generator.next_id(); | |
492 | } | |
493 | ||
494 | void mark_return(RetType ret) { | |
495 | _ret = ret; | |
496 | _has_ret = 1; | |
497 | } | |
498 | }; | |
499 | ||
500 | class base_api_object_void { | |
501 | protected: | |
502 | unique_id _id; | |
503 | int _has_ret; | |
504 | public: | |
505 | void generate_id() { | |
506 | _id = generator.next_id(); | |
507 | } | |
508 | ||
509 | void mark_return(void) { | |
510 | _has_ret = 1; | |
511 | } | |
512 | }; | |
513 | ||
514 | $classes | |
515 | }; | |
516 | """) | |
517 | ||
518 | cls_ret_tpl = Template(""" | |
519 | class api_object_$fn_name : public base_api_object<$ret_type> | |
520 | { | |
521 | public: | |
522 | api_object_$fn_name($ctor_type_params) { | |
523 | if (lttng_ust_tracepoint_enabled($provider, ${fn_name}_entry)) { | |
524 | generate_id(); | |
525 | $pass_by_struct | |
526 | lttng_ust_do_tracepoint($provider, | |
527 | ${fn_name}_entry, | |
528 | _id.thread_id, | |
529 | _id.local_id$ctor_params); | |
530 | } | |
531 | } | |
532 | ~api_object_$fn_name() { | |
533 | if (lttng_ust_tracepoint_enabled($provider, ${fn_name}_exit)) { | |
534 | lttng_ust_do_tracepoint($provider, | |
535 | ${fn_name}_exit, | |
536 | _id.thread_id, | |
537 | _id.local_id, | |
538 | _has_ret, | |
539 | _ret); | |
540 | } | |
541 | } | |
542 | }; | |
543 | """) | |
544 | ||
545 | cls_void_tpl = Template(""" | |
546 | class api_object_$fn_name : public base_api_object_void | |
547 | { | |
548 | public: | |
549 | api_object_$fn_name($ctor_type_params) { | |
550 | if (lttng_ust_tracepoint_enabled($provider, ${fn_name}_entry)) { | |
551 | generate_id(); | |
552 | $pass_by_struct | |
553 | lttng_ust_do_tracepoint($provider, | |
554 | ${fn_name}_entry, | |
555 | _id.thread_id, | |
556 | _id.local_id$ctor_params); | |
557 | } | |
558 | } | |
559 | ~api_object_$fn_name() { | |
560 | if (lttng_ust_tracepoint_enabled($provider, ${fn_name}_exit)) { | |
561 | lttng_ust_do_tracepoint($provider, | |
562 | ${fn_name}_exit, | |
563 | _id.thread_id, | |
564 | _id.local_id, | |
565 | _has_ret); | |
566 | } | |
567 | } | |
568 | }; | |
569 | """) | |
570 | ||
571 | with open(output_path, "w") as output: | |
572 | classes = [] | |
573 | for fn in function_declarations: | |
574 | ret_type = fn.get_return_type_name() | |
575 | if ret_type == "void": | |
576 | cls_tpl = cls_void_tpl | |
577 | else: | |
578 | cls_tpl = cls_ret_tpl | |
579 | classes.append(cls_tpl.substitute(provider=PROVIDER, | |
580 | fn_name=fn.name, | |
581 | pass_by_struct=fn.arguments_struct_variable(), | |
582 | ctor_type_params=", ".join([f"{arg.type_name()} {arg.name()}" | |
583 | for arg in fn.args]), | |
584 | ctor_params=fn.ctor_params(), | |
585 | ret_type=ret_type)) | |
586 | output.write(global_tpl.substitute(api_file=api_file, | |
587 | namespace=namespace, | |
588 | classes="".join(classes))) | |
589 | ||
590 | def generate_tracepoint_emulated_classes(function_declarations, api_file, output_path, | |
591 | header_guard, namespace): | |
592 | global_tpl = Template("""/* Auto-generated file! */ | |
593 | #include <stdint.h> | |
594 | #include <$api_file> | |
595 | #define ${NAMESPACE}_CAT_PRIMITIVE(A, B) A##B | |
596 | #define ${NAMESPACE}_CAT(A, B) ${NAMESPACE}_CAT_PRIMITIVE(A, B) | |
597 | ||
598 | struct ${namespace}_unique_id { | |
599 | uint64_t thread_id; | |
600 | uint64_t local_id; | |
601 | }; | |
602 | ||
603 | struct ${namespace}_id_generator { | |
604 | uint64_t thread_id; | |
605 | uint64_t local_id; | |
606 | int initialized; | |
607 | }; | |
608 | ||
609 | extern uint64_t ${namespace}_id_generator_thread_counter; | |
610 | extern _Thread_local struct ${namespace}_id_generator ${namespace}_generator; | |
611 | ||
612 | #define ${namespace}_unlikely(x) __builtin_expect(!!(x), 0) | |
613 | ||
614 | static inline void ${namespace}_id_generator_next_id(struct ${namespace}_unique_id *id) | |
615 | { | |
616 | if (${namespace}_unlikely(!${namespace}_generator.initialized)) { | |
617 | ${namespace}_generator.thread_id = | |
618 | __atomic_fetch_add(&${namespace}_id_generator_thread_counter, | |
619 | 1, | |
620 | __ATOMIC_RELAXED); | |
621 | ${namespace}_generator.initialized = 1; | |
622 | } | |
623 | ||
624 | id->thread_id = ${namespace}_generator.thread_id; | |
625 | id->local_id = ${namespace}_generator.local_id++; | |
626 | } | |
627 | ||
628 | #define ${NAMESPACE}_API_OBJECT_NAME ${namespace}_api_object | |
629 | ||
630 | #define ${NAMESPACE}_MAKE_API_OBJECT(name, ...) \\ | |
631 | struct ${NAMESPACE}_CAT(${namespace}_api_state_, name) __attribute__((cleanup(${NAMESPACE}_CAT(name, _exit)))) \\ | |
632 | ${NAMESPACE}_API_OBJECT_NAME = { 0 }; \\ | |
633 | ${NAMESPACE}_CAT(name, _entry)(&${NAMESPACE}_API_OBJECT_NAME, ##__VA_ARGS__); \\ | |
634 | do { } while (0) | |
635 | ||
636 | #define ${NAMESPACE}_MARK_RETURN_API_OBJECT(code) \\ | |
637 | ({ \\ | |
638 | ${NAMESPACE}_API_OBJECT_NAME.ret = code; \\ | |
639 | ${NAMESPACE}_API_OBJECT_NAME.has_ret = 1; \\ | |
640 | }) | |
641 | ${classes} | |
642 | """) | |
643 | ||
644 | cls_tpl = Template(""" | |
645 | struct ${namespace}_api_state_${fn_name} { | |
646 | struct ${namespace}_unique_id id; | |
647 | int has_ret; | |
648 | $ret_type ret; | |
649 | }; | |
650 | ||
651 | static inline void ${fn_name}_entry(${ctor_type_params}) | |
652 | { | |
653 | if (${namespace}_ust_tracepoint_enabled(${provider}, ${fn_name}_entry)) { | |
654 | ${namespace}_id_generator_next_id(<tng_state->id); | |
655 | ${pass_by_struct} | |
656 | ${namespace}_ust_do_tracepoint($provider, ${fn_name}_entry, | |
657 | lttng_state->id.thread_id, | |
658 | lttng_state->id.local_id${ctor_params}); | |
659 | } | |
660 | } | |
661 | ||
662 | static inline void ${fn_name}_exit(const struct ${namespace}_api_state_${fn_name} *lttng_state) | |
663 | { | |
664 | lttng_ust_tracepoint(${provider}, ${fn_name}_exit, | |
665 | lttng_state->id.thread_id, | |
666 | lttng_state->id.local_id, | |
667 | lttng_state->has_ret, | |
668 | lttng_state->ret); | |
669 | } | |
670 | """) | |
671 | with open(output_path, "w") as output: | |
672 | output.write(global_tpl.substitute(api_file=api_file, | |
673 | namespace=namespace, | |
674 | NAMESPACE=namespace.upper(), | |
675 | classes="".join([ | |
676 | cls_tpl.substitute(provider=PROVIDER, | |
677 | fn_name=fn.name, | |
678 | pass_by_struct=fn.arguments_struct_variable(), | |
679 | ctor_params=fn.ctor_params(), | |
680 | ctor_type_params=", ".join([f"struct {namespace}_api_state_{fn.name} *lttng_state"] + | |
681 | [f"{arg.type_name()} {arg.name()}" | |
682 | for arg in fn.args]), | |
683 | namespace=namespace, | |
684 | NAMESPACE=namespace.upper(), | |
685 | ret_type=fn.get_return_type_name()) | |
686 | for fn in function_declarations | |
687 | ]))) | |
688 | ||
74c0b9b3 OD |
689 | def generate_tracepoint_implementations(namespace, defs, impls): |
690 | tpl = Template("""/* Auto-generated !*/ | |
691 | #define LTTNG_UST_TRACEPOINT_CREATE_PROBES | |
692 | #define LTTNG_UST_TRACEPOINT_DEFINE | |
693 | #include "${defs}" | |
694 | """) | |
695 | ||
696 | with open(impls, "w") as output: | |
697 | output.write(tpl.substitute(defs=defs)) | |
698 | ||
699 | def generate_tracepoint_states(namespace, | |
700 | interface, | |
701 | classes, | |
702 | states, | |
703 | emulated_classes): | |
704 | ||
705 | if emulated_classes: | |
706 | body_tpl = Template(""" | |
707 | uint64_t ${namespace}_id_generator_thread_counter = 0; | |
708 | _Thread_local struct ${namespace}_id_generator ${namespace}_generator; | |
709 | """) | |
710 | else: | |
711 | body_tpl = Template(""" | |
712 | #include <atomic> | |
713 | namespace ${namespace} { | |
714 | std::atomic<uint64_t> id_generator::_thread_counter{0}; | |
715 | thread_local id_generator generator; | |
716 | }; | |
717 | """) | |
718 | ||
719 | tpl = Template("""/* Auto-generated! */ | |
720 | #include "${interface}" | |
721 | #include "${classes}" | |
722 | ||
723 | $body | |
724 | """) | |
725 | ||
726 | with open(states, "w") as output: | |
727 | output.write(tpl.substitute(interface=interface, | |
728 | classes=classes, | |
729 | body=body_tpl.substitute(namespace=namespace))) | |
730 | ||
731 | def main(): | |
732 | ||
733 | global COMMON_PREFIX | |
734 | global IGNORE | |
735 | global PROVIDER | |
736 | ||
737 | parser = argparse.ArgumentParser(prog="lttng-ust-autogen-api", | |
738 | description="Generate LTTng classes and tracepoint definitions") | |
739 | ||
740 | parser.add_argument("api", | |
741 | help="Header file that has the API") | |
742 | ||
743 | parser.add_argument("defs", | |
744 | help="Path to tracepoint definitions") | |
745 | ||
746 | parser.add_argument("interface", | |
747 | help="Path to tracepoints interfaces") | |
748 | ||
749 | parser.add_argument("classes", | |
750 | help="Path to tracepoint classes") | |
751 | ||
752 | parser.add_argument("impl", | |
753 | help="Path to tracepoint implementations") | |
754 | ||
755 | parser.add_argument("states", | |
756 | help="Path to states") | |
757 | ||
758 | parser.add_argument("--provider", | |
759 | dest="provider", | |
760 | metavar="PROVIDER", | |
761 | default="noprovider", | |
762 | help="Tracepoints PROVIDER") | |
763 | ||
764 | parser.add_argument("--common-prefix", | |
765 | dest="common_prefix", | |
766 | metavar="PREFIX", | |
767 | default="", | |
768 | help="Common PREFIX of API functions (C namespace)") | |
769 | ||
770 | parser.add_argument("-I", | |
771 | action="append", | |
772 | metavar="DIR", | |
773 | dest="includes", | |
774 | help="Add DIR to list of directories to include") | |
775 | ||
776 | parser.add_argument("-D", | |
777 | action="append", | |
778 | metavar="DEFINITION", | |
779 | dest="defines", | |
780 | help="Add DEFINITION to list of definitions") | |
781 | ||
782 | parser.add_argument("--tp-guard", | |
783 | dest="tp_guard", | |
784 | metavar="GUARD", | |
785 | default="LTTNG_TRACEPOINT_DEF_H", | |
786 | help="Use GUARD as header guard for tracepoint definitions") | |
787 | ||
788 | parser.add_argument("--classes-guard", | |
789 | dest="classes_guard", | |
790 | metavar="GUARD", | |
791 | default="LTTNG_TRACEPOINT_CLASSES_HPP", | |
792 | help="Use GUARD as header guard for classes definitions") | |
793 | ||
794 | parser.add_argument("--emulated-classes", | |
795 | dest="emulated_classes", | |
796 | action="store_true", | |
797 | default=False, | |
798 | help="Emulate C++ classes") | |
799 | ||
800 | parser.add_argument("--namespace", | |
801 | dest="namespace", | |
802 | metavar="NAMESPACE", | |
803 | default="lttng", | |
804 | help="Generate classes in NAMESPACE") | |
805 | ||
806 | parser.add_argument("--ignores", | |
807 | dest="ignores", | |
808 | metavar="FILE", | |
809 | default=None, | |
810 | help="Ignore functions listed in FILE") | |
811 | ||
812 | parser.add_argument("--c-header", | |
813 | dest="required_c_headers", | |
814 | metavar="HEADER", | |
815 | action="append", | |
816 | default=[], | |
817 | help="Search for HEADER in C_INCLUDE_PATH and add its directory to search path") | |
818 | ||
819 | parser.add_argument("--cxx-header", | |
820 | dest="required_cxx_headers", | |
821 | metavar="HEADER", | |
822 | action="append", | |
823 | default=[], | |
824 | help="Search for HEADER in CPLUS_INCLUDE_PATH add its directory to search path") | |
825 | ||
826 | args = parser.parse_args() | |
827 | ||
828 | PROVIDER = args.provider | |
829 | COMMON_PREFIX = args.common_prefix | |
830 | ||
831 | if args.ignores: | |
832 | with open(args.ignores, "r") as f: | |
833 | for ignore in f.read().splitlines(): | |
834 | IGNORE.add(ignore) | |
835 | ||
836 | root = parse_header(args.api, args.includes, args.defines, | |
837 | args.required_c_headers, | |
838 | args.required_cxx_headers) | |
839 | ||
840 | function_declarations = list_functions(root) | |
841 | enum_declarations = list_enums(root) | |
842 | ||
843 | generate_tracepoint_definitions(function_declarations, | |
844 | enum_declarations, | |
845 | args.api, args.defs, args.interface, | |
846 | args.tp_guard) | |
847 | ||
848 | if args.emulated_classes: | |
849 | generate_tracepoint_emulated_classes(function_declarations, | |
850 | args.api, | |
851 | args.classes, | |
852 | args.classes_guard, | |
853 | args.namespace) | |
854 | else: | |
855 | generate_tracepoint_classes(function_declarations, | |
856 | args.api, | |
857 | args.classes, | |
858 | args.classes_guard, | |
859 | args.namespace) | |
860 | ||
861 | generate_tracepoint_implementations(args.namespace, | |
862 | args.interface, | |
863 | args.impl) | |
864 | ||
865 | generate_tracepoint_states(args.namespace, | |
866 | args.interface, | |
867 | args.classes, | |
868 | args.states, | |
869 | args.emulated_classes) | |
870 | ||
871 | ||
872 | if __name__ == "__main__": | |
873 | main() |