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

            Bug ID: 126382
           Summary: Incorrect compiler generated code resulting in seg
                    fault - hoisted tls() in x86_cse caused register
                    clobbering
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: rtl-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: Reshma.Roy at amd dot com
  Target Milestone: ---

Created attachment 65120
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65120&action=edit
The preprocessed input file to be used for the command mentioned in the
description

GCC version: gcc (GCC) 17.0.0 20260722 (experimental)

GCC trunk generates incorrect code for AVX stack-realigned functions. This
causes a null pointer to dereference in libFLAME's legacy test suite
during Hessenberg reduction (FLA_Obj_datatype_check.c:18).

Bisection points to 5cf1b9a03ec5b617af8c50c1e9c0d223083fd7f2 ("x86-64: Remove
redundant TLS calls").

-------------------------------------------------------------------------------
Reduced test case:

typedef struct { long n; long m_inner; long n_inner; int base; } FLA_Obj;
__thread FLA_Obj FLA_ONE, W12;
long FLA_Obj_length ();
void FLA_Obj_width ();
void f (FLA_Obj A)
{
  while (FLA_Obj_length (A))
    FLA_Obj_width (FLA_ONE);
  FLA_Obj_width (FLA_ONE, W12);
}

Reproducer: gcc -mtune=native -O3 -mavx512f -mavx512dq -mfma
-fstack-protector-strong -fpie -std=c11 -fPIC -S -o testcase.s testcase.i

--------------------------------------------------------------------------------

Generated assembly:

.LFB0:
       :
        movq    %r10, -16(%rbp)
       :
        call    __tls_get_addr@PLT
        movq    %rax, %rbx
        jmp     .L2
        .p2align 4
        .p2align 3
.L3:
        vmovdqu (%rbx), %ymm0
        subq    $32, %rsp
        xorl    %eax, %eax
        vmovdqu %ymm0, (%rsp)
        vzeroupper
        call    FLA_Obj_width@PLT
        addq    $32, %rsp
.L2:
        vmovdqu (%r10), %ymm0       --> seg fault
        subq    $32, %rsp
        xorl    %eax, %eax
        vmovdqu %ymm0, (%rsp)
         vzeroupper
        call    FLA_Obj_length@PLT
        addq    $32, %rsp
        testq   %rax, %rax
        jne     .L3

Segfault explanation

At .L2, the code loads from (%r10), but %r10 does not hold a valid TLS address
at that point.

%r10 is the DRAP register used for AVX stack realignment. It is not the
register that holds the TLS pointer. The hoisted __tls_get_addr call stores the
TLS address in %rbx, and .L3 correctly uses (%rbx). At .L2, the compiler still
emits (%r10) instead.

-------------------------------------------------------------------------------

Root cause in x86_cse

The TLS call is hoisted out of the loop into block B0.

RTL evidence — insertion point

Before the pass (*.310r.rpad), entry block BB2 starts with insn 49 copying
incoming `%r10` into a pseudo register. There is no definition of %r10 above
that insn. Its real definition is created later by the prologue-epilog pass.
Because of this, DF_LIVE_IN does not treat %r10 as live when choosing where to
place the TLS call.  

(note 3 0 49 2 [bb 2] NOTE_INSN_BASIC_BLOCK)
(insn 49 3 2 2 (set (reg:DI 111)
        (reg:DI 38 r10)) 99 {*movdi_internal}
     (expr_list:REG_DEAD (reg:DI 38 r10)
        (nil)))
(note 2 49 26 2 NOTE_INSN_FUNCTION_BEG)

After the pass (*.311r.x86_cse), the call is placed right after the BB head
note, before insn 49. The backend then reuses %r10 for a TLS load, but %r10
still holds whatever value was in it on entry (caller state or an uninitialized
DRAP slot). Dereferencing that address causes the segfault.

The guard that should push the call past live caller-saved regs uses
DF_LIVE_IN: 

bitmap in = df_live ? DF_LIVE_IN (bb) : DF_LR_IN (bb);
…
if (tls_abi.clobbers_full_reg_p (i) && !fixed_regs[i] && bitmap_bit_p (in, i))
  bitmap_set_bit (live_caller_saved_regs, i);

DF_LIVE = DF_LR ∩ Reaching Definition. %r10 (DRAP) is used at insn 49, so it is
live in the LR sense, but it has no reaching definition at this pre-prologue
stage. Its defining insn is only created later by pro_and_epilogue. So
DF_LIVE_IN does not mark it live, and the TLS call is placed too early.

--------------------------------------------------------------------------------

Possible fixes

Two approaches are listed below. Please share which you think is safer and less
likely to cause side effects.

1. Use pure liveness (DF_LR_IN), which is always available. It is a superset of
`DF_LIVE_IN`, so placement can only become more conservative, not less safe:


diff --git a/gcc/config/i386/i386-features.cc
b/gcc/config/i386/i386-features.cc
index d65b6ce7672..ff3570f422d 100644
--- a/gcc/config/i386/i386-features.cc
+++ b/gcc/config/i386/i386-features.cc
@@ -4339,7 +4339,7 @@ ix86_emit_tls_call (rtx tls_set, x86_cse_kind kind,
basic_block bb,
         after all live registers clobbered are dead.  */

       auto_bitmap live_caller_saved_regs;
-      bitmap in = df_live ? DF_LIVE_IN (bb) : DF_LR_IN (bb);
+      bitmap in = DF_LR_IN (bb);

       if (bitmap_bit_p (in, FLAGS_REG))
        bitmap_set_bit (live_caller_saved_regs, FLAGS_REG);

With this fix, the *.311r.x86_cse dump places the call after insn 49.


2. Add a special case for the DRAP register. We are not fully confident this is
the right fix since tls_abi.clobbers_full_reg_p (r10) is true but the if
condition turns out to be false since "in" does not conatin r10.

diff --git a/gcc/config/i386/i386-features.cc
b/gcc/config/i386/i386-features.cc
index d65b6ce7672..b35578957e8 100644
--- a/gcc/config/i386/i386-features.cc
+++ b/gcc/config/i386/i386-features.cc
@@ -4355,7 +4355,11 @@ ix86_emit_tls_call (rtx tls_set, x86_cse_kind kind,
basic_block bb,
          for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
            if (tls_abi.clobbers_full_reg_p (i)
                && !fixed_regs[i]
-               && bitmap_bit_p (in, i))
+               && (bitmap_bit_p (in, i)
+                   || (crtl->drap_reg
+                       && i == (unsigned) REGNO (crtl->drap_reg)
+                       && bitmap_bit_p (DF_LR_IN (bb), i)))
+
              bitmap_set_bit (live_caller_saved_regs, i);
        }

Reply via email to