Fix wrong result for 1.0/3.0 at -O2 -fno-omit-frame-pointer -frounding-math
commit074226d5aa86cd3de517014acfe34c7f69a2ccc7
authorEric Botcazou <ebotcazou@adacore.com>
Mon, 1 Mar 2021 06:53:05 +0000 (1 07:53 +0100)
committerEric Botcazou <ebotcazou@adacore.com>
Mon, 1 Mar 2021 06:58:50 +0000 (1 07:58 +0100)
treebb9dd53e67dcb47d7a5adabf2db43feec428734f
parent2c83c3fbd2baaadb4091eddfc77398e43e6134ea
Fix wrong result for 1.0/3.0 at -O2 -fno-omit-frame-pointer -frounding-math

This wrong-code PR for the C++ compiler on x86-64/Windows is a regression
in GCC 9 and later, but the underlying issue has probably been there since
SEH was implemented and is exposed by this comment in config/i386/winnt.c:

  /* SEH records saves relative to the "current" stack pointer, whether
     or not there's a frame pointer in place.  This tracks the current
     stack pointer offset from the CFA.  */
  HOST_WIDE_INT sp_offset;

That's not what the (current) Microsoft documentation says; instead it says:

  /* SEH records offsets relative to the lowest address of the fixed stack
     allocation.  If there is no frame pointer, these offsets are from the
     stack pointer; if there is a frame pointer, these offsets are from the
     value of the stack pointer when the frame pointer was established, i.e.
     the frame pointer minus the offset in the .seh_setframe directive.  */

That's why the implementation is correct only under the condition that the
frame pointer be established *after* the fixed stack allocation; as a matter
of fact, that's clearly the model underpinning SEH, but is the opposite of
what is done e.g. on Linux.

However the issue is mostly papered over in practice because:

  1. SEH forces use_fast_prologue_epilogue to false, which in turns forces
save_regs_using_mov to false, so the general regs are always pushed when
they need to be saved, which eliminates the offset computation for them.

  2. As soon as a frame is larger than 240 bytes, the frame pointer is fixed
arbitrarily to 128 bytes above the stack pointer, which of course requires
that it be established after the fixed stack allocation.

So you need a small frame clobbering one of the call-saved XMM registers in
order to generate wrong SEH unwind info.

The attached fix makes sure that the frame pointer is always established
after the fixed stack allocation by pointing it at or below the lowest used
register save area, i.e. the SSE save area, and removing the special early
saves in the prologue; the end result is a uniform prologue sequence for
SEH whatever the frame size.  And it avoids a discrepancy between cases
where the number of saved general regs is even and cases where it is odd.

gcc/
PR target/99234
* config/i386/i386.c (ix86_compute_frame_layout): For a SEH target,
point the hard frame pointer to the SSE register save area instead
of the general register save area.  Perform only minimal adjustment
for small frames if it is initially not correctly aligned.
(ix86_expand_prologue): Remove early saves for a SEH target.
* config/i386/winnt.c (struct seh_frame_state): Document constraint.
gcc/testsuite/
* g++.dg/eh/seh-xmm-unwind.C: New test.
gcc/config/i386/i386.c
gcc/config/i386/winnt.c
gcc/testsuite/g++.dg/eh/seh-xmm-unwind.C [new file with mode: 0644]