/*
* main.c
*
* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
* See https://llvm.org/LICENSE.txt for license information.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#include "intern.h"
void gencases(Testable *fn, int number);
void docase(Testable *fn, uint32 *args);
void vet_for_decline(Testable *fn, uint32 *args, uint32 *result, int got_errno_in);
void seed_random(uint32 seed);
int check_declines = 0;
int lib_fo = 0;
int lib_no_arith = 0;
int ntests = 0;
int nargs_(Testable* f) {
switch((f)->type) {
case args2:
case args2f:
case semi2:
case semi2f:
case t_ldexp:
case t_ldexpf:
case args1c:
case args1fc:
case args1cr:
case args1fcr:
case compare:
case comparef:
return 2;
case args2c:
case args2fc:
return 4;
default:
return 1;
}
}
static int isdouble(Testable *f)
{
switch (f->type) {
case args1:
case rred:
case semi1:
case t_frexp:
case t_modf:
case classify:
case t_ldexp:
case args2:
case semi2:
case args1c:
case args1cr:
case compare:
case args2c:
return 1;
case args1f:
case rredf:
case semi1f:
case t_frexpf:
case t_modff:
case classifyf:
case args2f:
case semi2f:
case t_ldexpf:
case comparef:
case args1fc:
case args1fcr:
case args2fc:
return 0;
default:
assert(0 && "Bad function type");
}
}
Testable *find_function(const char *func)
{
int i;
for (i = 0; i < nfunctions; i++) {
if (func && !strcmp(func, functions[i].name)) {
return &functions[i];
}
}
return NULL;
}
void get_operand(const char *str, Testable *f, uint32 *word0, uint32 *word1)
{
struct special {
unsigned dblword0, dblword1, sglword;
const char *name;
} specials[] = {
{0x00000000,0x00000000,0x00000000,"0"},
{0x3FF00000,0x00000000,0x3f800000,"1"},
{0x7FF00000,0x00000000,0x7f800000,"inf"},
{0x7FF80000,0x00000001,0x7fc00000,"qnan"},
{0x7FF00000,0x00000001,0x7f800001,"snan"},
{0x3ff921fb,0x54442d18,0x3fc90fdb,"pi2"},
{0x400921fb,0x54442d18,0x40490fdb,"pi"},
{0x3fe921fb,0x54442d18,0x3f490fdb,"pi4"},
{0x4002d97c,0x7f3321d2,0x4016cbe4,"3pi4"},
};
int i;
for (i = 0; i < (int)(sizeof(specials)/sizeof(*specials)); i++) {
if (!strcmp(str, specials[i].name) ||
((str[0] == '-' || str[0] == '+') &&
!strcmp(str+1, specials[i].name))) {
assert(f);
if (isdouble(f)) {
*word0 = specials[i].dblword0;
*word1 = specials[i].dblword1;
} else {
*word0 = specials[i].sglword;
*word1 = 0;
}
if (str[0] == '-')
*word0 |= 0x80000000U;
return;
}
}
sscanf(str, "%"I32"x.%"I32"x", word0, word1);
}
void dofile(FILE *fp, int translating) {
char buf[1024], sparebuf[1024], *p;
/*
* Command syntax is:
*
* - "seed <integer>" sets a random seed
*
* - "test <function> <ntests>" generates random test lines
*
* - "<function> op1=foo [op2=bar]" generates a specific test
* - "func=<function> op1=foo [op2=bar]" does the same
* - "func=<function> op1=foo result=bar" will just output the line as-is
*
* - a semicolon or a blank line is ignored
*/
while (fgets(buf, sizeof(buf), fp)) {
buf[strcspn(buf, "\r\n")] = '\0';
strcpy(sparebuf, buf);
p = buf;
while (*p && isspace(*p)) p++;
if (!*p || *p == ';') {
/* Comment or blank line. Only print if `translating' is set. */
if (translating)
printf("%s\n", buf);
continue;
}
if (!strncmp(buf, "seed ", 5)) {
seed_random(atoi(buf+5));
} else if (!strncmp(buf, "random=", 7)) {
/*
* Copy 'random=on' / 'random=off' lines unconditionally
* to the output, so that random test failures can be
* accumulated into a recent-failures-list file and
* still identified as random-in-origin when re-run the
* next day.
*/
printf("%s\n", buf);
} else if (!strncmp(buf, "test ", 5)) {
char *p = buf+5;
char *q;
int ntests, i;
q = p;
while (*p && !isspace(*p)) p++;
if (*p) *p++ = '\0';
while (*p && isspace(*p)) p++;
if (*p)
ntests = atoi(p);
else
ntests = 100; /* *shrug* */
for (i = 0; i < nfunctions; i++) {
if (!strcmp(q, functions[i].name)) {
gencases(&functions[i], ntests);
break;
}
}
if (i == nfunctions) {
fprintf(stderr, "unknown test `%s'\n", q);
}
} else {
/*
* Parse a specific test line.
*/
uint32 ops[8], result[8];
int got_op = 0; /* &1 for got_op1, &4 for got_op3 etc. */
Testable *f = 0;
char *q, *r;
int got_result = 0, got_errno_in = 0;
for (q = strtok(p, " \t"); q; q = strtok(NULL, " \t")) {
r = strchr(q, '=');
if (!r) {
f = find_function(q);
} else {
*r++ = '\0';
if (!strcmp(q, "func"))
f = find_function(r);
else if (!strcmp(q, "op1") || !strcmp(q, "op1r")) {
get_operand(r, f, &ops[0], &ops[1]);
got_op |= 1;
} else if (!strcmp(q, "op2") || !strcmp(q, "op1i")) {
get_operand(r, f, &ops[2], &ops[3]);
got_op |= 2;
} else if (!strcmp(q, "op2r")) {
get_operand(r, f, &ops[4], &ops[5]);
got_op |= 4;
} else if (!strcmp(q, "op2i")) {
get_operand(r, f, &ops[6], &ops[7]);
got_op |= 8;
} else if (!strcmp(q, "result") || !strcmp(q, "resultr")) {
get_operand(r, f, &result[0], &result[1]);
got_result |= 1;
} else if (!strcmp(q, "resulti")) {
get_operand(r, f, &result[4], &result[5]);
got_result |= 2;
} else if (!strcmp(q, "res2")) {
get_operand(r, f, &result[2], &result[3]);
got_result |= 4;
} else if (!strcmp(q, "errno_in")) {
got_errno_in = 1;
}
}
}
/*
* Test cases already set up by the input are not
* reprocessed by default, unlike the fplib tests. (This
* is mostly for historical reasons, because we used to
* use a very slow and incomplete internal reference
* implementation; now our ref impl is MPFR/MPC it
* probably wouldn't be such a bad idea, though we'd still
* have to make sure all the special cases came out
* right.) If translating==2 (corresponding to the -T
* command-line option) then we regenerate everything
* regardless.
*/
if (got_result && translating < 2) {
if (f)
vet_for_decline(f, ops, result, got_errno_in);
puts(sparebuf);
continue;
}
if (f && got_op==(1<<nargs_(f))-1) {
/*
* And do it!
*/
docase(f, ops);
}
}
}
}
int main(int argc, char **argv) {
int errs = 0, opts = 1, files = 0, translating = 0;
unsigned int seed = 1; /* in case no explicit seed provided */
seed_random(seed);
setvbuf(stdout, NULL, _IOLBF, BUFSIZ); /* stops incomplete lines being printed when out of time */
while (--argc) {
FILE *fp;
char *p = *++argv;
if (opts && *p == '-') {
if(*(p+1) == 0) { /* single -, read from stdin */
break;
} else if (!strcmp(p, "-t")) {
translating = 1;
} else if (!strcmp(p, "-T")) {
translating = 2;
} else if (!strcmp(p, "-c")) {
check_declines = 1;
} else if (!strcmp(p, "--")) {
opts = 0;
} else if (!strcmp(p,"--seed") && argc > 1 && 1==sscanf(*(argv+1),"%u",&seed)) {
seed_random(seed);
argv++; /* next in argv is seed value, so skip */
--argc;
} else if (!strcmp(p, "-fo")) {
lib_fo = 1;
} else if (!strcmp(p, "-noarith")) {
lib_no_arith = 1;
} else {
fprintf(stderr,
"rtest: ignoring unrecognised option '%s'\n", p);
errs = 1;
}
} else {
files = 1;
if (!errs) {
fp = fopen(p, "r");
if (fp) {
dofile(fp, translating);
fclose(fp);
} else {
perror(p);
errs = 1;
}
}
}
}
/*
* If no filename arguments, use stdin.
*/
if (!files && !errs) {
dofile(stdin, translating);
}
if (check_declines) {
fprintf(stderr, "Tests expected to run: %d\n", ntests);
fflush(stderr);
}
return errs;
}