1 /*
2  * main.c
3  *
4  * Copyright (c) 1999-2019, Arm Limited.
5  * SPDX-License-Identifier: MIT
6  */
7 
8 #include <assert.h>
9 #include <stdio.h>
10 #include <string.h>
11 #include <ctype.h>
12 #include <stdlib.h>
13 #include <time.h>
14 
15 #include "intern.h"
16 
17 void gencases(Testable *fn, int number);
18 void docase(Testable *fn, uint32 *args);
19 void vet_for_decline(Testable *fn, uint32 *args, uint32 *result, int got_errno_in);
20 void seed_random(uint32 seed);
21 
22 int check_declines = 0;
23 int lib_fo = 0;
24 int lib_no_arith = 0;
25 int ntests = 0;
26 
27 int nargs_(Testable* f) {
28     switch((f)->type) {
29     case args2:
30     case args2f:
31     case semi2:
32     case semi2f:
33     case t_ldexp:
34     case t_ldexpf:
35     case args1c:
36     case args1fc:
37     case args1cr:
38     case args1fcr:
39     case compare:
40     case comparef:
41         return 2;
42     case args2c:
43     case args2fc:
44         return 4;
45     default:
46         return 1;
47     }
48 }
49 
50 static int isdouble(Testable *f)
51 {
52     switch (f->type) {
53       case args1:
54       case rred:
55       case semi1:
56       case t_frexp:
57       case t_modf:
58       case classify:
59       case t_ldexp:
60       case args2:
61       case semi2:
62       case args1c:
63       case args1cr:
64       case compare:
65       case args2c:
66         return 1;
67       case args1f:
68       case rredf:
69       case semi1f:
70       case t_frexpf:
71       case t_modff:
72       case classifyf:
73       case args2f:
74       case semi2f:
75       case t_ldexpf:
76       case comparef:
77       case args1fc:
78       case args1fcr:
79       case args2fc:
80         return 0;
81       default:
82         assert(0 && "Bad function type");
83     }
84 }
85 
86 Testable *find_function(const char *func)
87 {
88     int i;
89     for (i = 0; i < nfunctions; i++) {
90         if (func && !strcmp(func, functions[i].name)) {
91             return &functions[i];
92         }
93     }
94     return NULL;
95 }
96 
97 void get_operand(const char *str, Testable *f, uint32 *word0, uint32 *word1)
98 {
99     struct special {
100         unsigned dblword0, dblword1, sglword;
101         const char *name;
102     } specials[] = {
103         {0x00000000,0x00000000,0x00000000,"0"},
104         {0x3FF00000,0x00000000,0x3f800000,"1"},
105         {0x7FF00000,0x00000000,0x7f800000,"inf"},
106         {0x7FF80000,0x00000001,0x7fc00000,"qnan"},
107         {0x7FF00000,0x00000001,0x7f800001,"snan"},
108         {0x3ff921fb,0x54442d18,0x3fc90fdb,"pi2"},
109         {0x400921fb,0x54442d18,0x40490fdb,"pi"},
110         {0x3fe921fb,0x54442d18,0x3f490fdb,"pi4"},
111         {0x4002d97c,0x7f3321d2,0x4016cbe4,"3pi4"},
112     };
113     int i;
114 
115     for (i = 0; i < (int)(sizeof(specials)/sizeof(*specials)); i++) {
116         if (!strcmp(str, specials[i].name) ||
117             ((str[0] == '-' || str[0] == '+') &&
118              !strcmp(str+1, specials[i].name))) {
119             assert(f);
120             if (isdouble(f)) {
121                 *word0 = specials[i].dblword0;
122                 *word1 = specials[i].dblword1;
123             } else {
124                 *word0 = specials[i].sglword;
125                 *word1 = 0;
126             }
127             if (str[0] == '-')
128                 *word0 |= 0x80000000U;
129             return;
130         }
131     }
132 
133     sscanf(str, "%"I32"x.%"I32"x", word0, word1);
134 }
135 
136 void dofile(FILE *fp, int translating) {
137     char buf[1024], sparebuf[1024], *p;
138 
139     /*
140      * Command syntax is:
141      *
142      *  - "seed <integer>" sets a random seed
143      *
144      *  - "test <function> <ntests>" generates random test lines
145      *
146      *  - "<function> op1=foo [op2=bar]" generates a specific test
147      *  - "func=<function> op1=foo [op2=bar]" does the same
148      *  - "func=<function> op1=foo result=bar" will just output the line as-is
149      *
150      *  - a semicolon or a blank line is ignored
151      */
152     while (fgets(buf, sizeof(buf), fp)) {
153         buf[strcspn(buf, "\r\n")] = '\0';
154         strcpy(sparebuf, buf);
155         p = buf;
156         while (*p && isspace(*p)) p++;
157         if (!*p || *p == ';') {
158             /* Comment or blank line. Only print if `translating' is set. */
159             if (translating)
160                 printf("%s\n", buf);
161             continue;
162         }
163         if (!strncmp(buf, "seed ", 5)) {
164             seed_random(atoi(buf+5));
165         } else if (!strncmp(buf, "random=", 7)) {
166             /*
167              * Copy 'random=on' / 'random=off' lines unconditionally
168              * to the output, so that random test failures can be
169              * accumulated into a recent-failures-list file and
170              * still identified as random-in-origin when re-run the
171              * next day.
172              */
173             printf("%s\n", buf);
174         } else if (!strncmp(buf, "test ", 5)) {
175             char *p = buf+5;
176             char *q;
177             int ntests, i;
178             q = p;
179             while (*p && !isspace(*p)) p++;
180             if (*p) *p++ = '\0';
181             while (*p && isspace(*p)) p++;
182             if (*p)
183                 ntests = atoi(p);
184             else
185                 ntests = 100;          /* *shrug* */
186             for (i = 0; i < nfunctions; i++) {
187                 if (!strcmp(q, functions[i].name)) {
188                     gencases(&functions[i], ntests);
189                     break;
190                 }
191             }
192             if (i == nfunctions) {
193                 fprintf(stderr, "unknown test `%s'\n", q);
194             }
195         } else {
196             /*
197              * Parse a specific test line.
198              */
199             uint32 ops[8], result[8];
200             int got_op = 0; /* &1 for got_op1, &4 for got_op3 etc. */
201             Testable *f = 0;
202             char *q, *r;
203             int got_result = 0, got_errno_in = 0;
204 
205             for (q = strtok(p, " \t"); q; q = strtok(NULL, " \t")) {
206                 r = strchr(q, '=');
207                 if (!r) {
208                     f = find_function(q);
209                 } else {
210                     *r++ = '\0';
211 
212                     if (!strcmp(q, "func"))
213                         f = find_function(r);
214                     else if (!strcmp(q, "op1") || !strcmp(q, "op1r")) {
215                         get_operand(r, f, &ops[0], &ops[1]);
216                         got_op |= 1;
217                     } else if (!strcmp(q, "op2") || !strcmp(q, "op1i")) {
218                         get_operand(r, f, &ops[2], &ops[3]);
219                         got_op |= 2;
220                     } else if (!strcmp(q, "op2r")) {
221                         get_operand(r, f, &ops[4], &ops[5]);
222                         got_op |= 4;
223                     } else if (!strcmp(q, "op2i")) {
224                         get_operand(r, f, &ops[6], &ops[7]);
225                         got_op |= 8;
226                     } else if (!strcmp(q, "result") || !strcmp(q, "resultr")) {
227                         get_operand(r, f, &result[0], &result[1]);
228                         got_result |= 1;
229                     } else if (!strcmp(q, "resulti")) {
230                         get_operand(r, f, &result[4], &result[5]);
231                         got_result |= 2;
232                     } else if (!strcmp(q, "res2")) {
233                         get_operand(r, f, &result[2], &result[3]);
234                         got_result |= 4;
235                     } else if (!strcmp(q, "errno_in")) {
236                         got_errno_in = 1;
237                     }
238                 }
239             }
240 
241             /*
242              * Test cases already set up by the input are not
243              * reprocessed by default, unlike the fplib tests. (This
244              * is mostly for historical reasons, because we used to
245              * use a very slow and incomplete internal reference
246              * implementation; now our ref impl is MPFR/MPC it
247              * probably wouldn't be such a bad idea, though we'd still
248              * have to make sure all the special cases came out
249              * right.) If translating==2 (corresponding to the -T
250              * command-line option) then we regenerate everything
251              * regardless.
252              */
253             if (got_result && translating < 2) {
254                 if (f)
255                     vet_for_decline(f, ops, result, got_errno_in);
256                 puts(sparebuf);
257                 continue;
258             }
259 
260             if (f && got_op==(1<<nargs_(f))-1) {
261                 /*
262                  * And do it!
263                  */
264                 docase(f, ops);
265             }
266         }
267     }
268 }
269 
270 int main(int argc, char **argv) {
271     int errs = 0, opts = 1, files = 0, translating = 0;
272     unsigned int seed = 1; /* in case no explicit seed provided */
273 
274     seed_random(seed);
275 
276     setvbuf(stdout, NULL, _IOLBF, BUFSIZ); /* stops incomplete lines being printed when out of time */
277 
278     while (--argc) {
279         FILE *fp;
280         char *p = *++argv;
281 
282         if (opts && *p == '-') {
283             if(*(p+1) == 0) { /* single -, read from stdin */
284                 break;
285             } else if (!strcmp(p, "-t")) {
286                 translating = 1;
287             } else if (!strcmp(p, "-T")) {
288                 translating = 2;
289             } else if (!strcmp(p, "-c")) {
290                 check_declines = 1;
291             } else if (!strcmp(p, "--")) {
292                 opts = 0;
293             } else if (!strcmp(p,"--seed") && argc > 1 && 1==sscanf(*(argv+1),"%u",&seed)) {
294                 seed_random(seed);
295                 argv++; /* next in argv is seed value, so skip */
296                 --argc;
297             } else if (!strcmp(p, "-fo")) {
298                 lib_fo = 1;
299             } else if (!strcmp(p, "-noarith")) {
300                 lib_no_arith = 1;
301             } else {
302                 fprintf(stderr,
303                         "rtest: ignoring unrecognised option '%s'\n", p);
304                 errs = 1;
305             }
306         } else {
307             files = 1;
308             if (!errs) {
309                 fp = fopen(p, "r");
310                 if (fp) {
311                     dofile(fp, translating);
312                     fclose(fp);
313                 } else {
314                     perror(p);
315                     errs = 1;
316                 }
317             }
318         }
319     }
320 
321     /*
322      * If no filename arguments, use stdin.
323      */
324     if (!files && !errs) {
325         dofile(stdin, translating);
326     }
327 
328     if (check_declines) {
329         fprintf(stderr, "Tests expected to run: %d\n", ntests);
330         fflush(stderr);
331     }
332 
333     return errs;
334 }
335