Commit | Line | Data |
---|---|---|
74c0b9b3 OD |
1 | #!/usr/bin/env python3 |
2 | # | |
c38914e1 MJ |
3 | # SPDX-FileCopyrightText: 2023 EfficiOS, Inc. |
4 | # | |
5 | # SPDX-License-Identifier: MIT | |
74c0b9b3 OD |
6 | # |
7 | # Author: Olivier Dion <odion@efficios.com> | |
8 | # | |
c75d6f3f | 9 | # Auto-generate lttng-ust wrappers for HIP. |
74c0b9b3 OD |
10 | # |
11 | # Require: python-clang (libclang) | |
12 | ||
13 | import argparse | |
14 | import re | |
15 | import subprocess | |
16 | ||
17 | from string import Template | |
18 | ||
19 | import clang.cindex | |
20 | ||
21 | def list_function_declarations(root): | |
c75d6f3f | 22 | """Return all function declarations under ROOT.""" |
74c0b9b3 OD |
23 | return [ |
24 | child | |
25 | for child in root.get_children() | |
26 | if child.kind == clang.cindex.CursorKind.FUNCTION_DECL | |
27 | ] | |
28 | ||
29 | def get_system_include_paths(): | |
c75d6f3f | 30 | """Get default system include paths from Clang.""" |
74c0b9b3 OD |
31 | clang_args = ["clang", "-v", "-c", "-xc", "-o", "/dev/null", "/dev/null"] |
32 | paths = [] | |
33 | ||
34 | with subprocess.Popen(clang_args, stderr=subprocess.PIPE, text=True) as proc: | |
35 | start_sys_search = False | |
36 | for line in proc.stderr: | |
37 | if start_sys_search: | |
38 | if line == "End of search list.\n": | |
39 | break | |
40 | paths.append("-isystem") | |
41 | paths.append(line.strip()) | |
42 | elif line == "#include <...> search starts here:\n": | |
43 | start_sys_search = True | |
44 | ||
45 | return paths | |
46 | ||
47 | def parse_header(header_file, includes, defines): | |
c75d6f3f OD |
48 | """ |
49 | Parse HEADER_FILE with Clang with INCLUDES (-I) and DEFINES (-D). | |
50 | Return a cursor to root. | |
51 | """ | |
52 | ||
53 | # For some reason, python-clang does not use all system include paths. | |
54 | # Thus, compiler dependant headers, like stddef.h can not be found without | |
55 | # this. | |
74c0b9b3 OD |
56 | args = get_system_include_paths() |
57 | ||
58 | if includes: | |
59 | for inc in includes: | |
60 | args.append("-I") | |
61 | args.append(inc) | |
62 | ||
63 | if defines: | |
64 | for d in defines: | |
65 | args.append("-D") | |
66 | args.append(d) | |
67 | ||
68 | tu = clang.cindex.Index.create().parse(header_file, args=args) | |
69 | ||
70 | for d in tu.diagnostics: | |
71 | print(f"WARNING: {d}") | |
72 | ||
73 | return tu.cursor | |
74 | ||
75 | def list_functions(root): | |
c75d6f3f | 76 | """Return the list of function declarations with `hip' prefix from ROOT.""" |
74c0b9b3 OD |
77 | return [ |
78 | fn | |
79 | for fn in list_function_declarations(root) | |
80 | if fn.spelling.startswith("hip") and fn.spelling | |
81 | ] | |
82 | ||
83 | def exact_definition(arg): | |
c75d6f3f | 84 | """Given a cursor ARG that is a function argument, return its exact definition.""" |
74c0b9b3 OD |
85 | ct = arg.type.get_canonical() |
86 | if ct.kind == clang.cindex.TypeKind.POINTER: | |
87 | pt = ct.get_pointee() | |
88 | if pt.kind == clang.cindex.TypeKind.FUNCTIONPROTO: | |
89 | ret_type = pt.get_result().spelling | |
90 | argument_types = ", ".join([a.spelling for a in pt.argument_types()]) | |
91 | return f"{ret_type} (*{arg.spelling})({argument_types})" | |
92 | m = re.search(r'(\[[0-9]*\])+', arg.type.spelling) | |
93 | if m: | |
94 | return f"{arg.type.spelling[:m.start(0)]} {arg.spelling}{m.group(0)}" | |
95 | else: | |
96 | return f"{arg.type.spelling} {arg.spelling}" | |
97 | ||
98 | def cast(arg): | |
c75d6f3f OD |
99 | """ |
100 | Cast argument ARG to something that LTTng-UST can consume. Typically, | |
101 | this is used to cast any pointer to void * because pointers are not | |
102 | dereferenced anyway. Furthermore, array are also cast void *. | |
103 | """ | |
74c0b9b3 OD |
104 | canon = arg.type.get_canonical() |
105 | if canon.kind == clang.cindex.TypeKind.POINTER: | |
106 | return "void *" | |
107 | return re.sub(r'\[[0-9]*\]', '*', canon.spelling) | |
108 | ||
74c0b9b3 OD |
109 | def main(): |
110 | ||
c75d6f3f OD |
111 | # Extra works to do for functions. |
112 | # | |
113 | # Format: | |
114 | # key = str: function name ; e.g. hipMalloc | |
115 | # value = str: C code ; e.g. printf("hello\n"); | |
116 | extra_works = { | |
117 | } | |
118 | ||
74c0b9b3 OD |
119 | parser = argparse.ArgumentParser(prog="gen-hip-wrappers") |
120 | ||
121 | parser.add_argument("api", | |
122 | help="HIP API header") | |
123 | ||
124 | parser.add_argument("wrappers", | |
125 | help="Path to HIP wrappers") | |
126 | ||
127 | parser.add_argument("--ignores", | |
128 | dest="ignores", | |
129 | metavar="FILE", | |
130 | default=None, | |
131 | help="Ignore list") | |
132 | ||
133 | parser.add_argument("-I", | |
134 | action="append", | |
135 | metavar="DIR", | |
136 | dest="includes", | |
137 | help="Add DIR to list of directories to include") | |
138 | ||
139 | parser.add_argument("-D", | |
140 | action="append", | |
141 | metavar="DEFINITION", | |
142 | dest="defines", | |
143 | help="Add DEFINITION to list of definitions") | |
144 | ||
145 | args = parser.parse_args() | |
146 | ||
c75d6f3f OD |
147 | # The set of function to not instrument. |
148 | forbiden_list = set() | |
149 | ||
74c0b9b3 OD |
150 | if args.ignores: |
151 | with open(args.ignores, "r") as f: | |
152 | for ignore in f.read().splitlines(): | |
153 | forbiden_list.add(ignore) | |
154 | ||
155 | prologue_tpl = Template("""/* Auto-generated */ | |
156 | #include "lttng-ust-hip-states.h" | |
157 | """) | |
158 | ||
159 | ret_fn_tpl = Template(""" | |
160 | static ${ret_type} lttng_${fn_name}(${fn_arguments}) | |
161 | { | |
162 | ${ret_type} ret; | |
163 | { | |
164 | lttng_hip::api_object_${fn_name} lttng_api_object {${fn_rest_argument_names}}; | |
165 | ret = next_hip_table.${fn_name}_fn(${fn_pass_argument_names}); | |
166 | lttng_api_object.mark_return(ret); | |
167 | } | |
168 | $extra_work | |
169 | return ret; | |
170 | } | |
171 | """) | |
172 | ||
c75d6f3f | 173 | # Void function are special because they do not return anything .. |
74c0b9b3 OD |
174 | void_fn_tpl = Template(""" |
175 | static void lttng_${fn_name}(${fn_arguments}) | |
176 | { | |
177 | { | |
178 | lttng_hip::api_object_${fn_name} lttng_api_object {${fn_rest_argument_names}}; | |
179 | next_hip_table.${fn_name}_fn(${fn_pass_argument_names}); | |
180 | lttng_api_object.mark_return(); | |
181 | } | |
182 | $extra_work | |
183 | } | |
184 | """) | |
185 | ||
c75d6f3f | 186 | # Because C++ does not support designated initializer. |
74c0b9b3 OD |
187 | epilogue_tpl = Template(""" |
188 | static void lttng_hip_install_wrappers(void) | |
189 | { | |
190 | ${wrappers} | |
191 | } | |
192 | """) | |
193 | ||
194 | functions = list_functions(parse_header(args.api, | |
195 | args.includes, | |
196 | args.defines)) | |
197 | ||
198 | with open(args.wrappers, "w") as output: | |
199 | ||
200 | output.write(prologue_tpl.substitute()) | |
201 | ||
202 | for fn in functions: | |
203 | ||
204 | if fn.spelling in forbiden_list: | |
205 | continue | |
206 | ||
207 | args = list(fn.get_arguments()) | |
208 | fn_pass_argument_names = ", ".join([ | |
209 | f"{arg.spelling}" | |
210 | for arg in args | |
211 | ]) | |
212 | ||
213 | if args: | |
214 | fn_rest_argument_names = ", ".join([ | |
215 | "(%s)%s" % (cast(arg), arg.spelling) | |
216 | for arg in args | |
217 | ]) | |
218 | else: | |
219 | fn_rest_argument_names="" | |
220 | ||
221 | if fn.spelling in extra_works: | |
222 | extra_work = extra_works[fn.spelling] | |
223 | else: | |
224 | extra_work = "" | |
225 | ||
226 | if "void"== fn.type.get_result().spelling: | |
227 | fn_tpl = void_fn_tpl | |
228 | else: | |
229 | fn_tpl = ret_fn_tpl | |
230 | ||
231 | output.write(fn_tpl.substitute(fn_name=fn.spelling, | |
232 | fn_arguments=", ".join([ | |
233 | exact_definition(arg) | |
234 | for arg in fn.get_arguments() | |
235 | ]), | |
236 | fn_pass_argument_names=fn_pass_argument_names, | |
237 | fn_rest_argument_names=fn_rest_argument_names, | |
238 | ret_type=fn.type.get_result().spelling, | |
239 | extra_work=extra_work)) | |
240 | ||
241 | output.write(epilogue_tpl.substitute(wrappers="\n ".join([ | |
242 | f"lttng_hip_table.{fn.spelling}_fn = <tng_{fn.spelling};" | |
243 | for fn in functions if fn.spelling not in forbiden_list | |
244 | ]))) | |
245 | ||
246 | ||
247 | if __name__ == "__main__": | |
248 | main() |