]>
Commit | Line | Data |
---|---|---|
aaaa20b6 VSO |
1 | #! /usr/bin/env python3 |
2 | """Generate coroutine wrappers for block subsystem. | |
3 | ||
4 | The program parses one or several concatenated c files from stdin, | |
76a2f554 | 5 | searches for functions with the 'co_wrapper' specifier |
aaaa20b6 VSO |
6 | and generates corresponding wrappers on stdout. |
7 | ||
8 | Usage: block-coroutine-wrapper.py generated-file.c FILE.[ch]... | |
9 | ||
10 | Copyright (c) 2020 Virtuozzo International GmbH. | |
11 | ||
12 | This program is free software; you can redistribute it and/or modify | |
13 | it under the terms of the GNU General Public License as published by | |
14 | the Free Software Foundation; either version 2 of the License, or | |
15 | (at your option) any later version. | |
16 | ||
17 | This program is distributed in the hope that it will be useful, | |
18 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | GNU General Public License for more details. | |
21 | ||
22 | You should have received a copy of the GNU General Public License | |
23 | along with this program. If not, see <http://www.gnu.org/licenses/>. | |
24 | """ | |
25 | ||
26 | import sys | |
27 | import re | |
28 | from typing import Iterator | |
29 | ||
30 | ||
31 | def gen_header(): | |
32 | copyright = re.sub('^.*Copyright', 'Copyright', __doc__, flags=re.DOTALL) | |
33 | copyright = re.sub('^(?=.)', ' * ', copyright.strip(), flags=re.MULTILINE) | |
34 | copyright = re.sub('^$', ' *', copyright, flags=re.MULTILINE) | |
35 | return f"""\ | |
36 | /* | |
37 | * File is generated by scripts/block-coroutine-wrapper.py | |
38 | * | |
39 | {copyright} | |
40 | */ | |
41 | ||
42 | #include "qemu/osdep.h" | |
43 | #include "block/coroutines.h" | |
44 | #include "block/block-gen.h" | |
45 | #include "block/block_int.h"\ | |
46 | """ | |
47 | ||
48 | ||
49 | class ParamDecl: | |
50 | param_re = re.compile(r'(?P<decl>' | |
51 | r'(?P<type>.*[ *])' | |
52 | r'(?P<name>[a-z][a-z0-9_]*)' | |
53 | r')') | |
54 | ||
55 | def __init__(self, param_decl: str) -> None: | |
56 | m = self.param_re.match(param_decl.strip()) | |
57 | if m is None: | |
58 | raise ValueError(f'Wrong parameter declaration: "{param_decl}"') | |
59 | self.decl = m.group('decl') | |
60 | self.type = m.group('type') | |
61 | self.name = m.group('name') | |
62 | ||
63 | ||
64 | class FuncDecl: | |
76a2f554 EGE |
65 | def __init__(self, return_type: str, name: str, args: str, |
66 | variant: str) -> None: | |
aaaa20b6 VSO |
67 | self.return_type = return_type.strip() |
68 | self.name = name.strip() | |
76a2f554 | 69 | self.struct_name = snake_to_camel(self.name) |
aaaa20b6 | 70 | self.args = [ParamDecl(arg.strip()) for arg in args.split(',')] |
76a2f554 EGE |
71 | self.create_only_co = 'mixed' not in variant |
72 | ||
73 | subsystem, subname = self.name.split('_', 1) | |
74 | self.co_name = f'{subsystem}_co_{subname}' | |
75 | ||
76 | t = self.args[0].type | |
77 | if t == 'BlockDriverState *': | |
78 | bs = 'bs' | |
79 | elif t == 'BdrvChild *': | |
80 | bs = 'child->bs' | |
81 | else: | |
82 | bs = 'blk_bs(blk)' | |
83 | self.bs = bs | |
aaaa20b6 VSO |
84 | |
85 | def gen_list(self, format: str) -> str: | |
86 | return ', '.join(format.format_map(arg.__dict__) for arg in self.args) | |
87 | ||
88 | def gen_block(self, format: str) -> str: | |
89 | return '\n'.join(format.format_map(arg.__dict__) for arg in self.args) | |
90 | ||
91 | ||
76a2f554 EGE |
92 | # Match wrappers declared with a co_wrapper mark |
93 | func_decl_re = re.compile(r'^int\s*co_wrapper' | |
94 | r'(?P<variant>(_[a-z][a-z0-9_]*)?)\s*' | |
aaaa20b6 VSO |
95 | r'(?P<wrapper_name>[a-z][a-z0-9_]*)' |
96 | r'\((?P<args>[^)]*)\);$', re.MULTILINE) | |
97 | ||
98 | ||
99 | def func_decl_iter(text: str) -> Iterator: | |
100 | for m in func_decl_re.finditer(text): | |
101 | yield FuncDecl(return_type='int', | |
102 | name=m.group('wrapper_name'), | |
76a2f554 EGE |
103 | args=m.group('args'), |
104 | variant=m.group('variant')) | |
aaaa20b6 VSO |
105 | |
106 | ||
107 | def snake_to_camel(func_name: str) -> str: | |
108 | """ | |
109 | Convert underscore names like 'some_function_name' to camel-case like | |
110 | 'SomeFunctionName' | |
111 | """ | |
112 | words = func_name.split('_') | |
113 | words = [w[0].upper() + w[1:] for w in words] | |
114 | return ''.join(words) | |
115 | ||
116 | ||
76a2f554 EGE |
117 | def create_mixed_wrapper(func: FuncDecl) -> str: |
118 | """ | |
119 | Checks if we are already in coroutine | |
120 | """ | |
121 | name = func.co_name | |
122 | struct_name = func.struct_name | |
123 | return f"""\ | |
124 | int {func.name}({ func.gen_list('{decl}') }) | |
125 | {{ | |
126 | if (qemu_in_coroutine()) {{ | |
127 | return {name}({ func.gen_list('{name}') }); | |
128 | }} else {{ | |
129 | {struct_name} s = {{ | |
130 | .poll_state.bs = {func.bs}, | |
131 | .poll_state.in_progress = true, | |
132 | ||
133 | { func.gen_block(' .{name} = {name},') } | |
134 | }}; | |
135 | ||
136 | s.poll_state.co = qemu_coroutine_create({name}_entry, &s); | |
137 | ||
138 | return bdrv_poll_co(&s.poll_state); | |
139 | }} | |
140 | }}""" | |
141 | ||
142 | ||
143 | def create_co_wrapper(func: FuncDecl) -> str: | |
144 | """ | |
145 | Assumes we are not in coroutine, and creates one | |
146 | """ | |
147 | name = func.co_name | |
148 | struct_name = func.struct_name | |
149 | return f"""\ | |
150 | int {func.name}({ func.gen_list('{decl}') }) | |
151 | {{ | |
152 | {struct_name} s = {{ | |
153 | .poll_state.bs = {func.bs}, | |
154 | .poll_state.in_progress = true, | |
155 | ||
156 | { func.gen_block(' .{name} = {name},') } | |
157 | }}; | |
158 | assert(!qemu_in_coroutine()); | |
159 | ||
160 | s.poll_state.co = qemu_coroutine_create({name}_entry, &s); | |
161 | ||
162 | return bdrv_poll_co(&s.poll_state); | |
163 | }}""" | |
164 | ||
165 | ||
aaaa20b6 | 166 | def gen_wrapper(func: FuncDecl) -> str: |
bb436948 | 167 | assert not '_co_' in func.name |
aaaa20b6 | 168 | assert func.return_type == 'int' |
7d55a3bb VSO |
169 | assert func.args[0].type in ['BlockDriverState *', 'BdrvChild *', |
170 | 'BlockBackend *'] | |
aaaa20b6 | 171 | |
76a2f554 EGE |
172 | name = func.co_name |
173 | struct_name = func.struct_name | |
7d55a3bb | 174 | |
76a2f554 EGE |
175 | creation_function = create_mixed_wrapper |
176 | if func.create_only_co: | |
177 | creation_function = create_co_wrapper | |
aaaa20b6 VSO |
178 | |
179 | return f"""\ | |
180 | /* | |
181 | * Wrappers for {name} | |
182 | */ | |
183 | ||
184 | typedef struct {struct_name} {{ | |
185 | BdrvPollCo poll_state; | |
186 | { func.gen_block(' {decl};') } | |
187 | }} {struct_name}; | |
188 | ||
189 | static void coroutine_fn {name}_entry(void *opaque) | |
190 | {{ | |
191 | {struct_name} *s = opaque; | |
192 | ||
193 | s->poll_state.ret = {name}({ func.gen_list('s->{name}') }); | |
194 | s->poll_state.in_progress = false; | |
195 | ||
196 | aio_wait_kick(); | |
197 | }} | |
198 | ||
76a2f554 | 199 | {creation_function(func)}""" |
aaaa20b6 VSO |
200 | |
201 | ||
202 | def gen_wrappers(input_code: str) -> str: | |
203 | res = '' | |
204 | for func in func_decl_iter(input_code): | |
205 | res += '\n\n\n' | |
206 | res += gen_wrapper(func) | |
207 | ||
208 | return res | |
209 | ||
210 | ||
211 | if __name__ == '__main__': | |
212 | if len(sys.argv) < 3: | |
213 | exit(f'Usage: {sys.argv[0]} OUT_FILE.c IN_FILE.[ch]...') | |
214 | ||
215 | with open(sys.argv[1], 'w', encoding='utf-8') as f_out: | |
216 | f_out.write(gen_header()) | |
217 | for fname in sys.argv[2:]: | |
218 | with open(fname, encoding='utf-8') as f_in: | |
219 | f_out.write(gen_wrappers(f_in.read())) | |
220 | f_out.write('\n') |