1#!/usr/bin/python3 2# Copyright (C) 2014-2022 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 input file located in the benchtests directory. The name of the 23input file should be of the form foo-inputs where 'foo' is the name of 24the function. 25""" 26 27from __future__ import print_function 28import sys 29import os 30import itertools 31 32# Macro definitions for functions that take no arguments. For functions 33# that take arguments, the STRUCT_TEMPLATE, ARGS_TEMPLATE and 34# VARIANTS_TEMPLATE are used instead. 35DEFINES_TEMPLATE = ''' 36#define CALL_BENCH_FUNC(v, i) %(func)s () 37#define NUM_VARIANTS (1) 38#define NUM_SAMPLES(v) (1) 39#define VARIANT(v) FUNCNAME "()" 40''' 41 42# Structures to store arguments for the function call. A function may 43# have its inputs partitioned to represent distinct performance 44# characteristics or distinct flavors of the function. Each such 45# variant is represented by the _VARIANT structure. The ARGS structure 46# represents a single set of arguments. 47STRUCT_TEMPLATE = ''' 48#define CALL_BENCH_FUNC(v, i, x) %(func)s (x %(func_args)s) 49 50struct args 51{ 52%(args)s 53 double timing; 54}; 55 56struct _variants 57{ 58 const char *name; 59 int count; 60 struct args *in; 61}; 62''' 63 64# The actual input arguments. 65ARGS_TEMPLATE = ''' 66struct args in%(argnum)d[%(num_args)d] = { 67%(args)s 68}; 69''' 70 71# The actual variants, along with macros defined to access the variants. 72VARIANTS_TEMPLATE = ''' 73struct _variants variants[%(num_variants)d] = { 74%(variants)s 75}; 76 77#define NUM_VARIANTS %(num_variants)d 78#define NUM_SAMPLES(i) (variants[i].count) 79#define VARIANT(i) (variants[i].name) 80''' 81 82# Epilogue for the generated source file. 83EPILOGUE = ''' 84#define RESULT(__v, __i) (variants[(__v)].in[(__i)].timing) 85#define RESULT_ACCUM(r, v, i, old, new) \\ 86 ((RESULT ((v), (i))) = (RESULT ((v), (i)) * (old) + (r)) / ((new) + 1)) 87#define BENCH_FUNC(i, j) ({%(getret)s CALL_BENCH_FUNC (i, j, );}) 88#define BENCH_FUNC_LAT(i, j) ({%(getret)s CALL_BENCH_FUNC (i, j, %(latarg)s);}) 89#define BENCH_VARS %(defvar)s 90#define FUNCNAME "%(func)s" 91#include "bench-skeleton.c"''' 92 93 94def gen_source(func, directives, all_vals): 95 """Generate source for the function 96 97 Generate the C source for the function from the values and 98 directives. 99 100 Args: 101 func: The function name 102 directives: A dictionary of directives applicable to this function 103 all_vals: A dictionary input values 104 """ 105 # The includes go in first. 106 for header in directives['includes']: 107 print('#include <%s>' % header) 108 109 for header in directives['include-sources']: 110 print('#include "%s"' % header) 111 112 # Print macros. This branches out to a separate routine if 113 # the function takes arguments. 114 if not directives['args']: 115 print(DEFINES_TEMPLATE % {'func': func}) 116 outargs = [] 117 else: 118 outargs = _print_arg_data(func, directives, all_vals) 119 120 # Print the output variable definitions if necessary. 121 for out in outargs: 122 print(out) 123 124 # If we have a return value from the function, make sure it is 125 # assigned to prevent the compiler from optimizing out the 126 # call. 127 getret = '' 128 latarg = '' 129 defvar = '' 130 131 if directives['ret']: 132 print('static %s volatile ret;' % directives['ret']) 133 print('static %s zero __attribute__((used)) = 0;' % directives['ret']) 134 getret = 'ret = func_res = ' 135 # Note this may not work if argument and result type are incompatible. 136 latarg = 'func_res * zero +' 137 defvar = '%s func_res = 0;' % directives['ret'] 138 139 # Test initialization. 140 if directives['init']: 141 print('#define BENCH_INIT %s' % directives['init']) 142 143 print(EPILOGUE % {'getret': getret, 'func': func, 'latarg': latarg, 'defvar': defvar }) 144 145 146def _print_arg_data(func, directives, all_vals): 147 """Print argument data 148 149 This is a helper function for gen_source that prints structure and 150 values for arguments and their variants and returns output arguments 151 if any are found. 152 153 Args: 154 func: Function name 155 directives: A dictionary of directives applicable to this function 156 all_vals: A dictionary input values 157 158 Returns: 159 Returns a list of definitions for function arguments that act as 160 output parameters. 161 """ 162 # First, all of the definitions. We process writing of 163 # CALL_BENCH_FUNC, struct args and also the output arguments 164 # together in a single traversal of the arguments list. 165 func_args = [] 166 arg_struct = [] 167 outargs = [] 168 169 for arg, i in zip(directives['args'], itertools.count()): 170 if arg[0] == '<' and arg[-1] == '>': 171 pos = arg.rfind('*') 172 if pos == -1: 173 die('Output argument must be a pointer type') 174 175 outargs.append('static %s out%d __attribute__((used));' % (arg[1:pos], i)) 176 func_args.append(' &out%d' % i) 177 else: 178 arg_struct.append(' %s volatile arg%d;' % (arg, i)) 179 func_args.append('variants[v].in[i].arg%d' % i) 180 181 print(STRUCT_TEMPLATE % {'args' : '\n'.join(arg_struct), 'func': func, 182 'func_args': ', '.join(func_args)}) 183 184 # Now print the values. 185 variants = [] 186 for (k, vals), i in zip(all_vals.items(), itertools.count()): 187 out = [' {%s, 0},' % v for v in vals] 188 189 # Members for the variants structure list that we will 190 # print later. 191 variants.append(' {"%s", %d, in%d},' % (k, len(vals), i)) 192 print(ARGS_TEMPLATE % {'argnum': i, 'num_args': len(vals), 193 'args': '\n'.join(out)}) 194 195 # Print the variants and the last set of macros. 196 print(VARIANTS_TEMPLATE % {'num_variants': len(all_vals), 197 'variants': '\n'.join(variants)}) 198 return outargs 199 200 201def _process_directive(d_name, d_val): 202 """Process a directive. 203 204 Evaluate the directive name and value passed and return the 205 processed value. This is a helper function for parse_file. 206 207 Args: 208 d_name: Name of the directive 209 d_val: The string value to process 210 211 Returns: 212 The processed value, which may be the string as it is or an object 213 that describes the directive. 214 """ 215 # Process the directive values if necessary. name and ret don't 216 # need any processing. 217 if d_name.startswith('include'): 218 d_val = d_val.split(',') 219 elif d_name == 'args': 220 d_val = d_val.split(':') 221 222 # Return the values. 223 return d_val 224 225 226def parse_file(func): 227 """Parse an input file 228 229 Given a function name, open and parse an input file for the function 230 and get the necessary parameters for the generated code and the list 231 of inputs. 232 233 Args: 234 func: The function name 235 236 Returns: 237 A tuple of two elements, one a dictionary of directives and the 238 other a dictionary of all input values. 239 """ 240 all_vals = {} 241 # Valid directives. 242 directives = { 243 'name': '', 244 'args': [], 245 'includes': [], 246 'include-sources': [], 247 'ret': '', 248 'init': '' 249 } 250 251 try: 252 with open('%s-inputs' % func) as f: 253 for line in f: 254 # Look for directives and parse it if found. 255 if line.startswith('##'): 256 try: 257 d_name, d_val = line[2:].split(':', 1) 258 d_name = d_name.strip() 259 d_val = d_val.strip() 260 directives[d_name] = _process_directive(d_name, d_val) 261 except (IndexError, KeyError): 262 die('Invalid directive: %s' % line[2:]) 263 264 # Skip blank lines and comments. 265 line = line.split('#', 1)[0].rstrip() 266 if not line: 267 continue 268 269 # Otherwise, we're an input. Add to the appropriate 270 # input set. 271 cur_name = directives['name'] 272 all_vals.setdefault(cur_name, []) 273 all_vals[cur_name].append(line) 274 except IOError as ex: 275 die("Failed to open input file (%s): %s" % (ex.filename, ex.strerror)) 276 277 return directives, all_vals 278 279 280def die(msg): 281 """Exit with an error 282 283 Prints an error message to the standard error stream and exits with 284 a non-zero status. 285 286 Args: 287 msg: The error message to print to standard error 288 """ 289 print('%s\n' % msg, file=sys.stderr) 290 sys.exit(os.EX_DATAERR) 291 292 293def main(args): 294 """Main function 295 296 Use the first command line argument as function name and parse its 297 input file to generate C source that calls the function repeatedly 298 for the input. 299 300 Args: 301 args: The command line arguments with the program name dropped 302 303 Returns: 304 os.EX_USAGE on error and os.EX_OK on success. 305 """ 306 if len(args) != 1: 307 print('Usage: %s <function>' % sys.argv[0]) 308 return os.EX_USAGE 309 310 directives, all_vals = parse_file(args[0]) 311 gen_source(args[0], directives, all_vals) 312 return os.EX_OK 313 314 315if __name__ == '__main__': 316 sys.exit(main(sys.argv[1:])) 317