| #define _GNU_SOURCE |
| #include <fenv.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include "float.h" |
| |
| /* |
| * vfmin/vfmax instruction execution. |
| */ |
| #define VFMIN 0xEE |
| #define VFMAX 0xEF |
| |
| extern char insn[6]; |
| asm(".pushsection .rwx,\"awx\",@progbits\n" |
| ".globl insn\n" |
| /* e7 89 a0 00 2e ef */ |
| "insn: vfmaxsb %v24,%v25,%v26,0\n" |
| ".popsection\n"); |
| |
| static void vfminmax(unsigned int op, |
| unsigned int m4, unsigned int m5, unsigned int m6, |
| void *v1, const void *v2, const void *v3) |
| { |
| insn[3] = (m6 << 4) | m5; |
| insn[4] = (m4 << 4) | 0x0e; |
| insn[5] = op; |
| |
| asm("vl %%v25,%[v2]\n" |
| "vl %%v26,%[v3]\n" |
| "ex 0,%[insn]\n" |
| "vst %%v24,%[v1]\n" |
| : [v1] "=m" (*(char (*)[16])v1) |
| : [v2] "m" (*(const char (*)[16])v2) |
| , [v3] "m" (*(const char (*)[16])v3) |
| , [insn] "m" (insn) |
| : "v24", "v25", "v26"); |
| } |
| |
| /* |
| * PoP tables as close to the original as possible. |
| */ |
| struct signed_test { |
| int op; |
| int m6; |
| const char *m6_desc; |
| const char *table[N_SIGNED_CLASSES][N_SIGNED_CLASSES]; |
| } signed_tests[] = { |
| { |
| .op = VFMIN, |
| .m6 = 0, |
| .m6_desc = "IEEE MinNum", |
| .table = { |
| /* -inf -Fn -0 +0 +Fn +inf QNaN SNaN */ |
| {/* -inf */ "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(b*)"}, |
| {/* -Fn */ "T(b)", "T(M(a,b))", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(b*)"}, |
| {/* -0 */ "T(b)", "T(b)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(b*)"}, |
| {/* +0 */ "T(b)", "T(b)", "T(b)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(b*)"}, |
| {/* +Fn */ "T(b)", "T(b)", "T(b)", "T(b)", "T(M(a,b))", "T(a)", "T(a)", "Xi: T(b*)"}, |
| {/* +inf */ "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(a)", "T(a)", "Xi: T(b*)"}, |
| {/* QNaN */ "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(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*)"}, |
| }, |
| }, |
| { |
| .op = VFMIN, |
| .m6 = 1, |
| .m6_desc = "JAVA Math.Min()", |
| .table = { |
| /* -inf -Fn -0 +0 +Fn +inf QNaN SNaN */ |
| {/* -inf */ "T(b)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(b)", "Xi: T(b*)"}, |
| {/* -Fn */ "T(b)", "T(M(a,b))", "T(a)", "T(a)", "T(a)", "T(a)", "T(b)", "Xi: T(b*)"}, |
| {/* -0 */ "T(b)", "T(b)", "T(b)", "T(a)", "T(a)", "T(a)", "T(b)", "Xi: T(b*)"}, |
| {/* +0 */ "T(b)", "T(b)", "T(b)", "T(b)", "T(a)", "T(a)", "T(b)", "Xi: T(b*)"}, |
| {/* +Fn */ "T(b)", "T(b)", "T(b)", "T(b)", "T(M(a,b))", "T(a)", "T(b)", "Xi: T(b*)"}, |
| {/* +inf */ "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "Xi: T(b*)"}, |
| {/* QNaN */ "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(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*)"}, |
| }, |
| }, |
| { |
| .op = VFMIN, |
| .m6 = 2, |
| .m6_desc = "C-style Min Macro", |
| .table = { |
| /* -inf -Fn -0 +0 +Fn +inf QNaN SNaN */ |
| {/* -inf */ "T(b)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(b)", "Xi: T(b)"}, |
| {/* -Fn */ "T(b)", "T(M(a,b))", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(b)", "Xi: T(b)"}, |
| {/* -0 */ "T(b)", "T(b)", "T(b)", "T(b)", "T(a)", "T(a)", "Xi: T(b)", "Xi: T(b)"}, |
| {/* +0 */ "T(b)", "T(b)", "T(b)", "T(b)", "T(a)", "T(a)", "Xi: T(b)", "Xi: T(b)"}, |
| {/* +Fn */ "T(b)", "T(b)", "T(b)", "T(b)", "T(M(a,b))", "T(a)", "Xi: T(b)", "Xi: T(b)"}, |
| {/* +inf */ "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(a)", "Xi: T(b)", "Xi: T(b)"}, |
| {/* QNaN */ "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)"}, |
| {/* SNaN */ "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)"}, |
| }, |
| }, |
| { |
| .op = VFMIN, |
| .m6 = 3, |
| .m6_desc = "C++ algorithm.min()", |
| .table = { |
| /* -inf -Fn -0 +0 +Fn +inf QNaN SNaN */ |
| {/* -inf */ "T(b)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(a)", "Xi: T(a)"}, |
| {/* -Fn */ "T(b)", "T(M(a,b))", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(a)", "Xi: T(a)"}, |
| {/* -0 */ "T(b)", "T(b)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(a)", "Xi: T(a)"}, |
| {/* +0 */ "T(b)", "T(b)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(a)", "Xi: T(a)"}, |
| {/* +Fn */ "T(b)", "T(b)", "T(b)", "T(b)", "T(M(a,b))", "T(a)", "Xi: T(a)", "Xi: T(a)"}, |
| {/* +inf */ "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(a)", "Xi: T(a)", "Xi: T(a)"}, |
| {/* QNaN */ "Xi: T(a)", "Xi: T(a)", "Xi: T(a)", "Xi: T(a)", "Xi: T(a)", "Xi: T(a)", "Xi: T(a)", "Xi: T(a)"}, |
| {/* 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)"}, |
| }, |
| }, |
| { |
| .op = VFMIN, |
| .m6 = 4, |
| .m6_desc = "fmin()", |
| .table = { |
| /* -inf -Fn -0 +0 +Fn +inf QNaN SNaN */ |
| {/* -inf */ "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(a)"}, |
| {/* -Fn */ "T(b)", "T(M(a,b))", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(a)"}, |
| {/* -0 */ "T(b)", "T(b)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(a)"}, |
| {/* +0 */ "T(b)", "T(b)", "T(b)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(a)"}, |
| {/* +Fn */ "T(b)", "T(b)", "T(b)", "T(b)", "T(M(a,b))", "T(a)", "T(a)", "Xi: T(a)"}, |
| {/* +inf */ "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(a)", "T(a)", "Xi: T(a)"}, |
| {/* QNaN */ "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(a)", "Xi: T(a)"}, |
| {/* SNaN */ "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(a)", "Xi: T(a)"}, |
| }, |
| }, |
| |
| { |
| .op = VFMAX, |
| .m6 = 0, |
| .m6_desc = "IEEE MaxNum", |
| .table = { |
| /* -inf -Fn -0 +0 +Fn +inf QNaN SNaN */ |
| {/* -inf */ "T(a)", "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(a)", "Xi: T(b*)"}, |
| {/* -Fn */ "T(a)", "T(M(a,b))", "T(b)", "T(b)", "T(b)", "T(b)", "T(a)", "Xi: T(b*)"}, |
| {/* -0 */ "T(a)", "T(a)", "T(a)", "T(b)", "T(b)", "T(b)", "T(a)", "Xi: T(b*)"}, |
| {/* +0 */ "T(a)", "T(a)", "T(a)", "T(a)", "T(b)", "T(b)", "T(a)", "Xi: T(b*)"}, |
| {/* +Fn */ "T(a)", "T(a)", "T(a)", "T(a)", "T(M(a,b))", "T(b)", "T(a)", "Xi: T(b*)"}, |
| {/* +inf */ "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(b*)"}, |
| {/* QNaN */ "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(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*)"}, |
| }, |
| }, |
| { |
| .op = VFMAX, |
| .m6 = 1, |
| .m6_desc = "JAVA Math.Max()", |
| .table = { |
| /* -inf -Fn -0 +0 +Fn +inf QNaN SNaN */ |
| {/* -inf */ "T(a)", "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "Xi: T(b*)"}, |
| {/* -Fn */ "T(a)", "T(M(a,b))", "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "Xi: T(b*)"}, |
| {/* -0 */ "T(a)", "T(a)", "T(a)", "T(b)", "T(b)", "T(b)", "T(b)", "Xi: T(b*)"}, |
| {/* +0 */ "T(a)", "T(a)", "T(a)", "T(a)", "T(b)", "T(b)", "T(b)", "Xi: T(b*)"}, |
| {/* +Fn */ "T(a)", "T(a)", "T(a)", "T(a)", "T(M(a,b))", "T(b)", "T(b)", "Xi: T(b*)"}, |
| {/* +inf */ "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(b)", "Xi: T(b*)"}, |
| {/* QNaN */ "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(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*)"}, |
| }, |
| }, |
| { |
| .op = VFMAX, |
| .m6 = 2, |
| .m6_desc = "C-style Max Macro", |
| .table = { |
| /* -inf -Fn -0 +0 +Fn +inf QNaN SNaN */ |
| {/* -inf */ "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "Xi: T(b)", "Xi: T(b)"}, |
| {/* -Fn */ "T(a)", "T(M(a,b))", "T(b)", "T(b)", "T(b)", "T(b)", "Xi: T(b)", "Xi: T(b)"}, |
| {/* -0 */ "T(a)", "T(a)", "T(b)", "T(b)", "T(b)", "T(b)", "Xi: T(b)", "Xi: T(b)"}, |
| {/* +0 */ "T(a)", "T(a)", "T(b)", "T(b)", "T(b)", "T(b)", "Xi: T(b)", "Xi: T(b)"}, |
| {/* +Fn */ "T(a)", "T(a)", "T(a)", "T(a)", "T(M(a,b))", "T(b)", "Xi: T(b)", "Xi: T(b)"}, |
| {/* +inf */ "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(b)", "Xi: T(b)", "Xi: T(b)"}, |
| {/* QNaN */ "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)"}, |
| {/* SNaN */ "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)"}, |
| }, |
| }, |
| { |
| .op = VFMAX, |
| .m6 = 3, |
| .m6_desc = "C++ algorithm.max()", |
| .table = { |
| /* -inf -Fn -0 +0 +Fn +inf QNaN SNaN */ |
| {/* -inf */ "T(a)", "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "Xi: T(a)", "Xi: T(a)"}, |
| {/* -Fn */ "T(a)", "T(M(a,b))", "T(b)", "T(b)", "T(b)", "T(b)", "Xi: T(a)", "Xi: T(a)"}, |
| {/* -0 */ "T(a)", "T(a)", "T(a)", "T(a)", "T(b)", "T(b)", "Xi: T(a)", "Xi: T(a)"}, |
| {/* +0 */ "T(a)", "T(a)", "T(a)", "T(a)", "T(b)", "T(b)", "Xi: T(a)", "Xi: T(a)"}, |
| {/* +Fn */ "T(a)", "T(a)", "T(a)", "T(a)", "T(M(a,b))", "T(b)", "Xi: T(a)", "Xi: T(a)"}, |
| {/* +inf */ "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(a)", "Xi: T(a)"}, |
| {/* QNaN */ "Xi: T(a)", "Xi: T(a)", "Xi: T(a)", "Xi: T(a)", "Xi: T(a)", "Xi: T(a)", "Xi: T(a)", "Xi: T(a)"}, |
| {/* 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)"}, |
| }, |
| }, |
| { |
| .op = VFMAX, |
| .m6 = 4, |
| .m6_desc = "fmax()", |
| .table = { |
| /* -inf -Fn -0 +0 +Fn +inf QNaN SNaN */ |
| {/* -inf */ "T(a)", "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(a)", "Xi: T(a)"}, |
| {/* -Fn */ "T(a)", "T(M(a,b))", "T(b)", "T(b)", "T(b)", "T(b)", "T(a)", "Xi: T(a)"}, |
| {/* -0 */ "T(a)", "T(a)", "T(a)", "T(b)", "T(b)", "T(b)", "T(a)", "Xi: T(a)"}, |
| {/* +0 */ "T(a)", "T(a)", "T(a)", "T(a)", "T(b)", "T(b)", "T(a)", "Xi: T(a)"}, |
| {/* +Fn */ "T(a)", "T(a)", "T(a)", "T(a)", "T(M(a,b))", "T(b)", "T(a)", "Xi: T(a)"}, |
| {/* +inf */ "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "T(a)", "Xi: T(a)"}, |
| {/* QNaN */ "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(b)", "T(a)", "Xi: T(a)"}, |
| {/* SNaN */ "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(b)", "Xi: T(a)", "Xi: T(a)"}, |
| }, |
| }, |
| }; |
| |
| static int signed_test(struct signed_test *test, int m4, int m5, |
| const void *v1_exp, bool xi_exp, |
| const void *v2, const void *v3) |
| { |
| size_t n = (m5 & 8) ? float_sizes[m4 - 2] : 16; |
| char v1[16]; |
| bool xi; |
| |
| feclearexcept(FE_ALL_EXCEPT); |
| vfminmax(test->op, m4, m5, test->m6, v1, v2, v3); |
| xi = fetestexcept(FE_ALL_EXCEPT) == FE_INVALID; |
| |
| if (memcmp(v1, v1_exp, n) != 0 || xi != xi_exp) { |
| fprintf(stderr, "[ FAILED ] %s ", test->m6_desc); |
| dump_v(stderr, v2, n); |
| fprintf(stderr, ", "); |
| dump_v(stderr, v3, n); |
| fprintf(stderr, ", %d, %d, %d: actual=", m4, m5, test->m6); |
| dump_v(stderr, v1, n); |
| fprintf(stderr, "/%d, expected=", (int)xi); |
| dump_v(stderr, v1_exp, n); |
| fprintf(stderr, "/%d\n", (int)xi_exp); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| struct iter { |
| int cls[2]; |
| int val[2]; |
| }; |
| |
| static bool iter_next(struct iter *it, int fmt) |
| { |
| int i; |
| |
| for (i = 1; i >= 0; i--) { |
| if (++it->val[i] != signed_floats[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 false; |
| } |
| |
| int main(void) |
| { |
| int ret = 0; |
| size_t i; |
| |
| for (i = 0; i < sizeof(signed_tests) / sizeof(signed_tests[0]); i++) { |
| struct signed_test *test = &signed_tests[i]; |
| int fmt; |
| |
| for (fmt = 0; fmt < N_FORMATS; fmt++) { |
| size_t float_size = float_sizes[fmt]; |
| int m4 = fmt + 2; |
| int m5; |
| |
| for (m5 = 0; m5 <= 8; m5 += 8) { |
| char v1_exp[16], v2[16], v3[16]; |
| bool xi_exp = false; |
| struct iter it = {}; |
| int pos = 0; |
| |
| do { |
| const char *spec = test->table[it.cls[0]][it.cls[1]]; |
| |
| memcpy(&v2[pos], |
| signed_floats[fmt][it.cls[0]].v[it.val[0]], |
| float_size); |
| memcpy(&v3[pos], |
| signed_floats[fmt][it.cls[1]].v[it.val[1]], |
| float_size); |
| if (strcmp(spec, "T(a)") == 0 || |
| strcmp(spec, "Xi: T(a)") == 0) { |
| memcpy(&v1_exp[pos], &v2[pos], float_size); |
| } else if (strcmp(spec, "T(b)") == 0 || |
| strcmp(spec, "Xi: T(b)") == 0) { |
| memcpy(&v1_exp[pos], &v3[pos], float_size); |
| } else if (strcmp(spec, "Xi: T(a*)") == 0) { |
| memcpy(&v1_exp[pos], &v2[pos], float_size); |
| snan_to_qnan(&v1_exp[pos], fmt); |
| } else if (strcmp(spec, "Xi: T(b*)") == 0) { |
| memcpy(&v1_exp[pos], &v3[pos], float_size); |
| snan_to_qnan(&v1_exp[pos], fmt); |
| } else if (strcmp(spec, "T(M(a,b))") == 0) { |
| /* |
| * Comparing floats is risky, since the compiler might |
| * generate the same instruction that we are testing. |
| * Compare ints instead. This works, because we get |
| * here only for +-Fn, and the corresponding test |
| * values have identical exponents. |
| */ |
| int v2_int = *(int *)&v2[pos]; |
| int v3_int = *(int *)&v3[pos]; |
| |
| if ((v2_int < v3_int) == |
| ((test->op == VFMIN) != (v2_int < 0))) { |
| memcpy(&v1_exp[pos], &v2[pos], float_size); |
| } else { |
| memcpy(&v1_exp[pos], &v3[pos], float_size); |
| } |
| } else { |
| fprintf(stderr, "Unexpected spec: %s\n", spec); |
| return 1; |
| } |
| xi_exp |= spec[0] == 'X'; |
| pos += float_size; |
| |
| if ((m5 & 8) || pos == 16) { |
| ret |= signed_test(test, m4, m5, |
| v1_exp, xi_exp, v2, v3); |
| pos = 0; |
| xi_exp = false; |
| } |
| } while (iter_next(&it, fmt)); |
| |
| if (pos != 0) { |
| ret |= signed_test(test, m4, m5, v1_exp, xi_exp, v2, v3); |
| } |
| } |
| } |
| } |
| |
| return ret; |
| } |