1#!/usr/bin/python3 2# Copyright (C) 2021 Free Software Foundation, Inc. 3# This file is part of the GNU C Library. 4# 5# The GNU C Library is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2.1 of the License, or (at your option) any later version. 9# 10# The GNU C Library is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General Public 16# License along with the GNU C Library; if not, see 17# <https://www.gnu.org/licenses/>. 18 19"""Benchmark program generator script 20 21This script takes a function name as input and generates a program using 22an libmvec input file located in the sysdeps/x86_64/fpu directory. The 23name of the input file should be of the form libmvec-foo-inputs where 24'foo' is the name of the function. 25""" 26 27from __future__ import print_function 28import sys 29import os 30import itertools 31import re 32 33# Macro definitions for functions that take no arguments. For functions 34# that take arguments, the STRUCT_TEMPLATE, ARGS_TEMPLATE and 35# VARIANTS_TEMPLATE are used instead. 36DEFINES_TEMPLATE = ''' 37#define CALL_BENCH_FUNC(v, i) %(func)s () 38#define NUM_VARIANTS (1) 39#define NUM_SAMPLES(v) (1) 40#define VARIANT(v) FUNCNAME "()" 41''' 42 43# Structures to store arguments for the function call. A function may 44# have its inputs partitioned to represent distinct performance 45# characteristics or distinct flavors of the function. Each such 46# variant is represented by the _VARIANT structure. The ARGS structure 47# represents a single set of arguments. 48BENCH_VEC_TEMPLATE = ''' 49#define CALL_BENCH_FUNC(v, i) (__extension__ ({ \\ 50 %(defs)s mx0 = %(func)s (%(func_args)s); \\ 51 mx0; })) 52''' 53 54BENCH_SCALAR_TEMPLATE = ''' 55#define CALL_BENCH_FUNC(v, i) %(func)s (%(func_args)s) 56''' 57 58STRUCT_TEMPLATE = '''struct args 59{ 60%(args)s 61 double timing; 62}; 63 64struct _variants 65{ 66 const char *name; 67 int count; 68 struct args *in; 69}; 70''' 71 72# The actual input arguments. 73ARGS_TEMPLATE = '''struct args in%(argnum)d[%(num_args)d] = { 74%(args)s 75}; 76''' 77 78# The actual variants, along with macros defined to access the variants. 79VARIANTS_TEMPLATE = '''struct _variants variants[%(num_variants)d] = { 80%(variants)s 81}; 82 83#define NUM_VARIANTS %(num_variants)d 84#define NUM_SAMPLES(i) (variants[i].count) 85#define VARIANT(i) (variants[i].name) 86''' 87 88# Epilogue for the generated source file. 89EPILOGUE = ''' 90#define BENCH_FUNC(i, j) ({%(getret)s CALL_BENCH_FUNC (i, j);}) 91#define FUNCNAME "%(func)s" 92#include <bench-libmvec-skeleton.c>''' 93 94 95def gen_source(func_types, directives, all_vals): 96 """Generate source for the function 97 98 Generate the C source for the function from the values and 99 directives. 100 101 Args: 102 func: The function name 103 directives: A dictionary of directives applicable to this function 104 all_vals: A dictionary input values 105 """ 106 # The includes go in first. 107 for header in directives['includes']: 108 print('#include <%s>' % header) 109 110 for header in directives['include-sources']: 111 print('#include "%s"' % header) 112 113 argtype_vtable = { 114 2: '128', 115 4: '256', 116 8: '512' 117 } 118 prefix_vtable = { 119 2: 'b', 120 4: 'c', 121 8: 'e' 122 } 123 124 # Get all the function properties 125 funcname_argtype = '' 126 float_flag = False 127 if func_types[1] == 'float': 128 float_flag = True 129 avx_flag = False 130 if func_types[3] == 'avx2': 131 avx_flag = True 132 funcname_stride = int(func_types[2][4:]) 133 funcname_origin = func_types[-1] 134 if float_flag: 135 funcname_origin = funcname_origin[:-1] 136 137 if funcname_stride == 1: 138 # Prepare for scalar functions file generation 139 funcname_prefix = '' 140 funcname_prefix_1 = '' 141 funcname_argtype = 'double' 142 if float_flag: 143 funcname_argtype = 'float' 144 else: 145 # Prepare for libmvec functions file generation 146 funcname_prefix_1 = len(directives['args']) * 'v' + '_' 147 aligned_stride = funcname_stride 148 if float_flag: 149 aligned_stride /= 2 150 funcname_prefix = '_ZGV' 151 if (avx_flag and (aligned_stride == 4)): 152 funcname_prefix += 'd' 153 else: 154 funcname_prefix += prefix_vtable[aligned_stride] 155 funcname_prefix = funcname_prefix + 'N' + func_types[2][4:] 156 funcname_argtype = '__m' + argtype_vtable[aligned_stride] 157 if not float_flag: 158 funcname_argtype += 'd' 159 160 # Include x86intrin.h for vector functions 161 if not funcname_stride == 1: 162 print('#include <x86intrin.h>') 163 if (avx_flag and (aligned_stride == 4)): 164 # For bench-float-vlen8-avx2* and bench-double-vlen4-avx2* 165 print('#define REQUIRE_AVX2') 166 elif aligned_stride == 8: 167 # For bench-float-vlen16* and bench-double-vlen8* 168 print('#define REQUIRE_AVX512F') 169 elif aligned_stride == 4: 170 # For bench-float-vlen8* and bench-double-vlen4* without avx2 171 print('#define REQUIRE_AVX') 172 else: 173 print('#define FUNCTYPE %s' % funcname_argtype) 174 175 print('#define STRIDE %d ' % funcname_stride) 176 177 funcname = funcname_prefix + funcname_prefix_1 + funcname_origin 178 if float_flag: 179 funcname += 'f' 180 181 funcname_rettype = funcname_argtype 182 if directives['ret'] == '': 183 funcname_rettype = 'void' 184 185 funcname_inputtype = [] 186 for arg, i in zip(directives['args'], itertools.count()): 187 funcname_inputtype.append(funcname_argtype) 188 if arg[0] == '<' and arg[-1] == '>': 189 pos = arg.rfind('*') 190 if pos == -1: 191 die('Output argument must be a pointer type') 192 funcname_inputtype[i] += ' *' 193 194 if not funcname_stride == 1: 195 if len(directives['args']) == 2: 196 print('extern %s %s (%s, %s);' % (funcname_rettype, funcname, funcname_inputtype[0], funcname_inputtype[1])) 197 elif len(directives['args']) == 3: 198 print('extern %s %s (%s, %s, %s);' % (funcname_rettype, funcname, funcname_inputtype[0], funcname_inputtype[1], funcname_inputtype[2])) 199 else: 200 print('extern %s %s (%s);' % (funcname_rettype, funcname, funcname_inputtype[0])) 201 202 # Print macros. This branches out to a separate routine if 203 # the function takes arguments. 204 if not directives['args']: 205 print(DEFINES_TEMPLATE % {'funcname': funcname}) 206 outargs = [] 207 else: 208 outargs = _print_arg_data(funcname, float_flag, funcname_argtype, funcname_stride, directives, all_vals) 209 210 # Print the output variable definitions if necessary. 211 for out in outargs: 212 print(out) 213 214 # If we have a return value from the function, make sure it is 215 # assigned to prevent the compiler from optimizing out the 216 # call. 217 getret = '' 218 219 if directives['ret']: 220 if funcname_argtype != '': 221 print('static %s volatile ret;' % funcname_argtype) 222 getret = 'ret =' 223 else: 224 print('static %s volatile ret;' % directives['ret']) 225 getret = 'ret =' 226 227 # Test initialization. 228 if directives['init']: 229 print('#define BENCH_INIT %s' % directives['init']) 230 231 print(EPILOGUE % {'getret': getret, 'func': funcname}) 232 233 234def _print_arg_data(func, float_flag, funcname_argtype, funcname_stride, directives, all_vals): 235 """Print argument data 236 237 This is a helper function for gen_source that prints structure and 238 values for arguments and their variants and returns output arguments 239 if any are found. 240 241 Args: 242 func: Function name 243 float_flag: True if function is float type 244 funcname_argtype: Type for vector variants 245 funcname_stride: Vector Length 246 directives: A dictionary of directives applicable to this function 247 all_vals: A dictionary input values 248 249 Returns: 250 Returns a list of definitions for function arguments that act as 251 output parameters. 252 """ 253 # First, all of the definitions. We process writing of 254 # CALL_BENCH_FUNC, struct args and also the output arguments 255 # together in a single traversal of the arguments list. 256 func_args = [] 257 _func_args = [] 258 arg_struct = [] 259 outargs = [] 260 # Conversion function for each type 261 vtable = { 262 '__m128d': '_mm_loadu_pd', 263 '__m256d': '_mm256_loadu_pd', 264 '__m512d': '_mm512_loadu_pd', 265 '__m128': '_mm_loadu_ps', 266 '__m256': '_mm256_loadu_ps', 267 '__m512': '_mm512_loadu_ps', 268 'double': '', 269 'float': '' 270 } 271 272 # For double max_vlen=8, for float max_vlen=16. 273 if float_flag == True: 274 max_vlen = 16 275 else: 276 max_vlen = 8 277 278 for arg, i in zip(directives['args'], itertools.count()): 279 if arg[0] == '<' and arg[-1] == '>': 280 outargs.append('static %s out%d __attribute__((used));' % (funcname_argtype, i)) 281 func_args.append('&out%d' % i) 282 _func_args.append('&out%d' % i) 283 else: 284 arg_struct.append(' %s arg%d[STRIDE];' % (arg, i)) 285 func_args.append('%s (variants[v].in[i].arg%d)' % 286 (vtable[funcname_argtype], i)) 287 _func_args.append('variants[v].in[i].arg%d[0]' % i) 288 289 if funcname_stride == 1: 290 print(BENCH_SCALAR_TEMPLATE % {'func': func, 291 'func_args': ', '.join(_func_args)}) 292 elif directives['ret'] == '': 293 print(BENCH_SCALAR_TEMPLATE % {'func': func, 294 'func_args': ', '.join(func_args)}) 295 else: 296 print(BENCH_VEC_TEMPLATE % {'func': func, 'func_args': ', '.join(func_args), 297 'defs': funcname_argtype}) 298 print(STRUCT_TEMPLATE % {'args': '\n'.join(arg_struct)}) 299 300 # Now print the values. 301 variants = [] 302 for (k, _vals), i in zip(all_vals.items(), itertools.count()): 303 vals = [] 304 temp_vals = [] 305 j = 0 306 temp_j = 0 307 result_v = ['', '', ''] 308 for _v in _vals: 309 nums = _v.split(',') 310 for l in range(0, len(nums)): 311 result_v[l] = result_v[l] + nums[l].strip() + ',' 312 j += 1 313 temp_j += 1 314 315 if temp_j == funcname_stride: 316 final_result = '' 317 for l in range(0, len(nums)): 318 final_result = final_result + '{' + result_v[l][:-1] + '},' 319 temp_vals.append(final_result[:-1]) 320 temp_j = 0 321 result_v = ['', '', ''] 322 323 # Make sure amount of test data is multiple of max_vlen 324 # to keep data size same for all vector length. 325 if j == max_vlen: 326 vals.extend(temp_vals) 327 temp_vals = [] 328 j = 0 329 330 out = [' {%s, 0},' % v for v in vals] 331 332 # Members for the variants structure list that we will 333 # print later. 334 variants.append(' {"%s", %d, in%d},' % (k, len(vals), i)) 335 print(ARGS_TEMPLATE % {'argnum': i, 'num_args': len(vals), 336 'args': '\n'.join(out)}) 337 338 # Print the variants and the last set of macros. 339 print(VARIANTS_TEMPLATE % {'num_variants': len(all_vals), 340 'variants': '\n'.join(variants)}) 341 return outargs 342 343 344def _process_directive(d_name, d_val, func_args): 345 """Process a directive. 346 347 Evaluate the directive name and value passed and return the 348 processed value. This is a helper function for parse_file. 349 350 Args: 351 d_name: Name of the directive 352 d_val: The string value to process 353 354 Returns: 355 The processed value, which may be the string as it is or an object 356 that describes the directive. 357 """ 358 # Process the directive values if necessary. name and ret don't 359 # need any processing. 360 if d_name.startswith('include'): 361 d_val = d_val.split(',') 362 elif d_name == 'args': 363 d_val = d_val.split(':') 364 # Check if args type match 365 if not d_val[0] == func_args: 366 die("Args mismatch, should be %s, but get %s" % (d_val[0], func_args)) 367 368 # Return the values. 369 return d_val 370 371 372def parse_file(func_types): 373 """Parse an input file 374 375 Given a function name, open and parse an input file for the function 376 and get the necessary parameters for the generated code and the list 377 of inputs. 378 379 Args: 380 func: The function name 381 382 Returns: 383 A tuple of two elements, one a dictionary of directives and the 384 other a dictionary of all input values. 385 """ 386 all_vals = {} 387 # Valid directives. 388 directives = { 389 'name': '', 390 'args': [], 391 'includes': [], 392 'include-sources': [], 393 'ret': '', 394 'init': '' 395 } 396 397 func = func_types[-1] 398 try: 399 with open('../sysdeps/x86_64/fpu/libmvec-%s-inputs' % func) as f: 400 for line in f: 401 # Look for directives and parse it if found. 402 if line.startswith('##'): 403 try: 404 d_name, d_val = line[2:].split(':', 1) 405 d_name = d_name.strip() 406 d_val = d_val.strip() 407 directives[d_name] = _process_directive(d_name, d_val, func_types[1]) 408 except (IndexError, KeyError): 409 die('Invalid directive: %s' % line[2:]) 410 411 # Skip blank lines and comments. 412 line = line.split('#', 1)[0].rstrip() 413 if not line: 414 continue 415 416 # Otherwise, we're an input. Add to the appropriate 417 # input set. 418 cur_name = directives['name'] 419 all_vals.setdefault(cur_name, []) 420 all_vals[cur_name].append(line) 421 except IOError as ex: 422 die("Failed to open input file (%s): %s" % (ex.filename, ex.strerror)) 423 424 return directives, all_vals 425 426 427def die(msg): 428 """Exit with an error 429 430 Prints an error message to the standard error stream and exits with 431 a non-zero status. 432 433 Args: 434 msg: The error message to print to standard error 435 """ 436 print('%s\n' % msg, file=sys.stderr) 437 sys.exit(os.EX_DATAERR) 438 439 440def main(args): 441 """Main function 442 443 Use the first command line argument as function name and parse its 444 input file to generate C source that calls the function repeatedly 445 for the input. 446 447 Args: 448 args: The command line arguments with the program name dropped 449 450 Returns: 451 os.EX_USAGE on error and os.EX_OK on success. 452 """ 453 if len(args) != 1: 454 print('Usage: %s <function>' % sys.argv[0]) 455 return os.EX_USAGE 456 457 func_types = args[0].split('-') 458 directives, all_vals = parse_file(func_types) 459 gen_source(func_types, directives, all_vals) 460 return os.EX_OK 461 462 463if __name__ == '__main__': 464 sys.exit(main(sys.argv[1:])) 465