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);
}