Search code examples
cgccfloating-pointfloating-point-exceptions

gcc -O0 still optimizes out "unused" code that should raise an FP exception. Is there a compile flag to change that?


As I brought up in this question, gcc is removing (yes, with -O0) a line of code _mm_div_ss(s1, s2); presumably because the result is not saved. However, this should trigger a floating point exception and raise SIGFPE, which can't happen if the call is removed.

Question: Is there a flag, or multiple flags, to pass to gcc so that code is compiled as-is? I'm thinking something like fno-remove-unused but I'm not seeing anything like that. Ideally this would be a compiler flag instead of having to change my source code, but if that isn't supported is there some gcc attribute/pragma to use instead?

Things I've tried:

$ gcc --help=optimizers | grep -i remove

no results.

$ gcc --help=optimizers | grep -i unused

no results.

And explicitly disabling all dead code/elimination flags -- note that there is no warning about unused code:

$ gcc -O0 -msse2 -Wall -Wextra -pedantic -Winline \
     -fno-dce -fno-dse -fno-tree-dce \
     -fno-tree-dse -fno-tree-fre -fno-compare-elim -fno-gcse  \
     -fno-gcse-after-reload -fno-gcse-las -fno-rerun-cse-after-loop \
     -fno-tree-builtin-call-dce -fno-tree-cselim a.c
a.c: In function ‘main’:
a.c:25:5: warning: ISO C90 forbids mixed declarations and code [-Wpedantic]
     __m128 s1, s2;
     ^
$

Source program

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <xmmintrin.h>

static void sigaction_sfpe(int signal, siginfo_t *si, void *arg)
{
    printf("%d,%d,%d\n", signal, si!=NULL?1:0, arg!=NULL?1:0);
    printf("inside SIGFPE handler\nexit now.\n");
    exit(1);
}

int main()
{
    struct sigaction sa;

    memset(&sa, 0, sizeof(sa));
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = sigaction_sfpe;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGFPE, &sa, NULL);

    _mm_setcsr(0x00001D80);

    __m128 s1, s2;
    s1 = _mm_set_ps(1.0, 1.0, 1.0, 1.0);
    s2 = _mm_set_ps(0.0, 0.0, 0.0, 0.0);
    _mm_div_ss(s1, s2);

    printf("done (no error).\n");

    return 0;
}

Compiling the above program gives

$ ./a.out
done (no error).

Changing the line

_mm_div_ss(s1, s2);

to

s2 = _mm_div_ss(s1, s2); // add "s2 = "

produces the expected result:

$ ./a.out
inside SIGFPE handler

Edit with more details.

This appears to be related to the __always_inline__ attribute on the _mm_div_ss definition.

$ cat t.c
int
div(int b)
{
    return 1/b;
}

int main()
{
    div(0);
    return 0;
}


$ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out
$  

(no warnings or errors)

$ ./t.out
Floating point exception
$

vs below (same except for function attributes)

$ cat t.c
__inline int __attribute__((__always_inline__))
div(int b)
{
    return 1/b;
}

int main()
{
    div(0);
    return 0;
}

$ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out
$   

(no warnings or errors)

$ ./t.out
$

Adding the function attribute __warn_unused_result__ at least gives a helpful message:

$ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out
t.c: In function ‘main’:
t.c:9:5: warning: ignoring return value of ‘div’, declared with attribute warn_unused_result [-Wunused-result]
     div(0);
     ^

edit:

Some discussion on the gcc mailing list. Ultimately, I think everything is working as intended.


Solution

  • GCC doesn't "optimize out" anything here. It just doesn't generate useless code. It seems to a very common illusion that there's some pure form of code that the compiler should generate and any changes to that are an "optimization". There is no such thing.

    The compiler creates some data structure that represents what the code means, then it applies some transformations on that data structure and from that it generates assembler that then gets compiled down to instructions. If you compile without "optimizations" it just means that the compiler will only do the least effort possible to generate code.

    In this case, the whole statement is useless because it doesn't do anything and is thrown away immediately (after expanding the inlines and what the builtins mean it is equivalent to writing a/b;, the difference is that writing a/b; will emit a warning about statement with no effect while the builtins probably aren't handled by the same warnings). This is not an optimization, the compiler would actually have to expend extra effort to invent meaning to a meaningless statement, then fake a temporary variable to store the result of this statement to then throw it away.

    What you're looking for is not flags to disable optimizations, but pessimization flags. I don't think any compiler developers waste time implementing such flags. Other than maybe as an April fools joke.