| // SPDX-License-Identifier: GPL-2.0 |
| #undef _GNU_SOURCE |
| #define _GNU_SOURCE 1 |
| #undef __USE_GNU |
| #define __USE_GNU 1 |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <signal.h> |
| #include <sys/types.h> |
| #include <sys/select.h> |
| #include <sys/time.h> |
| #include <sys/wait.h> |
| #include <fenv.h> |
| |
| enum { |
| CF = 1 << 0, |
| PF = 1 << 2, |
| ZF = 1 << 6, |
| ARITH = CF | PF | ZF, |
| }; |
| |
| long res_fcomi_pi_1; |
| long res_fcomi_1_pi; |
| long res_fcomi_1_1; |
| long res_fcomi_nan_1; |
| /* sNaN is s|111 1111 1|1xx xxxx xxxx xxxx xxxx xxxx */ |
| /* qNaN is s|111 1111 1|0xx xxxx xxxx xxxx xxxx xxxx (some x must be nonzero) */ |
| int snan = 0x7fc11111; |
| int qnan = 0x7f811111; |
| unsigned short snan1[5]; |
| /* sNaN80 is s|111 1111 1111 1111 |10xx xx...xx (some x must be nonzero) */ |
| unsigned short snan80[5] = { 0x1111, 0x1111, 0x1111, 0x8111, 0x7fff }; |
| |
| int test(long flags) |
| { |
| feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); |
| |
| asm ("\n" |
| |
| " push %0""\n" |
| " popf""\n" |
| " fld1""\n" |
| " fldpi""\n" |
| " fcomi %%st(1), %%st" "\n" |
| " ffree %%st(0)" "\n" |
| " ffree %%st(1)" "\n" |
| " pushf""\n" |
| " pop res_fcomi_1_pi""\n" |
| |
| " push %0""\n" |
| " popf""\n" |
| " fldpi""\n" |
| " fld1""\n" |
| " fcomi %%st(1), %%st" "\n" |
| " ffree %%st(0)" "\n" |
| " ffree %%st(1)" "\n" |
| " pushf""\n" |
| " pop res_fcomi_pi_1""\n" |
| |
| " push %0""\n" |
| " popf""\n" |
| " fld1""\n" |
| " fld1""\n" |
| " fcomi %%st(1), %%st" "\n" |
| " ffree %%st(0)" "\n" |
| " ffree %%st(1)" "\n" |
| " pushf""\n" |
| " pop res_fcomi_1_1""\n" |
| : |
| : "r" (flags) |
| ); |
| if ((res_fcomi_1_pi & ARITH) != (0)) { |
| printf("[BAD]\tfcomi_1_pi with flags:%lx\n", flags); |
| return 1; |
| } |
| if ((res_fcomi_pi_1 & ARITH) != (CF)) { |
| printf("[BAD]\tfcomi_pi_1 with flags:%lx->%lx\n", flags, res_fcomi_pi_1 & ARITH); |
| return 1; |
| } |
| if ((res_fcomi_1_1 & ARITH) != (ZF)) { |
| printf("[BAD]\tfcomi_1_1 with flags:%lx\n", flags); |
| return 1; |
| } |
| if (fetestexcept(FE_INVALID) != 0) { |
| printf("[BAD]\tFE_INVALID is set in %s\n", __func__); |
| return 1; |
| } |
| return 0; |
| } |
| |
| int test_qnan(long flags) |
| { |
| feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); |
| |
| asm ("\n" |
| " push %0""\n" |
| " popf""\n" |
| " flds qnan""\n" |
| " fld1""\n" |
| " fnclex""\n" // fld of a qnan raised FE_INVALID, clear it |
| " fcomi %%st(1), %%st" "\n" |
| " ffree %%st(0)" "\n" |
| " ffree %%st(1)" "\n" |
| " pushf""\n" |
| " pop res_fcomi_nan_1""\n" |
| : |
| : "r" (flags) |
| ); |
| if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) { |
| printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags); |
| return 1; |
| } |
| if (fetestexcept(FE_INVALID) != FE_INVALID) { |
| printf("[BAD]\tFE_INVALID is not set in %s\n", __func__); |
| return 1; |
| } |
| return 0; |
| } |
| |
| int testu_qnan(long flags) |
| { |
| feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); |
| |
| asm ("\n" |
| " push %0""\n" |
| " popf""\n" |
| " flds qnan""\n" |
| " fld1""\n" |
| " fnclex""\n" // fld of a qnan raised FE_INVALID, clear it |
| " fucomi %%st(1), %%st" "\n" |
| " ffree %%st(0)" "\n" |
| " ffree %%st(1)" "\n" |
| " pushf""\n" |
| " pop res_fcomi_nan_1""\n" |
| : |
| : "r" (flags) |
| ); |
| if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) { |
| printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags); |
| return 1; |
| } |
| if (fetestexcept(FE_INVALID) != 0) { |
| printf("[BAD]\tFE_INVALID is set in %s\n", __func__); |
| return 1; |
| } |
| return 0; |
| } |
| |
| int testu_snan(long flags) |
| { |
| feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); |
| |
| asm ("\n" |
| " push %0""\n" |
| " popf""\n" |
| // " flds snan""\n" // WRONG, this will convert 32-bit fp snan to a *qnan* in 80-bit fp register! |
| // " fstpt snan1""\n" // if uncommented, it prints "snan1:7fff c111 1100 0000 0000" - c111, not 8111! |
| // " fnclex""\n" // flds of a snan raised FE_INVALID, clear it |
| " fldt snan80""\n" // fldt never raise FE_INVALID |
| " fld1""\n" |
| " fucomi %%st(1), %%st" "\n" |
| " ffree %%st(0)" "\n" |
| " ffree %%st(1)" "\n" |
| " pushf""\n" |
| " pop res_fcomi_nan_1""\n" |
| : |
| : "r" (flags) |
| ); |
| if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) { |
| printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags); |
| return 1; |
| } |
| // printf("snan:%x snan1:%04x %04x %04x %04x %04x\n", snan, snan1[4], snan1[3], snan1[2], snan1[1], snan1[0]); |
| if (fetestexcept(FE_INVALID) != FE_INVALID) { |
| printf("[BAD]\tFE_INVALID is not set in %s\n", __func__); |
| return 1; |
| } |
| return 0; |
| } |
| |
| int testp(long flags) |
| { |
| feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); |
| |
| asm ("\n" |
| |
| " push %0""\n" |
| " popf""\n" |
| " fld1""\n" |
| " fldpi""\n" |
| " fcomip %%st(1), %%st" "\n" |
| " ffree %%st(0)" "\n" |
| " pushf""\n" |
| " pop res_fcomi_1_pi""\n" |
| |
| " push %0""\n" |
| " popf""\n" |
| " fldpi""\n" |
| " fld1""\n" |
| " fcomip %%st(1), %%st" "\n" |
| " ffree %%st(0)" "\n" |
| " pushf""\n" |
| " pop res_fcomi_pi_1""\n" |
| |
| " push %0""\n" |
| " popf""\n" |
| " fld1""\n" |
| " fld1""\n" |
| " fcomip %%st(1), %%st" "\n" |
| " ffree %%st(0)" "\n" |
| " pushf""\n" |
| " pop res_fcomi_1_1""\n" |
| : |
| : "r" (flags) |
| ); |
| if ((res_fcomi_1_pi & ARITH) != (0)) { |
| printf("[BAD]\tfcomi_1_pi with flags:%lx\n", flags); |
| return 1; |
| } |
| if ((res_fcomi_pi_1 & ARITH) != (CF)) { |
| printf("[BAD]\tfcomi_pi_1 with flags:%lx->%lx\n", flags, res_fcomi_pi_1 & ARITH); |
| return 1; |
| } |
| if ((res_fcomi_1_1 & ARITH) != (ZF)) { |
| printf("[BAD]\tfcomi_1_1 with flags:%lx\n", flags); |
| return 1; |
| } |
| if (fetestexcept(FE_INVALID) != 0) { |
| printf("[BAD]\tFE_INVALID is set in %s\n", __func__); |
| return 1; |
| } |
| return 0; |
| } |
| |
| int testp_qnan(long flags) |
| { |
| feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); |
| |
| asm ("\n" |
| " push %0""\n" |
| " popf""\n" |
| " flds qnan""\n" |
| " fld1""\n" |
| " fnclex""\n" // fld of a qnan raised FE_INVALID, clear it |
| " fcomip %%st(1), %%st" "\n" |
| " ffree %%st(0)" "\n" |
| " pushf""\n" |
| " pop res_fcomi_nan_1""\n" |
| : |
| : "r" (flags) |
| ); |
| if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) { |
| printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags); |
| return 1; |
| } |
| if (fetestexcept(FE_INVALID) != FE_INVALID) { |
| printf("[BAD]\tFE_INVALID is not set in %s\n", __func__); |
| return 1; |
| } |
| return 0; |
| } |
| |
| int testup_qnan(long flags) |
| { |
| feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW); |
| |
| asm ("\n" |
| " push %0""\n" |
| " popf""\n" |
| " flds qnan""\n" |
| " fld1""\n" |
| " fnclex""\n" // fld of a qnan raised FE_INVALID, clear it |
| " fucomip %%st(1), %%st" "\n" |
| " ffree %%st(0)" "\n" |
| " pushf""\n" |
| " pop res_fcomi_nan_1""\n" |
| : |
| : "r" (flags) |
| ); |
| if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) { |
| printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags); |
| return 1; |
| } |
| if (fetestexcept(FE_INVALID) != 0) { |
| printf("[BAD]\tFE_INVALID is set in %s\n", __func__); |
| return 1; |
| } |
| return 0; |
| } |
| |
| void sighandler(int sig) |
| { |
| printf("[FAIL]\tGot signal %d, exiting\n", sig); |
| exit(1); |
| } |
| |
| int main(int argc, char **argv, char **envp) |
| { |
| int err = 0; |
| |
| /* SIGILL triggers on 32-bit kernels w/o fcomi emulation |
| * when run with "no387 nofxsr". Other signals are caught |
| * just in case. |
| */ |
| signal(SIGILL, sighandler); |
| signal(SIGFPE, sighandler); |
| signal(SIGSEGV, sighandler); |
| |
| printf("[RUN]\tTesting f[u]comi[p] instructions\n"); |
| err |= test(0); |
| err |= test_qnan(0); |
| err |= testu_qnan(0); |
| err |= testu_snan(0); |
| err |= test(CF|ZF|PF); |
| err |= test_qnan(CF|ZF|PF); |
| err |= testu_qnan(CF|ZF|PF); |
| err |= testu_snan(CF|ZF|PF); |
| err |= testp(0); |
| err |= testp_qnan(0); |
| err |= testup_qnan(0); |
| err |= testp(CF|ZF|PF); |
| err |= testp_qnan(CF|ZF|PF); |
| err |= testup_qnan(CF|ZF|PF); |
| if (!err) |
| printf("[OK]\tf[u]comi[p]\n"); |
| else |
| printf("[FAIL]\tf[u]comi[p] errors: %d\n", err); |
| |
| return err; |
| } |