| /* |
| * 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; |
| } |