mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-10 19:14:58 -06:00

Add a test to prevent regressions. Share some useful pieces with the vfminmax test. Remove the duplicates from the floating point class values. Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-ID: <20241023000147.34035-3-iii@linux.ibm.com> Signed-off-by: Thomas Huth <thuth@redhat.com>
233 lines
8.2 KiB
C
233 lines
8.2 KiB
C
/*
|
|
* Test floating-point multiply-and-add instructions.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
#include <fenv.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "float.h"
|
|
|
|
union val {
|
|
float e;
|
|
double d;
|
|
long double x;
|
|
char buf[16];
|
|
};
|
|
|
|
/*
|
|
* PoP tables as close to the original as possible.
|
|
*/
|
|
static const char *table1[N_SIGNED_CLASSES][N_SIGNED_CLASSES] = {
|
|
/* -inf -Fn -0 +0 +Fn +inf QNaN SNaN */
|
|
{/* -inf */ "P(+inf)", "P(+inf)", "Xi: T(dNaN)", "Xi: T(dNaN)", "P(-inf)", "P(-inf)", "P(b)", "Xi: T(b*)"},
|
|
{/* -Fn */ "P(+inf)", "P(a*b)", "P(+0)", "P(-0)", "P(a*b)", "P(-inf)", "P(b)", "Xi: T(b*)"},
|
|
{/* -0 */ "Xi: T(dNaN)", "P(+0)", "P(+0)", "P(-0)", "P(-0)", "Xi: T(dNaN)", "P(b)", "Xi: T(b*)"},
|
|
{/* +0 */ "Xi: T(dNaN)", "P(-0)", "P(-0)", "P(+0)", "P(+0)", "Xi: T(dNaN)", "P(b)", "Xi: T(b*)"},
|
|
{/* +Fn */ "P(-inf)", "P(a*b)", "P(-0)", "P(+0)", "P(a*b)", "P(+inf)", "P(b)", "Xi: T(b*)"},
|
|
{/* +inf */ "P(-inf)", "P(-inf)", "Xi: T(dNaN)", "Xi: T(dNaN)", "P(+inf)", "P(+inf)", "P(b)", "Xi: T(b*)"},
|
|
{/* QNaN */ "P(a)", "P(a)", "P(a)", "P(a)", "P(a)", "P(a)", "P(a)", "Xi: T(b*)"},
|
|
{/* SNaN */ "Xi: T(a*)", "Xi: T(a*)", "Xi: T(a*)", "Xi: T(a*)", "Xi: T(a*)", "Xi: T(a*)", "Xi: T(a*)", "Xi: T(a*)"},
|
|
};
|
|
|
|
static const char *table2[N_SIGNED_CLASSES][N_SIGNED_CLASSES] = {
|
|
/* -inf -Fn -0 +0 +Fn +inf QNaN SNaN */
|
|
{/* -inf */ "T(-inf)", "T(-inf)", "T(-inf)", "T(-inf)", "T(-inf)", "Xi: T(dNaN)", "T(c)", "Xi: T(c*)"},
|
|
{/* -Fn */ "T(-inf)", "R(p+c)", "R(p)", "R(p)", "R(p+c)", "T(+inf)", "T(c)", "Xi: T(c*)"},
|
|
{/* -0 */ "T(-inf)", "R(c)", "T(-0)", "Rezd", "R(c)", "T(+inf)", "T(c)", "Xi: T(c*)"},
|
|
{/* +0 */ "T(-inf)", "R(c)", "Rezd", "T(+0)", "R(c)", "T(+inf)", "T(c)", "Xi: T(c*)"},
|
|
{/* +Fn */ "T(-inf)", "R(p+c)", "R(p)", "R(p)", "R(p+c)", "T(+inf)", "T(c)", "Xi: T(c*)"},
|
|
{/* +inf */ "Xi: T(dNaN)", "T(+inf)", "T(+inf)", "T(+inf)", "T(+inf)", "T(+inf)", "T(c)", "Xi: T(c*)"},
|
|
{/* QNaN */ "T(p)", "T(p)", "T(p)", "T(p)", "T(p)", "T(p)", "T(p)", "Xi: T(c*)"},
|
|
/* SNaN: can't happen */
|
|
};
|
|
|
|
static void interpret_tables(union val *r, bool *xi, int fmt,
|
|
int cls_a, const union val *a,
|
|
int cls_b, const union val *b,
|
|
int cls_c, const union val *c)
|
|
{
|
|
const char *spec1 = table1[cls_a][cls_b];
|
|
const char *spec2;
|
|
union val p;
|
|
int cls_p;
|
|
|
|
*xi = false;
|
|
|
|
if (strcmp(spec1, "P(-inf)") == 0) {
|
|
cls_p = CLASS_MINUS_INF;
|
|
} else if (strcmp(spec1, "P(+inf)") == 0) {
|
|
cls_p = CLASS_PLUS_INF;
|
|
} else if (strcmp(spec1, "P(-0)") == 0) {
|
|
cls_p = CLASS_MINUS_ZERO;
|
|
} else if (strcmp(spec1, "P(+0)") == 0) {
|
|
cls_p = CLASS_PLUS_ZERO;
|
|
} else if (strcmp(spec1, "P(a)") == 0) {
|
|
cls_p = cls_a;
|
|
memcpy(&p, a, sizeof(p));
|
|
} else if (strcmp(spec1, "P(b)") == 0) {
|
|
cls_p = cls_b;
|
|
memcpy(&p, b, sizeof(p));
|
|
} else if (strcmp(spec1, "P(a*b)") == 0) {
|
|
/*
|
|
* In the general case splitting fma into multiplication and addition
|
|
* doesn't work, but this is the case with our test inputs.
|
|
*/
|
|
cls_p = cls_a == cls_b ? CLASS_PLUS_FN : CLASS_MINUS_FN;
|
|
switch (fmt) {
|
|
case 0:
|
|
p.e = a->e * b->e;
|
|
break;
|
|
case 1:
|
|
p.d = a->d * b->d;
|
|
break;
|
|
case 2:
|
|
p.x = a->x * b->x;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unsupported fmt: %d\n", fmt);
|
|
exit(1);
|
|
}
|
|
} else if (strcmp(spec1, "Xi: T(dNaN)") == 0) {
|
|
memcpy(r, default_nans[fmt], sizeof(*r));
|
|
*xi = true;
|
|
return;
|
|
} else if (strcmp(spec1, "Xi: T(a*)") == 0) {
|
|
memcpy(r, a, sizeof(*r));
|
|
snan_to_qnan(r->buf, fmt);
|
|
*xi = true;
|
|
return;
|
|
} else if (strcmp(spec1, "Xi: T(b*)") == 0) {
|
|
memcpy(r, b, sizeof(*r));
|
|
snan_to_qnan(r->buf, fmt);
|
|
*xi = true;
|
|
return;
|
|
} else {
|
|
fprintf(stderr, "Unsupported spec1: %s\n", spec1);
|
|
exit(1);
|
|
}
|
|
|
|
spec2 = table2[cls_p][cls_c];
|
|
if (strcmp(spec2, "T(-inf)") == 0) {
|
|
memcpy(r, signed_floats[fmt][CLASS_MINUS_INF].v[0], sizeof(*r));
|
|
} else if (strcmp(spec2, "T(+inf)") == 0) {
|
|
memcpy(r, signed_floats[fmt][CLASS_PLUS_INF].v[0], sizeof(*r));
|
|
} else if (strcmp(spec2, "T(-0)") == 0) {
|
|
memcpy(r, signed_floats[fmt][CLASS_MINUS_ZERO].v[0], sizeof(*r));
|
|
} else if (strcmp(spec2, "T(+0)") == 0 || strcmp(spec2, "Rezd") == 0) {
|
|
memcpy(r, signed_floats[fmt][CLASS_PLUS_ZERO].v[0], sizeof(*r));
|
|
} else if (strcmp(spec2, "R(c)") == 0 || strcmp(spec2, "T(c)") == 0) {
|
|
memcpy(r, c, sizeof(*r));
|
|
} else if (strcmp(spec2, "R(p)") == 0 || strcmp(spec2, "T(p)") == 0) {
|
|
memcpy(r, &p, sizeof(*r));
|
|
} else if (strcmp(spec2, "R(p+c)") == 0 || strcmp(spec2, "T(p+c)") == 0) {
|
|
switch (fmt) {
|
|
case 0:
|
|
r->e = p.e + c->e;
|
|
break;
|
|
case 1:
|
|
r->d = p.d + c->d;
|
|
break;
|
|
case 2:
|
|
r->x = p.x + c->x;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unsupported fmt: %d\n", fmt);
|
|
exit(1);
|
|
}
|
|
} else if (strcmp(spec2, "Xi: T(dNaN)") == 0) {
|
|
memcpy(r, default_nans[fmt], sizeof(*r));
|
|
*xi = true;
|
|
} else if (strcmp(spec2, "Xi: T(c*)") == 0) {
|
|
memcpy(r, c, sizeof(*r));
|
|
snan_to_qnan(r->buf, fmt);
|
|
*xi = true;
|
|
} else {
|
|
fprintf(stderr, "Unsupported spec2: %s\n", spec2);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
struct iter {
|
|
int fmt;
|
|
int cls[3];
|
|
int val[3];
|
|
};
|
|
|
|
static bool iter_next(struct iter *it)
|
|
{
|
|
int i;
|
|
|
|
for (i = 2; i >= 0; i--) {
|
|
if (++it->val[i] != signed_floats[it->fmt][it->cls[i]].n) {
|
|
return true;
|
|
}
|
|
it->val[i] = 0;
|
|
|
|
if (++it->cls[i] != N_SIGNED_CLASSES) {
|
|
return true;
|
|
}
|
|
it->cls[i] = 0;
|
|
}
|
|
|
|
return ++it->fmt != N_FORMATS;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int ret = EXIT_SUCCESS;
|
|
struct iter it = {};
|
|
|
|
do {
|
|
size_t n = float_sizes[it.fmt];
|
|
union val a, b, c, exp, res;
|
|
bool xi_exp, xi;
|
|
|
|
memcpy(&a, signed_floats[it.fmt][it.cls[0]].v[it.val[0]], sizeof(a));
|
|
memcpy(&b, signed_floats[it.fmt][it.cls[1]].v[it.val[1]], sizeof(b));
|
|
memcpy(&c, signed_floats[it.fmt][it.cls[2]].v[it.val[2]], sizeof(c));
|
|
|
|
interpret_tables(&exp, &xi_exp, it.fmt,
|
|
it.cls[1], &b, it.cls[2], &c, it.cls[0], &a);
|
|
|
|
memcpy(&res, &a, sizeof(res));
|
|
feclearexcept(FE_ALL_EXCEPT);
|
|
switch (it.fmt) {
|
|
case 0:
|
|
asm("maebr %[a],%[b],%[c]"
|
|
: [a] "+f" (res.e) : [b] "f" (b.e), [c] "f" (c.e));
|
|
break;
|
|
case 1:
|
|
asm("madbr %[a],%[b],%[c]"
|
|
: [a] "+f" (res.d) : [b] "f" (b.d), [c] "f" (c.d));
|
|
break;
|
|
case 2:
|
|
asm("wfmaxb %[a],%[c],%[b],%[a]"
|
|
: [a] "+v" (res.x) : [b] "v" (b.x), [c] "v" (c.x));
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unsupported fmt: %d\n", it.fmt);
|
|
exit(1);
|
|
}
|
|
xi = fetestexcept(FE_ALL_EXCEPT) == FE_INVALID;
|
|
|
|
if (memcmp(&res, &exp, n) != 0 || xi != xi_exp) {
|
|
fprintf(stderr, "[ FAILED ] ");
|
|
dump_v(stderr, &b, n);
|
|
fprintf(stderr, " * ");
|
|
dump_v(stderr, &c, n);
|
|
fprintf(stderr, " + ");
|
|
dump_v(stderr, &a, n);
|
|
fprintf(stderr, ": actual=");
|
|
dump_v(stderr, &res, n);
|
|
fprintf(stderr, "/%d, expected=", (int)xi);
|
|
dump_v(stderr, &exp, n);
|
|
fprintf(stderr, "/%d\n", (int)xi_exp);
|
|
ret = EXIT_FAILURE;
|
|
}
|
|
} while (iter_next(&it));
|
|
|
|
return ret;
|
|
}
|