1#! /usr/bin/env python3 2"""Generate coroutine wrappers for block subsystem. 3 4The program parses one or several concatenated c files from stdin, 5searches for functions with the 'co_wrapper' specifier 6and generates corresponding wrappers on stdout. 7 8Usage: block-coroutine-wrapper.py generated-file.c FILE.[ch]... 9 10Copyright (c) 2020 Virtuozzo International GmbH. 11 12This program is free software; you can redistribute it and/or modify 13it under the terms of the GNU General Public License as published by 14the Free Software Foundation; either version 2 of the License, or 15(at your option) any later version. 16 17This program is distributed in the hope that it will be useful, 18but WITHOUT ANY WARRANTY; without even the implied warranty of 19MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20GNU General Public License for more details. 21 22You should have received a copy of the GNU General Public License 23along with this program. If not, see <http://www.gnu.org/licenses/>. 24""" 25 26import sys 27import re 28from typing import Iterator 29 30 31def 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#include "block/dirty-bitmap.h" 47""" 48 49 50class ParamDecl: 51 param_re = re.compile(r'(?P<decl>' 52 r'(?P<type>.*[ *])' 53 r'(?P<name>[a-z][a-z0-9_]*)' 54 r')') 55 56 def __init__(self, param_decl: str) -> None: 57 m = self.param_re.match(param_decl.strip()) 58 if m is None: 59 raise ValueError(f'Wrong parameter declaration: "{param_decl}"') 60 self.decl = m.group('decl') 61 self.type = m.group('type') 62 self.name = m.group('name') 63 64 65class FuncDecl: 66 def __init__(self, wrapper_type: str, return_type: str, name: str, 67 args: str, variant: str) -> None: 68 self.return_type = return_type.strip() 69 self.name = name.strip() 70 self.struct_name = snake_to_camel(self.name) 71 self.args = [ParamDecl(arg.strip()) for arg in args.split(',')] 72 self.create_only_co = 'mixed' not in variant 73 self.graph_rdlock = 'bdrv_rdlock' in variant 74 self.graph_wrlock = 'bdrv_wrlock' in variant 75 76 self.wrapper_type = wrapper_type 77 78 if wrapper_type == 'co': 79 if self.graph_wrlock: 80 raise ValueError(f"co function can't be wrlock: {self.name}") 81 subsystem, subname = self.name.split('_', 1) 82 self.target_name = f'{subsystem}_co_{subname}' 83 else: 84 assert wrapper_type == 'no_co' 85 subsystem, co_infix, subname = self.name.split('_', 2) 86 if co_infix != 'co': 87 raise ValueError(f"Invalid no_co function name: {self.name}") 88 if not self.create_only_co: 89 raise ValueError(f"no_co function can't be mixed: {self.name}") 90 if self.graph_rdlock: 91 raise ValueError(f"no_co function can't be rdlock: {self.name}") 92 self.target_name = f'{subsystem}_{subname}' 93 94 self.ctx = self.gen_ctx() 95 96 self.get_result = 's->ret = ' 97 self.ret = 'return s.ret;' 98 self.co_ret = 'return ' 99 self.return_field = self.return_type + " ret;" 100 if self.return_type == 'void': 101 self.get_result = '' 102 self.ret = '' 103 self.co_ret = '' 104 self.return_field = '' 105 106 def gen_ctx(self, prefix: str = '') -> str: 107 t = self.args[0].type 108 name = self.args[0].name 109 if t == 'BlockDriverState *': 110 return f'bdrv_get_aio_context({prefix}{name})' 111 elif t == 'BdrvChild *': 112 return f'bdrv_get_aio_context({prefix}{name}->bs)' 113 elif t == 'BlockBackend *': 114 return f'blk_get_aio_context({prefix}{name})' 115 else: 116 return 'qemu_get_aio_context()' 117 118 def gen_list(self, format: str) -> str: 119 return ', '.join(format.format_map(arg.__dict__) for arg in self.args) 120 121 def gen_block(self, format: str) -> str: 122 return '\n'.join(format.format_map(arg.__dict__) for arg in self.args) 123 124 125# Match wrappers declared with a co_wrapper mark 126func_decl_re = re.compile(r'^(?P<return_type>[a-zA-Z][a-zA-Z0-9_]* [\*]?)' 127 r'(\s*coroutine_fn)?' 128 r'\s*(?P<wrapper_type>(no_)?co)_wrapper' 129 r'(?P<variant>(_[a-z][a-z0-9_]*)?)\s*' 130 r'(?P<wrapper_name>[a-z][a-z0-9_]*)' 131 r'\((?P<args>[^)]*)\);$', re.MULTILINE) 132 133 134def func_decl_iter(text: str) -> Iterator: 135 for m in func_decl_re.finditer(text): 136 yield FuncDecl(wrapper_type=m.group('wrapper_type'), 137 return_type=m.group('return_type'), 138 name=m.group('wrapper_name'), 139 args=m.group('args'), 140 variant=m.group('variant')) 141 142 143def snake_to_camel(func_name: str) -> str: 144 """ 145 Convert underscore names like 'some_function_name' to camel-case like 146 'SomeFunctionName' 147 """ 148 words = func_name.split('_') 149 words = [w[0].upper() + w[1:] for w in words] 150 return ''.join(words) 151 152 153def create_mixed_wrapper(func: FuncDecl) -> str: 154 """ 155 Checks if we are already in coroutine 156 """ 157 name = func.target_name 158 struct_name = func.struct_name 159 graph_assume_lock = 'assume_graph_lock();' if func.graph_rdlock else '' 160 161 return f"""\ 162{func.return_type} {func.name}({ func.gen_list('{decl}') }) 163{{ 164 if (qemu_in_coroutine()) {{ 165 {graph_assume_lock} 166 {func.co_ret}{name}({ func.gen_list('{name}') }); 167 }} else {{ 168 {struct_name} s = {{ 169 .poll_state.ctx = {func.ctx}, 170 .poll_state.in_progress = true, 171 172{ func.gen_block(' .{name} = {name},') } 173 }}; 174 175 s.poll_state.co = qemu_coroutine_create({name}_entry, &s); 176 177 bdrv_poll_co(&s.poll_state); 178 {func.ret} 179 }} 180}}""" 181 182 183def create_co_wrapper(func: FuncDecl) -> str: 184 """ 185 Assumes we are not in coroutine, and creates one 186 """ 187 name = func.target_name 188 struct_name = func.struct_name 189 return f"""\ 190{func.return_type} {func.name}({ func.gen_list('{decl}') }) 191{{ 192 {struct_name} s = {{ 193 .poll_state.ctx = {func.ctx}, 194 .poll_state.in_progress = true, 195 196{ func.gen_block(' .{name} = {name},') } 197 }}; 198 assert(!qemu_in_coroutine()); 199 200 s.poll_state.co = qemu_coroutine_create({name}_entry, &s); 201 202 bdrv_poll_co(&s.poll_state); 203 {func.ret} 204}}""" 205 206 207def gen_co_wrapper(func: FuncDecl) -> str: 208 assert not '_co_' in func.name 209 assert func.wrapper_type == 'co' 210 211 name = func.target_name 212 struct_name = func.struct_name 213 214 graph_lock='' 215 graph_unlock='' 216 if func.graph_rdlock: 217 graph_lock=' bdrv_graph_co_rdlock();' 218 graph_unlock=' bdrv_graph_co_rdunlock();' 219 220 creation_function = create_mixed_wrapper 221 if func.create_only_co: 222 creation_function = create_co_wrapper 223 224 return f"""\ 225/* 226 * Wrappers for {name} 227 */ 228 229typedef struct {struct_name} {{ 230 BdrvPollCo poll_state; 231 {func.return_field} 232{ func.gen_block(' {decl};') } 233}} {struct_name}; 234 235static void coroutine_fn {name}_entry(void *opaque) 236{{ 237 {struct_name} *s = opaque; 238 239{graph_lock} 240 {func.get_result}{name}({ func.gen_list('s->{name}') }); 241{graph_unlock} 242 s->poll_state.in_progress = false; 243 244 aio_wait_kick(); 245}} 246 247{creation_function(func)}""" 248 249 250def gen_no_co_wrapper(func: FuncDecl) -> str: 251 assert '_co_' in func.name 252 assert func.wrapper_type == 'no_co' 253 254 name = func.target_name 255 struct_name = func.struct_name 256 257 graph_lock='' 258 graph_unlock='' 259 if func.graph_wrlock: 260 graph_lock=' bdrv_graph_wrlock(NULL);' 261 graph_unlock=' bdrv_graph_wrunlock();' 262 263 return f"""\ 264/* 265 * Wrappers for {name} 266 */ 267 268typedef struct {struct_name} {{ 269 Coroutine *co; 270 {func.return_field} 271{ func.gen_block(' {decl};') } 272}} {struct_name}; 273 274static void {name}_bh(void *opaque) 275{{ 276 {struct_name} *s = opaque; 277 AioContext *ctx = {func.gen_ctx('s->')}; 278 279{graph_lock} 280 aio_context_acquire(ctx); 281 {func.get_result}{name}({ func.gen_list('s->{name}') }); 282 aio_context_release(ctx); 283{graph_unlock} 284 285 aio_co_wake(s->co); 286}} 287 288{func.return_type} coroutine_fn {func.name}({ func.gen_list('{decl}') }) 289{{ 290 {struct_name} s = {{ 291 .co = qemu_coroutine_self(), 292{ func.gen_block(' .{name} = {name},') } 293 }}; 294 assert(qemu_in_coroutine()); 295 296 aio_bh_schedule_oneshot(qemu_get_aio_context(), {name}_bh, &s); 297 qemu_coroutine_yield(); 298 299 {func.ret} 300}}""" 301 302 303def gen_wrappers(input_code: str) -> str: 304 res = '' 305 for func in func_decl_iter(input_code): 306 res += '\n\n\n' 307 if func.wrapper_type == 'co': 308 res += gen_co_wrapper(func) 309 else: 310 res += gen_no_co_wrapper(func) 311 312 return res 313 314 315if __name__ == '__main__': 316 if len(sys.argv) < 3: 317 exit(f'Usage: {sys.argv[0]} OUT_FILE.c IN_FILE.[ch]...') 318 319 with open(sys.argv[1], 'w', encoding='utf-8') as f_out: 320 f_out.write(gen_header()) 321 for fname in sys.argv[2:]: 322 with open(fname, encoding='utf-8') as f_in: 323 f_out.write(gen_wrappers(f_in.read())) 324 f_out.write('\n') 325