(root)/
glibc-2.38/
benchtests/
scripts/
bench.py
       1  #!/usr/bin/python3
       2  # Copyright (C) 2014-2023 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  
      21  This script takes a function name as input and generates a program using
      22  an input file located in the benchtests directory.  The name of the
      23  input file should be of the form foo-inputs where 'foo' is the name of
      24  the function.
      25  """
      26  
      27  from __future__ import print_function
      28  import sys
      29  import os
      30  import 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.
      35  DEFINES_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.
      47  STRUCT_TEMPLATE = '''
      48  #define CALL_BENCH_FUNC(v, i, x) %(func)s (x %(func_args)s)
      49  
      50  struct args
      51  {
      52  %(args)s
      53    double timing;
      54  };
      55  
      56  struct _variants
      57  {
      58    const char *name;
      59    int count;
      60    struct args *in;
      61  };
      62  '''
      63  
      64  # The actual input arguments.
      65  ARGS_TEMPLATE = '''
      66  struct 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.
      72  VARIANTS_TEMPLATE = '''
      73  struct _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.
      83  EPILOGUE = '''
      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  
      94  def 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  
     146  def _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  
     201  def _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  
     226  def 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  
     280  def 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  
     293  def 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  
     315  if __name__ == '__main__':
     316      sys.exit(main(sys.argv[1:]))