1/*
2 * main.c
3 *
4 * Copyright (c) 1999-2019, Arm Limited.
5 * SPDX-License-Identifier: MIT OR Apache-2.0 WITH LLVM-exception
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
17void gencases(Testable *fn, int number);
18void docase(Testable *fn, uint32 *args);
19void vet_for_decline(Testable *fn, uint32 *args, uint32 *result, int got_errno_in);
20void seed_random(uint32 seed);
21
22int check_declines = 0;
23int lib_fo = 0;
24int lib_no_arith = 0;
25int ntests = 0;
26
27int 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
50static 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
86Testable *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
97void 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
136void 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
270int 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