https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79593

            Bug ID: 79593
           Summary: [Regression] Poor/Worse code generation for FPU on
                    versions after 6
           Product: gcc
           Version: 6.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: rtl-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: katsunori.kumatani at gmail dot com
  Target Milestone: ---

First of all sorry if the "Component" is set wrong, I didn't know what to pick
(in respect to worse code generation than former versions) :)

Newer GCC versions seem to have regressed in their x87 code generation and do
it extremely poor mistakes. This is about a regression in code generation for
x87 code, not a "wishlist" of better code (i.e. it's about the possibility of
having it reverted back to v5, at least in respect to x87 code gen). Sometimes
I want to use it for various reasons, for example for "long double" high
intermediate precision.

So technically this is a "performance bug" regression: In fact GCC version 5
generates quite good x87 code! So I'd simply want that generation reverted as a
goal with this report (if the culprit is found of course), so don't
misunderstand my intention please.

For example, since GCC 6, when you "convert" from say a "float" to a "long
double", it generates spurious instructions like this sequence:

        fld     st(0)
        fcomip  st, st(2)

Instead of just:

        fcomip  st, st(1)

Which is IMHO killing one of the few nice features of x87 that it has automatic
conversions to 80-bit all the time at no extra cost.

Bear in mind, GCC 5 is just fine and does the latter which is optimal. My C++
code actually does all calculations in "long double" on purpose, but it has to
interface with data found in a "float" on a constant basis (reading it), and
also returning some of it as float or double. The problem is, as you can see,
newer versions of GCC do pointless "conversions" or something like that (I'm
not sure myself why it does those pointless loads) when you load a "float" into
a "long double" or something similar to that effect.

This is a two-fold problem: not only there's extra instructions, but it also
requires one more "space" in the register stack resulting in more pointless
spills (of long doubles!).


Now to illustrate what I mean so you can verify yourself, I've included a
stupid little testcase that doesn't do much of anything, but it seems to
exhibit this "poor code" generation, here's the code:



#include <stdint.h>

#undef MIN
#undef MAX
template<typename T> T inline MIN(T a, T b) { return a < b ? a : b; }
template<typename T> T inline MAX(T a, T b) { return a > b ? a : b; }

struct foo
{
  uint32_t num;
  union { uint32_t i; float f; };
};

extern float global_data[1024];

float bar(foo* __restrict__ e, uint32_t id)
{
  if(id >= e->num) return 0.0f;
  long double delta = (global_data[0]), min = (global_data[1]);
  delta = ((delta < 0.0l) ? (min-((long double)e->i)) : ((e->f)-min)) / delta;
  return (MIN(MAX(delta, 0.0l), 1.0l));
}



I compiled with e.g. -m32 -Ofast -mfpmath=387    (to have the return value in
st(0) to show this issue better)

Here's the outputs for GCC versions 5, 6 and 7, with comments from me showing
obvious poor code:


GCC 5:
   sub     esp, 12
   fldz
   mov     eax, DWORD PTR [esp+16]
   mov     ecx, DWORD PTR [esp+20]
   cmp     DWORD PTR [eax], ecx
   jbe     .L2
   fld     DWORD PTR global_data
   fld     DWORD PTR global_data+4
   fxch    st(2)
   fcomip  st, st(1)
   ja      .L12
   fxch    st(1)
   fsubr   DWORD PTR [eax+4]
 .L5:
   fdivrp  st(1), st
   fldz
   fxch    st(1)
   fcomi   st, st(1)
   fcmovb  st, st(1)
   fstp    st(1)
   fld1
   fcomi   st, st(1)
   fcmovnb st, st(1)
   fstp    st(1)
 .L2:
   add     esp, 12
   ret
 .L12:
   mov     eax, DWORD PTR [eax+4] # here the only issue I have with GCC 5
   xor     edx, edx
   mov     DWORD PTR [esp+4], edx # I don't understand what's this spill for?
   mov     DWORD PTR [esp], eax   # can't it load directly from [eax+4]?
   fild    QWORD PTR [esp]        # fild DWORD PTR [eax+4] or am I wrong?
   fsubp   st(2), st
   fxch    st(1)
   jmp     .L5



GCC 6:
   sub     esp, 12
   fldz
   mov     eax, DWORD PTR [esp+16]
   mov     ecx, DWORD PTR [esp+20]
   cmp     DWORD PTR [eax], ecx
   jbe     .L1
   fld     DWORD PTR global_data
   fld     st(0)                    # this is the poor "conversion" mentioned
   fld     DWORD PTR global_data+4
   fxch    st(3)
   fcomip  st, st(2)                # here's its "pop" (unneeded otherwise)
   fstp    st(1)
   ja      .L12
   fxch    st(1)
   fsubr   DWORD PTR [eax+4]
 .L5:
   fdivrp  st(1), st
   fldz
   fxch    st(1)
   fcomi   st, st(1)
   fcmovb  st, st(1)
   fstp    st(1)
   fld1
   fcomi   st, st(1)
   fcmovnb st, st(1)
   fstp    st(1)
 .L1:
   add     esp, 12
   ret
 .L12:
   mov     eax, DWORD PTR [eax+4]
   xor     edx, edx
   mov     DWORD PTR [esp+4], edx
   mov     DWORD PTR [esp], eax
   fild    QWORD PTR [esp]
   fsubp   st(2), st
   fxch    st(1)
   jmp     .L5



GCC 7:
   sub     esp, 12
   fldz
   mov     eax, DWORD PTR [esp+16]
   mov     edx, DWORD PTR [esp+20]
   cmp     DWORD PTR [eax], edx
   jbe     .L1
   fld     DWORD PTR global_data
   mov     eax, DWORD PTR [eax+4]
   fld     st(0)                    # same pointless instruction as v6
   fld     DWORD PTR global_data+4
   fxch    st(3)
   fcomip  st, st(2)
   fstp    st(1)
   ja      .L12
   mov     DWORD PTR [esp], eax     # worse than v6: spills to stack!
   fld     DWORD PTR [esp]          # instead of 'fsubr DWORD PTR [eax+4]'
   fsubrp  st(2), st
 .L5:
   fdivp   st(1), st
   fldz
   fxch    st(1)
   fcomi   st, st(1)
   fcmovb  st, st(1)
   fstp    st(1)
   fld1
   fld     st(0)                    # here it "converts" the constant 1.0l?
   fcomip  st, st(2)
   jnb     .L13
   fstp    st(1)
   jmp     .L6
 .L13:
   fstp    st(0)
 .L6:
 .L1:
   add     esp, 12
   ret
 .L12:
   mov     DWORD PTR [esp], eax
   mov     DWORD PTR [esp+4], 0
   fild    QWORD PTR [esp]
   fsubp   st(2), st
   jmp     .L5



As you can see, each new version made it even worse than it was. GCC 7 is
especially bad, it even refuses to do "fsubr DWORD PTR [eax+4]" directly, and
opts for a spill and a load. Furthermore, it loads a constant twice for no
reason, even when I explicitly declared the constant literal as "long double".
Am I missing something obvious?

Can this behavior be reverted? Version 5 obviously produces much better x87
code. Please note that I don't know anything about GCC's internals, but IMO
code gen of version 5 is "quite good" and should be kept instead of breaking it
like now...

Alternatively, I'd love to hear if this is a new behavior consequence of a
command line setting that is now enabled by default on optimizing, so I can
unset it myself. Am I missing some command line setting here?

Let me know if you need more info so I can see what to provide.

Reply via email to