https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117184
Matt Turner <mattst88 at gmail dot com> changed:
What |Removed |Added
----------------------------------------------------------------------------
CC| |mattst88 at gmail dot com
--- Comment #4 from Matt Turner <mattst88 at gmail dot com> ---
Reproduces on trunk (17.0.0 20260715). Root cause found; it is a middle-end LRA
bug and not specific to EH or to Modula-2. This is not an EH bug.
The terminate called after throwing an instance of 'unsigned int' is a red
herring. What actually happens is a plain NULL pointer dereference:
INSTR-SIG: signum=11 si_addr=(nil) si_code=1 PC=0x1200301c8
INSTR-RAISE: number=3 file=../../gcc/m2/gm2-libs/RTExceptions.mod line=650
col=9
function=invalidloc message=invalid address referenced
libgm2 installs SIGSEGV/SIGBUS handlers (sigbusDespatcher,
m2/pge-boot/GSysExceptions.cc:127), which turn the fault into an
invalidLocation Modula-2 exception. Nothing handles it, so it propagates out as
the thrown unsigned int. Chasing the exception leads nowhere; the miscompile is
several frames earlier.
ira_setup_eliminable_regset (ira.cc:2493) calls df_set_regs_ever_live for the
hard frame pointer when it decides frame_pointer_needed:
if (frame_pointer_needed)
for (i = 0; i < fp_reg_count; i++)
df_set_regs_ever_live (HARD_FRAME_POINTER_REGNUM + i, true);
LRA can make that decision later instead, in setup_can_eliminate
(lra-eliminations.cc:145) when mark_not_eliminable finds the frame pointer to
stack pointer elimination is no longer possible, and it does not mark the
register live there.
alpha builds its prologue save mask from df_regs_ever_live_p
(alpha_compute_frame_layout), so $15 is left out of sa_mask.
alpha_expand_prologue still emits the mov $30,$15 that clobbers it. Worse,
alpha_expand_epilogue restores the register whenever frame_pointer_needed,
using an fp_offset that is only ever assigned inside the save-mask loop, so it
keeps its fp_offset = 0 initializer -- offset 0 is the return address slot.
In DescribeElement (m2/pge-boot/Gpge.cc):
reload LRA
------------------------------ ------------------------------
lda sp,-64(sp) lda sp,-64(sp)
stq fp,24(sp) <- saved stq ra,0(sp)
mov sp,fp mov sp,fp <- clobbers $15, never saved
... ...
ldq fp,24(sp) <- restored ldq fp,0(sp) <- reads the RA slot
prologue saves: ra@0 s0@8 s1@16 s2@24 s3@32 (no fp)
epilogue restores: ra@0 s0@8 s1@16 s2@24 s3@32 + fp@0
$15 is call-saved, so the caller gets its value back as a code address. In pge
the caller is SearchAndDo (m2/pge-boot/GSymbolKey.cc:235), which keeps the tree
node t in $15; the next ldq s4,16(fp) faults.
Do what IRA does, so both paths agree:
```
--- a/gcc/lra-eliminations.cc
+++ b/gcc/lra-eliminations.cc
@@ -147,7 +147,17 @@ setup_can_eliminate (class lra_elim_table *ep, bool value)
ep->can_eliminate = ep->prev_can_eliminate = value;
if (! value
&& ep->from == FRAME_POINTER_REGNUM && ep->to == STACK_POINTER_REGNUM)
- frame_pointer_needed = 1;
+ {
+ frame_pointer_needed = 1;
+ /* ira_setup_eliminable_regset marks the hard frame pointer live when
+ it decides a frame pointer is needed. When we make that decision
+ here instead, we have to do the same, otherwise a target whose
+ prologue keys the register save off df_regs_ever_live_p would
+ clobber the caller's hard frame pointer without saving it. */
+ int fp_reg_count = hard_regno_nregs (HARD_FRAME_POINTER_REGNUM, Pmode);
+ for (int i = 0; i < fp_reg_count; i++)
+ df_set_regs_ever_live (HARD_FRAME_POINTER_REGNUM + i, true);
+ }
if (!frame_pointer_needed)
REGNO_POINTER_ALIGN (HARD_FRAME_POINTER_REGNUM) = 0;
}
```
An alpha-local alternative also works -- adding HARD_FRAME_POINTER_REGNUM to
sa_mask when frame_pointer_needed in alpha_compute_frame_layout -- but the
inconsistency looks like it belongs in LRA: any target keying prologue saves
off df_regs_ever_live_p has the same exposure. CC'ing Vlad for a view on which
is right.
The VLA has to reach the caller by inlining. A function with its own VLA has
cfun->calls_alloca set, so IRA already knows a frame pointer is needed and
marks $15 live itself, and there is no bug. This is why hand-written VLA tests
do not reproduce.
```
/* { dg-do compile } */
/* { dg-options "-O2" } */
#include <string.h>
extern void use (const char *, unsigned);
extern void sink (char);
extern unsigned Indent;
static void
ind (const char *s_, unsigned n)
{
char a[n + 1];
memcpy (a, s_, n + 1);
for (unsigned i = 0; i < Indent; i++)
sink (' ');
use (a, n);
}
void
caller (unsigned name)
{
ind ("", 0);
use ("x", name);
}
/* { dg-final { scan-assembler "stq\[ \t\]+\\\$15" } } */
```
0 stq $15 before the patch, 1 after.
No native bootstrap is needed. pge is a host tool, so a normal cross build
compiles it with the host compiler and never exercises the alpha compiler --
which is why cross builds look clean. But its sources can be compiled with the
alpha cross compiler and the result run under qemu-alpha:
configure an ev56 cross (--with-cpu=ev56 --enable-languages=c,c++) with
libstdc++;
generate the two m2 headers: run gcc/m2/configure in <build>/gcc/m2 for
gm2config.aci, then grep -v "define PACKAGE_" gm2config.aci > gm2config.h; run
gcc/m2/gm2-libs/config-host in <build>/gcc/m2/gm2-libs for gm2-libs-host.h.
(Both are feature/SIZEOF_LONG checks; x86_64-linux and alpha-linux agree.)
compile all of gcc/m2/pge-boot/*.{cc,c} with the alpha xg++ at -O2 -DMC_M2,
link static against the target libstdc++;
qemu-alpha ./pge -k -l gcc/m2/gm2-compiler/P2Build.bnf -o out.mod.
A gmp.h shim is needed because gcc/system.h includes <gmp.h> unconditionally
and there is no alpha GMP in the sysroot; pge never calls GMP, so declaring
__mpz_struct/mpz_t/mpz_ptr/ mpz_srcptr plus mpz_init/mpz_clear is enough for
auto_mpz to parse.
Result, deterministic over repeated runs:
built -mlra -> terminate called after throwing an instance of 'unsigned
int', core dump,
output truncated at 20435 bytes
built -mno-lra -> clean run, complete output, 179318 bytes
Same sources, same flags, same qemu; only the allocator differs.
Testing
With the patch, a full LRA-built pge produces byte-identical output to the
reload-built one on all six .bnf grammars (P0SyntaxCheck, P1Build, P2Build,
P3Build, PCBuild, PHBuild).
x86_64-pc-linux-gnu bootstrap passes, stage2/stage3 comparison successful.
x86_64 make check-gcc check-g++, A/B against the same tree with only this
patch reverted: zero differing result lines (225686 gcc passes / 275979 g++
passes either way). So the change is a no-op there.
alpha gcc.c-torture/execute under qemu at -O0/-O2, -mlra vs -mno-lra, on
both ev56 and a baseline cross: identical results, 3380 per configuration.
A native alpha bootstrap with m2 would be a good independent confirmation -- I
only have cross + qemu.
PR117185 (baseline/non-BWX) is a separate and still-live problem; this patch
does not address it, and LRA cannot be defaulted on for alpha until that one is
fixed.