Remove "Contributed by" lines
[platform/upstream/glibc.git] / math / gen-libm-test.py
1 #!/usr/bin/python3
2 # Generate tests for libm functions.
3 # Copyright (C) 2018-2021 Free Software Foundation, Inc.
4 # This file is part of the GNU C Library.
5 #
6 # The GNU C Library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2.1 of the License, or (at your option) any later version.
10 #
11 # The GNU C Library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # Lesser General Public License for more details.
15 #
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with the GNU C Library; if not, see
18 # <https://www.gnu.org/licenses/>.
19
20 import argparse
21 from collections import defaultdict
22 import os
23 import re
24
25
26 # Sorted list of all float types in ulps files.
27 ALL_FLOATS = ('double', 'float', 'float128', 'ldouble')
28
29 # Map float types in ulps files to C-like prefix for macros.
30 ALL_FLOATS_PFX = {'double': 'DBL',
31                   'ldouble': 'LDBL',
32                   'float': 'FLT',
33                   'float128': 'FLT128'}
34
35 # Float types in the order used in the generated ulps tables in the
36 # manual.
37 ALL_FLOATS_MANUAL = ('float', 'double', 'ldouble', 'float128')
38
39 # Map float types in ulps files to C function suffix.
40 ALL_FLOATS_SUFFIX = {'double': '',
41                      'ldouble': 'l',
42                      'float': 'f',
43                      'float128': 'f128'}
44
45 # Number of arguments in structure (as opposed to arguments that are
46 # pointers to return values) for an argument descriptor.
47 DESCR_NUM_ARGS = {'f': 1, 'a': 1, 'j': 1, 'i': 1, 'u': 1, 'l': 1, 'L': 1,
48                   'p': 0, 'F': 0, 'I': 0,
49                   'c': 2}
50
51 # Number of results in structure for a result descriptor.
52 DESCR_NUM_RES = {'f': 1, 'i': 1, 'l': 1, 'L': 1, 'M': 1, 'U': 1, 'b': 1,
53                  '1': 1,
54                  'c': 2}
55
56 # Rounding modes, in the form in which they appear in
57 # auto-libm-test-out-* and the order in which expected results appear
58 # in structures and TEST_* calls.
59 ROUNDING_MODES = ('downward', 'tonearest', 'towardzero', 'upward')
60
61 # Map from special text in TEST_* calls for rounding-mode-specific
62 # results and flags, to those results for each mode.
63 ROUNDING_MAP = {
64     'plus_oflow': ('max_value', 'plus_infty', 'max_value', 'plus_infty'),
65     'minus_oflow': ('minus_infty', 'minus_infty', '-max_value', '-max_value'),
66     'plus_uflow': ('plus_zero', 'plus_zero', 'plus_zero', 'min_subnorm_value'),
67     'minus_uflow': ('-min_subnorm_value', 'minus_zero', 'minus_zero',
68                     'minus_zero'),
69     'ERRNO_PLUS_OFLOW': ('0', 'ERRNO_ERANGE', '0', 'ERRNO_ERANGE'),
70     'ERRNO_MINUS_OFLOW': ('ERRNO_ERANGE', 'ERRNO_ERANGE', '0', '0'),
71     'ERRNO_PLUS_UFLOW': ('ERRNO_ERANGE', 'ERRNO_ERANGE', 'ERRNO_ERANGE', '0'),
72     'ERRNO_MINUS_UFLOW': ('0', 'ERRNO_ERANGE', 'ERRNO_ERANGE', 'ERRNO_ERANGE'),
73     'XFAIL_ROUNDING_IBM128_LIBGCC': ('XFAIL_IBM128_LIBGCC', '0',
74                                      'XFAIL_IBM128_LIBGCC',
75                                      'XFAIL_IBM128_LIBGCC')
76     }
77
78 # Map from raw test arguments to a nicer form to use when displaying
79 # test results.
80 BEAUTIFY_MAP = {'minus_zero': '-0',
81                 'plus_zero': '+0',
82                 '-0x0p+0f': '-0',
83                 '-0x0p+0': '-0',
84                 '-0x0p+0L': '-0',
85                 '0x0p+0f': '+0',
86                 '0x0p+0': '+0',
87                 '0x0p+0L': '+0',
88                 'minus_infty': '-inf',
89                 'plus_infty': 'inf',
90                 'qnan_value': 'qNaN',
91                 'snan_value': 'sNaN',
92                 'snan_value_ld': 'sNaN'}
93
94 # Flags in auto-libm-test-out that map directly to C flags.
95 FLAGS_SIMPLE = {'ignore-zero-inf-sign': 'IGNORE_ZERO_INF_SIGN',
96                 'xfail': 'XFAIL_TEST'}
97
98 # Exceptions in auto-libm-test-out, and their corresponding C flags
99 # for being required, OK or required to be absent.
100 EXC_EXPECTED = {'divbyzero': 'DIVBYZERO_EXCEPTION',
101                 'inexact': 'INEXACT_EXCEPTION',
102                 'invalid': 'INVALID_EXCEPTION',
103                 'overflow': 'OVERFLOW_EXCEPTION',
104                 'underflow': 'UNDERFLOW_EXCEPTION'}
105 EXC_OK = {'divbyzero': 'DIVBYZERO_EXCEPTION_OK',
106           'inexact': '0',
107           'invalid': 'INVALID_EXCEPTION_OK',
108           'overflow': 'OVERFLOW_EXCEPTION_OK',
109           'underflow': 'UNDERFLOW_EXCEPTION_OK'}
110 EXC_NO = {'divbyzero': '0',
111           'inexact': 'NO_INEXACT_EXCEPTION',
112           'invalid': '0',
113           'overflow': '0',
114           'underflow': '0'}
115
116
117 class Ulps(object):
118     """Maximum expected errors of libm functions."""
119
120     def __init__(self):
121         """Initialize an Ulps object."""
122         # normal[function][float_type] is the ulps value, and likewise
123         # for real and imag.
124         self.normal = defaultdict(lambda: defaultdict(lambda: 0))
125         self.real = defaultdict(lambda: defaultdict(lambda: 0))
126         self.imag = defaultdict(lambda: defaultdict(lambda: 0))
127         # List of ulps kinds, in the order in which they appear in
128         # sorted ulps files.
129         self.ulps_kinds = (('Real part of ', self.real),
130                            ('Imaginary part of ', self.imag),
131                            ('', self.normal))
132         self
133
134     def read(self, ulps_file):
135         """Read ulps from a file into an Ulps object."""
136         self.ulps_file = ulps_file
137         with open(ulps_file, 'r') as f:
138             ulps_dict = None
139             ulps_fn = None
140             for line in f:
141                 # Ignore comments.
142                 if line.startswith('#'):
143                     continue
144                 line = line.rstrip()
145                 # Ignore empty lines.
146                 if line == '':
147                     continue
148                 m = re.match(r'([^:]*): (.*)\Z', line)
149                 if not m:
150                     raise ValueError('bad ulps line: %s' % line)
151                 line_first = m.group(1)
152                 line_second = m.group(2)
153                 if line_first == 'Function':
154                     fn = None
155                     ulps_dict = None
156                     for k_prefix, k_dict in self.ulps_kinds:
157                         if line_second.startswith(k_prefix):
158                             ulps_dict = k_dict
159                             fn = line_second[len(k_prefix):]
160                             break
161                     if not fn.startswith('"') or not fn.endswith('":'):
162                         raise ValueError('bad ulps line: %s' % line)
163                     ulps_fn = fn[1:-2]
164                 else:
165                     if line_first not in ALL_FLOATS:
166                         raise ValueError('bad ulps line: %s' % line)
167                     ulps_val = int(line_second)
168                     if ulps_val > 0:
169                         ulps_dict[ulps_fn][line_first] = max(
170                             ulps_dict[ulps_fn][line_first],
171                             ulps_val)
172
173     def all_functions(self):
174         """Return the set of functions with ulps and whether they are
175         complex."""
176         funcs = set()
177         complex = {}
178         for k_prefix, k_dict in self.ulps_kinds:
179             for f in k_dict:
180                 funcs.add(f)
181                 complex[f] = True if k_prefix else False
182         return funcs, complex
183
184     def write(self, ulps_file):
185         """Write ulps back out as a sorted ulps file."""
186         # Output is sorted first by function name, then by (real,
187         # imag, normal), then by float type.
188         out_data = {}
189         for order, (prefix, d) in enumerate(self.ulps_kinds):
190             for fn in d.keys():
191                 fn_data = ['%s: %d' % (f, d[fn][f])
192                            for f in sorted(d[fn].keys())]
193                 fn_text = 'Function: %s"%s":\n%s' % (prefix, fn,
194                                                      '\n'.join(fn_data))
195                 out_data[(fn, order)] = fn_text
196         out_list = [out_data[fn_order] for fn_order in sorted(out_data.keys())]
197         out_text = ('# Begin of automatic generation\n\n'
198                     '# Maximal error of functions:\n'
199                     '%s\n\n'
200                     '# end of automatic generation\n'
201                     % '\n\n'.join(out_list))
202         with open(ulps_file, 'w') as f:
203             f.write(out_text)
204
205     @staticmethod
206     def ulps_table(name, ulps_dict):
207         """Return text of a C table of ulps."""
208         ulps_list = []
209         for fn in sorted(ulps_dict.keys()):
210             fn_ulps = [str(ulps_dict[fn][f]) for f in ALL_FLOATS]
211             ulps_list.append('    { "%s", {%s} },' % (fn, ', '.join(fn_ulps)))
212         ulps_text = ('static const struct ulp_data %s[] =\n'
213                      '  {\n'
214                      '%s\n'
215                      '  };'
216                      % (name, '\n'.join(ulps_list)))
217         return ulps_text
218
219     def write_header(self, ulps_header):
220         """Write header file with ulps data."""
221         header_text_1 = ('/* This file is automatically generated\n'
222                          '   from %s with gen-libm-test.py.\n'
223                          '   Don\'t change it - change instead the master '
224                          'files.  */\n\n'
225                          'struct ulp_data\n'
226                          '{\n'
227                          '  const char *name;\n'
228                          '  FLOAT max_ulp[%d];\n'
229                          '};'
230                          % (self.ulps_file, len(ALL_FLOATS)))
231         macro_list = []
232         for i, f in enumerate(ALL_FLOATS):
233             if f.startswith('i'):
234                 itxt = 'I_'
235                 f = f[1:]
236             else:
237                 itxt = ''
238             macro_list.append('#define ULP_%s%s %d'
239                               % (itxt, ALL_FLOATS_PFX[f], i))
240         header_text = ('%s\n\n'
241                        '%s\n\n'
242                        '/* Maximal error of functions.  */\n'
243                        '%s\n'
244                        '%s\n'
245                        '%s\n'
246                        % (header_text_1, '\n'.join(macro_list),
247                           self.ulps_table('func_ulps', self.normal),
248                           self.ulps_table('func_real_ulps', self.real),
249                           self.ulps_table('func_imag_ulps', self.imag)))
250         with open(ulps_header, 'w') as f:
251             f.write(header_text)
252
253
254 def read_all_ulps(srcdir):
255     """Read all platforms' libm-test-ulps files."""
256     all_ulps = {}
257     for dirpath, dirnames, filenames in os.walk(srcdir):
258         if 'libm-test-ulps' in filenames:
259             with open(os.path.join(dirpath, 'libm-test-ulps-name')) as f:
260                 name = f.read().rstrip()
261             all_ulps[name] = Ulps()
262             all_ulps[name].read(os.path.join(dirpath, 'libm-test-ulps'))
263     return all_ulps
264
265
266 def read_auto_tests(test_file):
267     """Read tests from auto-libm-test-out-<function> (possibly None)."""
268     auto_tests = defaultdict(lambda: defaultdict(dict))
269     if test_file is None:
270         return auto_tests
271     with open(test_file, 'r') as f:
272         for line in f:
273             if not line.startswith('= '):
274                 continue
275             line = line[len('= '):].rstrip()
276             # Function, rounding mode, condition and inputs, outputs
277             # and flags.
278             m = re.match(r'([^ ]+) ([^ ]+) ([^: ][^ ]* [^:]*) : (.*)\Z', line)
279             if not m:
280                 raise ValueError('bad automatic test line: %s' % line)
281             auto_tests[m.group(1)][m.group(2)][m.group(3)] = m.group(4)
282     return auto_tests
283
284
285 def beautify(arg):
286     """Return a nicer representation of a test argument."""
287     if arg in BEAUTIFY_MAP:
288         return BEAUTIFY_MAP[arg]
289     if arg.startswith('-') and arg[1:] in BEAUTIFY_MAP:
290         return '-' + BEAUTIFY_MAP[arg[1:]]
291     if re.match(r'-?0x[0-9a-f.]*p[-+][0-9]+f\Z', arg):
292         return arg[:-1]
293     if re.search(r'[0-9]L\Z', arg):
294         return arg[:-1]
295     return arg
296
297
298 def complex_beautify(arg_real, arg_imag):
299     """Return a nicer representation of a complex test argument."""
300     res_real = beautify(arg_real)
301     res_imag = beautify(arg_imag)
302     if res_imag.startswith('-'):
303         return '%s - %s i' % (res_real, res_imag[1:])
304     else:
305         return '%s + %s i' % (res_real, res_imag)
306
307
308 def apply_lit_token(arg, macro):
309     """Apply the LIT or ARG_LIT macro to a single token."""
310     # The macro must only be applied to a floating-point constant, not
311     # to an integer constant or lit_* value.
312     sign_re = r'[+-]?'
313     exp_re = r'([+-])?[0-9]+'
314     suffix_re = r'[lLfF]?'
315     dec_exp_re = r'[eE]' + exp_re
316     hex_exp_re = r'[pP]' + exp_re
317     dec_frac_re = r'(?:[0-9]*\.[0-9]+|[0-9]+\.)'
318     hex_frac_re = r'(?:[0-9a-fA-F]*\.[0-9a-fA-F]+|[0-9a-fA-F]+\.)'
319     dec_int_re = r'[0-9]+'
320     hex_int_re = r'[0-9a-fA-F]+'
321     dec_cst_re = r'(?:%s(?:%s)?|%s%s)' % (dec_frac_re, dec_exp_re,
322                                           dec_int_re, dec_exp_re)
323     hex_cst_re = r'0[xX](?:%s|%s)%s' % (hex_frac_re, hex_int_re, hex_exp_re)
324     fp_cst_re = r'(%s(?:%s|%s))%s\Z' % (sign_re, dec_cst_re, hex_cst_re,
325                                         suffix_re)
326     m = re.match(fp_cst_re, arg)
327     if m:
328         return '%s (%s)' % (macro, m.group(1))
329     else:
330         return arg
331
332
333 def apply_lit(arg, macro):
334     """Apply the LIT or ARG_LIT macro to constants within an expression."""
335     # Assume expressions follow the GNU Coding Standards, with tokens
336     # separated by spaces.
337     return ' '.join([apply_lit_token(t, macro) for t in arg.split()])
338
339
340 def gen_test_args_res(descr_args, descr_res, args, res_rm):
341     """Generate a test given the arguments and per-rounding-mode results."""
342     test_snan = False
343     all_args_res = list(args)
344     for r in res_rm:
345         all_args_res.extend(r[:len(r)-1])
346     for a in all_args_res:
347         if 'snan_value' in a:
348             test_snan = True
349     # Process the arguments.
350     args_disp = []
351     args_c = []
352     arg_pos = 0
353     for d in descr_args:
354         if DESCR_NUM_ARGS[d] == 0:
355             continue
356         if d == 'c':
357             args_disp.append(complex_beautify(args[arg_pos],
358                                               args[arg_pos + 1]))
359             args_c.append(apply_lit(args[arg_pos], 'LIT'))
360             args_c.append(apply_lit(args[arg_pos + 1], 'LIT'))
361         else:
362             args_disp.append(beautify(args[arg_pos]))
363             if d == 'f':
364                 args_c.append(apply_lit(args[arg_pos], 'LIT'))
365             elif d == 'a':
366                 args_c.append(apply_lit(args[arg_pos], 'ARG_LIT'))
367             else:
368                 args_c.append(args[arg_pos])
369         arg_pos += DESCR_NUM_ARGS[d]
370     args_disp_text = ', '.join(args_disp).replace('"', '\\"')
371     # Process the results.
372     for rm in range(len(ROUNDING_MODES)):
373         res = res_rm[rm]
374         res_pos = 0
375         rm_args = []
376         ignore_result_any = False
377         ignore_result_all = True
378         special = []
379         for d in descr_res:
380             if d == '1':
381                 special.append(res[res_pos])
382             elif DESCR_NUM_RES[d] == 1:
383                 result = res[res_pos]
384                 if result == 'IGNORE':
385                     ignore_result_any = True
386                     result = '0'
387                 else:
388                     ignore_result_all = False
389                     if d == 'f':
390                         result = apply_lit(result, 'LIT')
391                 rm_args.append(result)
392             else:
393                 # Complex result.
394                 result1 = res[res_pos]
395                 if result1 == 'IGNORE':
396                     ignore_result_any = True
397                     result1 = '0'
398                 else:
399                     ignore_result_all = False
400                     result1 = apply_lit(result1, 'LIT')
401                 rm_args.append(result1)
402                 result2 = res[res_pos + 1]
403                 if result2 == 'IGNORE':
404                     ignore_result_any = True
405                     result2 = '0'
406                 else:
407                     ignore_result_all = False
408                     result2 = apply_lit(result2, 'LIT')
409                 rm_args.append(result2)
410             res_pos += DESCR_NUM_RES[d]
411         if ignore_result_any and not ignore_result_all:
412             raise ValueError('some but not all function results ignored')
413         flags = []
414         if ignore_result_any:
415             flags.append('IGNORE_RESULT')
416         if test_snan:
417             flags.append('TEST_SNAN')
418         flags.append(res[res_pos])
419         rm_args.append('|'.join(flags))
420         for sp in special:
421             if sp == 'IGNORE':
422                 rm_args.extend(['0', '0'])
423             else:
424                 rm_args.extend(['1', apply_lit(sp, 'LIT')])
425         for k in sorted(ROUNDING_MAP.keys()):
426             rm_args = [arg.replace(k, ROUNDING_MAP[k][rm]) for arg in rm_args]
427         args_c.append('{ %s }' % ', '.join(rm_args))
428     return '    { "%s", %s },\n' % (args_disp_text, ', '.join(args_c))
429
430
431 def convert_condition(cond):
432     """Convert a condition from auto-libm-test-out to C form."""
433     conds = cond.split(':')
434     conds_c = []
435     for c in conds:
436         if not c.startswith('arg_fmt('):
437             c = c.replace('-', '_')
438         conds_c.append('TEST_COND_' + c)
439     return '(%s)' % ' && '.join(conds_c)
440
441
442 def cond_value(cond, if_val, else_val):
443     """Return a C conditional expression between two values."""
444     if cond == '1':
445         return if_val
446     elif cond == '0':
447         return else_val
448     else:
449         return '(%s ? %s : %s)' % (cond, if_val, else_val)
450
451
452 def gen_auto_tests(auto_tests, descr_args, descr_res, fn):
453     """Generate C code for the auto-libm-test-out-* tests for a function."""
454     for rm_idx, rm_name in enumerate(ROUNDING_MODES):
455         this_tests = sorted(auto_tests[fn][rm_name].keys())
456         if rm_idx == 0:
457             rm_tests = this_tests
458             if not rm_tests:
459                 raise ValueError('no automatic tests for %s' % fn)
460         else:
461             if rm_tests != this_tests:
462                 raise ValueError('inconsistent lists of tests of %s' % fn)
463     test_list = []
464     for test in rm_tests:
465         fmt_args = test.split()
466         fmt = fmt_args[0]
467         args = fmt_args[1:]
468         test_list.append('#if %s\n' % convert_condition(fmt))
469         res_rm = []
470         for rm in ROUNDING_MODES:
471             test_out = auto_tests[fn][rm][test]
472             out_str, flags_str = test_out.split(':', 1)
473             this_res = out_str.split()
474             flags = flags_str.split()
475             flag_cond = {}
476             for flag in flags:
477                 m = re.match(r'([^:]*):(.*)\Z', flag)
478                 if m:
479                     f_name = m.group(1)
480                     cond = convert_condition(m.group(2))
481                     if f_name in flag_cond:
482                         if flag_cond[f_name] != '1':
483                             flag_cond[f_name] = ('%s || %s'
484                                                  % (flag_cond[f_name], cond))
485                     else:
486                         flag_cond[f_name] = cond
487                 else:
488                     flag_cond[flag] = '1'
489             flags_c = []
490             for flag in sorted(FLAGS_SIMPLE.keys()):
491                 if flag in flag_cond:
492                     flags_c.append(cond_value(flag_cond[flag],
493                                               FLAGS_SIMPLE[flag], '0'))
494             for exc in sorted(EXC_EXPECTED.keys()):
495                 exc_expected = EXC_EXPECTED[exc]
496                 exc_ok = EXC_OK[exc]
497                 no_exc = EXC_NO[exc]
498                 exc_cond = flag_cond.get(exc, '0')
499                 exc_ok_cond = flag_cond.get(exc + '-ok', '0')
500                 flags_c.append(cond_value(exc_cond,
501                                           cond_value(exc_ok_cond, exc_ok,
502                                                      exc_expected),
503                                           cond_value(exc_ok_cond, exc_ok,
504                                                      no_exc)))
505             if 'errno-edom' in flag_cond and 'errno-erange' in flag_cond:
506                 raise ValueError('multiple errno values expected')
507             if 'errno-edom' in flag_cond:
508                 if flag_cond['errno-edom'] != '1':
509                     raise ValueError('unexpected condition for errno-edom')
510                 errno_expected = 'ERRNO_EDOM'
511             elif 'errno-erange' in flag_cond:
512                 if flag_cond['errno-erange'] != '1':
513                     raise ValueError('unexpected condition for errno-erange')
514                 errno_expected = 'ERRNO_ERANGE'
515             else:
516                 errno_expected = 'ERRNO_UNCHANGED'
517             if 'errno-edom-ok' in flag_cond:
518                 if ('errno-erange-ok' in flag_cond
519                     and (flag_cond['errno-erange-ok']
520                          != flag_cond['errno-edom-ok'])):
521                     errno_unknown_cond = ('%s || %s'
522                                           % (flag_cond['errno-edom-ok'],
523                                              flag_cond['errno-erange-ok']))
524                 else:
525                     errno_unknown_cond = flag_cond['errno-edom-ok']
526             else:
527                 errno_unknown_cond = flag_cond.get('errno-erange-ok', '0')
528             flags_c.append(cond_value(errno_unknown_cond, '0', errno_expected))
529             flags_c = [flag for flag in flags_c if flag != '0']
530             if not flags_c:
531                 flags_c = ['NO_EXCEPTION']
532             this_res.append(' | '.join(flags_c))
533             res_rm.append(this_res)
534         test_list.append(gen_test_args_res(descr_args, descr_res, args,
535                                            res_rm))
536         test_list.append('#endif\n')
537     return ''.join(test_list)
538
539
540 def gen_test_line(descr_args, descr_res, args_str):
541     """Generate C code for the tests for a single TEST_* line."""
542     test_args = args_str.split(',')
543     test_args = test_args[1:]
544     test_args = [a.strip() for a in test_args]
545     num_args = sum([DESCR_NUM_ARGS[c] for c in descr_args])
546     num_res = sum([DESCR_NUM_RES[c] for c in descr_res])
547     args = test_args[:num_args]
548     res = test_args[num_args:]
549     if len(res) == num_res:
550         # One set of results for all rounding modes, no flags.
551         res.append('0')
552         res_rm = [res, res, res, res]
553     elif len(res) == num_res + 1:
554         # One set of results for all rounding modes, with flags.
555         if not ('EXCEPTION' in res[-1]
556                 or 'ERRNO' in res[-1]
557                 or 'IGNORE_ZERO_INF_SIGN' in res[-1]
558                 or 'TEST_NAN_SIGN' in res[-1]
559                 or 'XFAIL' in res[-1]):
560             raise ValueError('wrong number of arguments: %s' % args_str)
561         res_rm = [res, res, res, res]
562     elif len(res) == (num_res + 1) * 4:
563         # One set of results per rounding mode, with flags.
564         nr_plus = num_res + 1
565         res_rm = [res[:nr_plus], res[nr_plus:2*nr_plus],
566                   res[2*nr_plus:3*nr_plus], res[3*nr_plus:]]
567     return gen_test_args_res(descr_args, descr_res, args, res_rm)
568
569
570 def generate_testfile(inc_input, auto_tests, c_output):
571     """Generate test .c file from .inc input."""
572     test_list = []
573     with open(inc_input, 'r') as f:
574         for line in f:
575             line_strip = line.strip()
576             if line_strip.startswith('AUTO_TESTS_'):
577                 m = re.match(r'AUTO_TESTS_([^_]*)_([^_ ]*) *\(([^)]*)\),\Z',
578                              line_strip)
579                 if not m:
580                     raise ValueError('bad AUTO_TESTS line: %s' % line)
581                 test_list.append(gen_auto_tests(auto_tests, m.group(1),
582                                                 m.group(2), m.group(3)))
583             elif line_strip.startswith('TEST_'):
584                 m = re.match(r'TEST_([^_]*)_([^_ ]*) *\((.*)\),\Z', line_strip)
585                 if not m:
586                     raise ValueError('bad TEST line: %s' % line)
587                 test_list.append(gen_test_line(m.group(1), m.group(2),
588                                                m.group(3)))
589             else:
590                 test_list.append(line)
591     with open(c_output, 'w') as f:
592         f.write(''.join(test_list))
593
594
595 def generate_err_table_sub(all_ulps, all_functions, fns_complex, platforms):
596     """Generate a single table within the overall ulps table section."""
597     plat_width = [' {1000 + i 1000}' for p in platforms]
598     plat_header = [' @tab %s' % p for p in platforms]
599     table_list = ['@multitable {nexttowardf} %s\n' % ''.join(plat_width),
600                   '@item Function %s\n' % ''.join(plat_header)]
601     for func in all_functions:
602         for flt in ALL_FLOATS_MANUAL:
603             func_ulps = []
604             for p in platforms:
605                 p_ulps = all_ulps[p]
606                 if fns_complex[func]:
607                     ulp_real = p_ulps.real[func][flt]
608                     ulp_imag = p_ulps.imag[func][flt]
609                     ulp_str = '%d + i %d' % (ulp_real, ulp_imag)
610                     ulp_str = ulp_str if ulp_real or ulp_imag else '-'
611                 else:
612                     ulp = p_ulps.normal[func][flt]
613                     ulp_str = str(ulp) if ulp else '-'
614                 func_ulps.append(ulp_str)
615             table_list.append('@item %s%s  @tab %s\n'
616                               % (func, ALL_FLOATS_SUFFIX[flt],
617                                  ' @tab '.join(func_ulps)))
618     table_list.append('@end multitable\n')
619     return ''.join(table_list)
620
621
622 def generate_err_table(all_ulps, err_table):
623     """Generate ulps table for manual."""
624     all_platforms = sorted(all_ulps.keys())
625     functions_set = set()
626     functions_complex = {}
627     for p in all_platforms:
628         p_functions, p_complex = all_ulps[p].all_functions()
629         functions_set.update(p_functions)
630         functions_complex.update(p_complex)
631     all_functions = sorted([f for f in functions_set
632                             if ('_downward' not in f
633                                 and '_towardzero' not in f
634                                 and '_upward' not in f
635                                 and '_vlen' not in f)])
636     err_table_list = []
637     # Print five platforms at a time.
638     num_platforms = len(all_platforms)
639     for i in range((num_platforms + 4) // 5):
640         start = i * 5
641         end = i * 5 + 5 if num_platforms >= i * 5 + 5 else num_platforms
642         err_table_list.append(generate_err_table_sub(all_ulps, all_functions,
643                                                      functions_complex,
644                                                      all_platforms[start:end]))
645     with open(err_table, 'w') as f:
646         f.write(''.join(err_table_list))
647
648
649 def main():
650     """The main entry point."""
651     parser = argparse.ArgumentParser(description='Generate libm tests.')
652     parser.add_argument('-a', dest='auto_input', metavar='FILE',
653                         help='input file with automatically generated tests')
654     parser.add_argument('-c', dest='inc_input', metavar='FILE',
655                         help='input file .inc file with tests')
656     parser.add_argument('-u', dest='ulps_file', metavar='FILE',
657                         help='input file with ulps')
658     parser.add_argument('-s', dest='srcdir', metavar='DIR',
659                         help='input source directory with all ulps')
660     parser.add_argument('-n', dest='ulps_output', metavar='FILE',
661                         help='generate sorted ulps file FILE')
662     parser.add_argument('-C', dest='c_output', metavar='FILE',
663                         help='generate output C file FILE from .inc file')
664     parser.add_argument('-H', dest='ulps_header', metavar='FILE',
665                         help='generate output ulps header FILE')
666     parser.add_argument('-m', dest='err_table', metavar='FILE',
667                         help='generate output ulps table for manual FILE')
668     args = parser.parse_args()
669     ulps = Ulps()
670     if args.ulps_file is not None:
671         ulps.read(args.ulps_file)
672     auto_tests = read_auto_tests(args.auto_input)
673     if args.srcdir is not None:
674         all_ulps = read_all_ulps(args.srcdir)
675     if args.ulps_output is not None:
676         ulps.write(args.ulps_output)
677     if args.ulps_header is not None:
678         ulps.write_header(args.ulps_header)
679     if args.c_output is not None:
680         generate_testfile(args.inc_input, auto_tests, args.c_output)
681     if args.err_table is not None:
682         generate_err_table(all_ulps, args.err_table)
683
684
685 if __name__ == '__main__':
686     main()