bench.py 9.7 KB

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