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