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