1#!/usr/bin/python3 2# Generate tests for libm functions. 3# Copyright (C) 2018-2022 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 20import argparse 21from collections import defaultdict 22import os 23import re 24 25 26# Sorted list of all float types in ulps files. 27ALL_FLOATS = ('double', 'float', 'float128', 'ldouble') 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 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 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', 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. 100EXC_EXPECTED = {'divbyzero': 'DIVBYZERO_EXCEPTION', 101 'inexact': 'INEXACT_EXCEPTION', 102 'invalid': 'INVALID_EXCEPTION', 103 'overflow': 'OVERFLOW_EXCEPTION', 104 'underflow': 'UNDERFLOW_EXCEPTION'} 105EXC_OK = {'divbyzero': 'DIVBYZERO_EXCEPTION_OK', 106 'inexact': '0', 107 'invalid': 'INVALID_EXCEPTION_OK', 108 'overflow': 'OVERFLOW_EXCEPTION_OK', 109 'underflow': 'UNDERFLOW_EXCEPTION_OK'} 110EXC_NO = {'divbyzero': '0', 111 'inexact': 'NO_INEXACT_EXCEPTION', 112 'invalid': '0', 113 'overflow': '0', 114 'underflow': '0'} 115 116 117class 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 254def 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 266def 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 285def 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 298def 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 308def 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 333def 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 340def 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 431def 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 442def 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 452def 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 540def 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 570def 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 595def 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 622def 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 649def 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 685if __name__ == '__main__': 686 main() 687