Blame benchtests/scripts/bench.py

Packit 6c4009
#!/usr/bin/python
Packit 6c4009
# Copyright (C) 2014-2018 Free Software Foundation, Inc.
Packit 6c4009
# This file is part of the GNU C Library.
Packit 6c4009
#
Packit 6c4009
# The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
# modify it under the terms of the GNU Lesser General Public
Packit 6c4009
# License as published by the Free Software Foundation; either
Packit 6c4009
# version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
#
Packit 6c4009
# The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
# Lesser General Public License for more details.
Packit 6c4009
#
Packit 6c4009
# You should have received a copy of the GNU Lesser General Public
Packit 6c4009
# License along with the GNU C Library; if not, see
Packit 6c4009
# <http://www.gnu.org/licenses/>.
Packit 6c4009
Packit 6c4009
"""Benchmark program generator script
Packit 6c4009
Packit 6c4009
This script takes a function name as input and generates a program using
Packit 6c4009
an input file located in the benchtests directory.  The name of the
Packit 6c4009
input file should be of the form foo-inputs where 'foo' is the name of
Packit 6c4009
the function.
Packit 6c4009
"""
Packit 6c4009
Packit 6c4009
from __future__ import print_function
Packit 6c4009
import sys
Packit 6c4009
import os
Packit 6c4009
import itertools
Packit 6c4009
Packit 6c4009
# Macro definitions for functions that take no arguments.  For functions
Packit 6c4009
# that take arguments, the STRUCT_TEMPLATE, ARGS_TEMPLATE and
Packit 6c4009
# VARIANTS_TEMPLATE are used instead.
Packit 6c4009
DEFINES_TEMPLATE = '''
Packit 6c4009
#define CALL_BENCH_FUNC(v, i) %(func)s ()
Packit 6c4009
#define NUM_VARIANTS (1)
Packit 6c4009
#define NUM_SAMPLES(v) (1)
Packit 6c4009
#define VARIANT(v) FUNCNAME "()"
Packit 6c4009
'''
Packit 6c4009
Packit 6c4009
# Structures to store arguments for the function call.  A function may
Packit 6c4009
# have its inputs partitioned to represent distinct performance
Packit 6c4009
# characteristics or distinct flavors of the function.  Each such
Packit 6c4009
# variant is represented by the _VARIANT structure.  The ARGS structure
Packit 6c4009
# represents a single set of arguments.
Packit 6c4009
STRUCT_TEMPLATE = '''
Packit 6c4009
#define CALL_BENCH_FUNC(v, i, x) %(func)s (x %(func_args)s)
Packit 6c4009
Packit 6c4009
struct args
Packit 6c4009
{
Packit 6c4009
%(args)s
Packit 6c4009
  double timing;
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
struct _variants
Packit 6c4009
{
Packit 6c4009
  const char *name;
Packit 6c4009
  int count;
Packit 6c4009
  struct args *in;
Packit 6c4009
};
Packit 6c4009
'''
Packit 6c4009
Packit 6c4009
# The actual input arguments.
Packit 6c4009
ARGS_TEMPLATE = '''
Packit 6c4009
struct args in%(argnum)d[%(num_args)d] = {
Packit 6c4009
%(args)s
Packit 6c4009
};
Packit 6c4009
'''
Packit 6c4009
Packit 6c4009
# The actual variants, along with macros defined to access the variants.
Packit 6c4009
VARIANTS_TEMPLATE = '''
Packit 6c4009
struct _variants variants[%(num_variants)d] = {
Packit 6c4009
%(variants)s
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
#define NUM_VARIANTS %(num_variants)d
Packit 6c4009
#define NUM_SAMPLES(i) (variants[i].count)
Packit 6c4009
#define VARIANT(i) (variants[i].name)
Packit 6c4009
'''
Packit 6c4009
Packit 6c4009
# Epilogue for the generated source file.
Packit 6c4009
EPILOGUE = '''
Packit 6c4009
#define RESULT(__v, __i) (variants[(__v)].in[(__i)].timing)
Packit 6c4009
#define RESULT_ACCUM(r, v, i, old, new) \\
Packit 6c4009
        ((RESULT ((v), (i))) = (RESULT ((v), (i)) * (old) + (r)) / ((new) + 1))
Packit 6c4009
#define BENCH_FUNC(i, j) ({%(getret)s CALL_BENCH_FUNC (i, j, );})
Packit 6c4009
#define BENCH_FUNC_LAT(i, j) ({%(getret)s CALL_BENCH_FUNC (i, j, %(latarg)s);})
Packit 6c4009
#define BENCH_VARS %(defvar)s
Packit 6c4009
#define FUNCNAME "%(func)s"
Packit 6c4009
#include "bench-skeleton.c"'''
Packit 6c4009
Packit 6c4009
Packit 6c4009
def gen_source(func, directives, all_vals):
Packit 6c4009
    """Generate source for the function
Packit 6c4009
Packit 6c4009
    Generate the C source for the function from the values and
Packit 6c4009
    directives.
Packit 6c4009
Packit 6c4009
    Args:
Packit 6c4009
      func: The function name
Packit 6c4009
      directives: A dictionary of directives applicable to this function
Packit 6c4009
      all_vals: A dictionary input values
Packit 6c4009
    """
Packit 6c4009
    # The includes go in first.
Packit 6c4009
    for header in directives['includes']:
Packit 6c4009
        print('#include <%s>' % header)
Packit 6c4009
Packit 6c4009
    for header in directives['include-sources']:
Packit 6c4009
        print('#include "%s"' % header)
Packit 6c4009
Packit 6c4009
    # Print macros.  This branches out to a separate routine if
Packit 6c4009
    # the function takes arguments.
Packit 6c4009
    if not directives['args']:
Packit 6c4009
        print(DEFINES_TEMPLATE % {'func': func})
Packit 6c4009
        outargs = []
Packit 6c4009
    else:
Packit 6c4009
        outargs = _print_arg_data(func, directives, all_vals)
Packit 6c4009
Packit 6c4009
    # Print the output variable definitions if necessary.
Packit 6c4009
    for out in outargs:
Packit 6c4009
        print(out)
Packit 6c4009
Packit 6c4009
    # If we have a return value from the function, make sure it is
Packit 6c4009
    # assigned to prevent the compiler from optimizing out the
Packit 6c4009
    # call.
Packit 6c4009
    getret = ''
Packit 6c4009
    latarg = ''
Packit 6c4009
    defvar = ''
Packit 6c4009
Packit 6c4009
    if directives['ret']:
Packit 6c4009
        print('static %s volatile ret;' % directives['ret'])
Packit 6c4009
        print('static %s zero __attribute__((used)) = 0;' % directives['ret'])
Packit 6c4009
        getret = 'ret = func_res = '
Packit 6c4009
        # Note this may not work if argument and result type are incompatible.
Packit 6c4009
        latarg = 'func_res * zero +'
Packit 6c4009
        defvar = '%s func_res = 0;' % directives['ret']
Packit 6c4009
Packit 6c4009
    # Test initialization.
Packit 6c4009
    if directives['init']:
Packit 6c4009
        print('#define BENCH_INIT %s' % directives['init'])
Packit 6c4009
Packit 6c4009
    print(EPILOGUE % {'getret': getret, 'func': func, 'latarg': latarg, 'defvar': defvar })
Packit 6c4009
Packit 6c4009
Packit 6c4009
def _print_arg_data(func, directives, all_vals):
Packit 6c4009
    """Print argument data
Packit 6c4009
Packit 6c4009
    This is a helper function for gen_source that prints structure and
Packit 6c4009
    values for arguments and their variants and returns output arguments
Packit 6c4009
    if any are found.
Packit 6c4009
Packit 6c4009
    Args:
Packit 6c4009
      func: Function name
Packit 6c4009
      directives: A dictionary of directives applicable to this function
Packit 6c4009
      all_vals: A dictionary input values
Packit 6c4009
Packit 6c4009
    Returns:
Packit 6c4009
      Returns a list of definitions for function arguments that act as
Packit 6c4009
      output parameters.
Packit 6c4009
    """
Packit 6c4009
    # First, all of the definitions.  We process writing of
Packit 6c4009
    # CALL_BENCH_FUNC, struct args and also the output arguments
Packit 6c4009
    # together in a single traversal of the arguments list.
Packit 6c4009
    func_args = []
Packit 6c4009
    arg_struct = []
Packit 6c4009
    outargs = []
Packit 6c4009
Packit 6c4009
    for arg, i in zip(directives['args'], itertools.count()):
Packit 6c4009
        if arg[0] == '<' and arg[-1] == '>':
Packit 6c4009
            pos = arg.rfind('*')
Packit 6c4009
            if pos == -1:
Packit 6c4009
                die('Output argument must be a pointer type')
Packit 6c4009
Packit 6c4009
            outargs.append('static %s out%d __attribute__((used));' % (arg[1:pos], i))
Packit 6c4009
            func_args.append(' &out%d' % i)
Packit 6c4009
        else:
Packit 6c4009
            arg_struct.append('  %s volatile arg%d;' % (arg, i))
Packit 6c4009
            func_args.append('variants[v].in[i].arg%d' % i)
Packit 6c4009
Packit 6c4009
    print(STRUCT_TEMPLATE % {'args' : '\n'.join(arg_struct), 'func': func,
Packit 6c4009
                             'func_args': ', '.join(func_args)})
Packit 6c4009
Packit 6c4009
    # Now print the values.
Packit 6c4009
    variants = []
Packit 6c4009
    for (k, vals), i in zip(all_vals.items(), itertools.count()):
Packit 6c4009
        out = ['  {%s, 0},' % v for v in vals]
Packit 6c4009
Packit 6c4009
        # Members for the variants structure list that we will
Packit 6c4009
        # print later.
Packit 6c4009
        variants.append('  {"%s", %d, in%d},' % (k, len(vals), i))
Packit 6c4009
        print(ARGS_TEMPLATE % {'argnum': i, 'num_args': len(vals),
Packit 6c4009
                               'args': '\n'.join(out)})
Packit 6c4009
Packit 6c4009
    # Print the variants and the last set of macros.
Packit 6c4009
    print(VARIANTS_TEMPLATE % {'num_variants': len(all_vals),
Packit 6c4009
                               'variants': '\n'.join(variants)})
Packit 6c4009
    return outargs
Packit 6c4009
Packit 6c4009
Packit 6c4009
def _process_directive(d_name, d_val):
Packit 6c4009
    """Process a directive.
Packit 6c4009
Packit 6c4009
    Evaluate the directive name and value passed and return the
Packit 6c4009
    processed value. This is a helper function for parse_file.
Packit 6c4009
Packit 6c4009
    Args:
Packit 6c4009
      d_name: Name of the directive
Packit 6c4009
      d_val: The string value to process
Packit 6c4009
Packit 6c4009
    Returns:
Packit 6c4009
      The processed value, which may be the string as it is or an object
Packit 6c4009
      that describes the directive.
Packit 6c4009
    """
Packit 6c4009
    # Process the directive values if necessary.  name and ret don't
Packit 6c4009
    # need any processing.
Packit 6c4009
    if d_name.startswith('include'):
Packit 6c4009
        d_val = d_val.split(',')
Packit 6c4009
    elif d_name == 'args':
Packit 6c4009
        d_val = d_val.split(':')
Packit 6c4009
Packit 6c4009
    # Return the values.
Packit 6c4009
    return d_val
Packit 6c4009
Packit 6c4009
Packit 6c4009
def parse_file(func):
Packit 6c4009
    """Parse an input file
Packit 6c4009
Packit 6c4009
    Given a function name, open and parse an input file for the function
Packit 6c4009
    and get the necessary parameters for the generated code and the list
Packit 6c4009
    of inputs.
Packit 6c4009
Packit 6c4009
    Args:
Packit 6c4009
      func: The function name
Packit 6c4009
Packit 6c4009
    Returns:
Packit 6c4009
      A tuple of two elements, one a dictionary of directives and the
Packit 6c4009
      other a dictionary of all input values.
Packit 6c4009
    """
Packit 6c4009
    all_vals = {}
Packit 6c4009
    # Valid directives.
Packit 6c4009
    directives = {
Packit 6c4009
            'name': '',
Packit 6c4009
            'args': [],
Packit 6c4009
            'includes': [],
Packit 6c4009
            'include-sources': [],
Packit 6c4009
            'ret': '',
Packit 6c4009
            'init': ''
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
    try:
Packit 6c4009
        with open('%s-inputs' % func) as f:
Packit 6c4009
            for line in f:
Packit 6c4009
                # Look for directives and parse it if found.
Packit 6c4009
                if line.startswith('##'):
Packit 6c4009
                    try:
Packit 6c4009
                        d_name, d_val = line[2:].split(':', 1)
Packit 6c4009
                        d_name = d_name.strip()
Packit 6c4009
                        d_val = d_val.strip()
Packit 6c4009
                        directives[d_name] = _process_directive(d_name, d_val)
Packit 6c4009
                    except (IndexError, KeyError):
Packit 6c4009
                        die('Invalid directive: %s' % line[2:])
Packit 6c4009
Packit 6c4009
                # Skip blank lines and comments.
Packit 6c4009
                line = line.split('#', 1)[0].rstrip()
Packit 6c4009
                if not line:
Packit 6c4009
                    continue
Packit 6c4009
Packit 6c4009
                # Otherwise, we're an input.  Add to the appropriate
Packit 6c4009
                # input set.
Packit 6c4009
                cur_name = directives['name']
Packit 6c4009
                all_vals.setdefault(cur_name, [])
Packit 6c4009
                all_vals[cur_name].append(line)
Packit 6c4009
    except IOError as ex:
Packit 6c4009
        die("Failed to open input file (%s): %s" % (ex.filename, ex.strerror))
Packit 6c4009
Packit 6c4009
    return directives, all_vals
Packit 6c4009
Packit 6c4009
Packit 6c4009
def die(msg):
Packit 6c4009
    """Exit with an error
Packit 6c4009
Packit 6c4009
    Prints an error message to the standard error stream and exits with
Packit 6c4009
    a non-zero status.
Packit 6c4009
Packit 6c4009
    Args:
Packit 6c4009
      msg: The error message to print to standard error
Packit 6c4009
    """
Packit 6c4009
    print('%s\n' % msg, file=sys.stderr)
Packit 6c4009
    sys.exit(os.EX_DATAERR)
Packit 6c4009
Packit 6c4009
Packit 6c4009
def main(args):
Packit 6c4009
    """Main function
Packit 6c4009
Packit 6c4009
    Use the first command line argument as function name and parse its
Packit 6c4009
    input file to generate C source that calls the function repeatedly
Packit 6c4009
    for the input.
Packit 6c4009
Packit 6c4009
    Args:
Packit 6c4009
      args: The command line arguments with the program name dropped
Packit 6c4009
Packit 6c4009
    Returns:
Packit 6c4009
      os.EX_USAGE on error and os.EX_OK on success.
Packit 6c4009
    """
Packit 6c4009
    if len(args) != 1:
Packit 6c4009
        print('Usage: %s <function>' % sys.argv[0])
Packit 6c4009
        return os.EX_USAGE
Packit 6c4009
Packit 6c4009
    directives, all_vals = parse_file(args[0])
Packit 6c4009
    gen_source(args[0], directives, all_vals)
Packit 6c4009
    return os.EX_OK
Packit 6c4009
Packit 6c4009
Packit 6c4009
if __name__ == '__main__':
Packit 6c4009
    sys.exit(main(sys.argv[1:]))