1  #!/usr/bin/python3
       2  # Copyright (C) 2021-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 libmvec input file located in the sysdeps/x86_64/fpu directory.  The
      23  name of the input file should be of the form libmvec-foo-inputs where
      24  'foo' is the name of the function.
      25  """
      26  
      27  from __future__ import print_function
      28  import sys
      29  import os
      30  import itertools
      31  import 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.
      36  DEFINES_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.
      48  BENCH_VEC_TEMPLATE = '''
      49  #define CALL_BENCH_FUNC(v, i) (__extension__ ({ \\
      50    %(defs)s mx0 = %(func)s (%(func_args)s); \\
      51    mx0; }))
      52  '''
      53  
      54  BENCH_SCALAR_TEMPLATE = '''
      55  #define CALL_BENCH_FUNC(v, i) %(func)s (%(func_args)s)
      56  '''
      57  
      58  STRUCT_TEMPLATE = '''struct args
      59  {
      60  %(args)s
      61    double timing;
      62  };
      63  
      64  struct _variants
      65  {
      66    const char *name;
      67    int count;
      68    struct args *in;
      69  };
      70  '''
      71  
      72  # The actual input arguments.
      73  ARGS_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.
      79  VARIANTS_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.
      89  EPILOGUE = '''
      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  
      95  def 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  
     234  def _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  
     344  def _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  
     372  def 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('../benchtests/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  
     427  def 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  
     440  def 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  
     463  if __name__ == '__main__':
     464    sys.exit(main(sys.argv[1:]))