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