[llvm] [DAGCombiner] Don't fold GET_FPENV_MEM into a store whose address depends on it (PR #228839)

Doha Nam via llvm-commits llvm-commits at lists.llvm.org
Sun Oct 4 00:00:03 PDT 2026


https://github.com/waroad created https://github.com/llvm/llvm-project/pull/228839

visitGET_FPENV_MEM folds `get_fpenv_mem tmp; load tmp; store -> p` into
`get_fpenv_mem p`, and the new node replaces the original one. When the
store address depends on the original node, the DAG becomes cyclic. This
happens for `ret i256 @llvm.get.fpenv.i256()`: the hidden sret pointer is
read with a CopyFromReg that is chained after the load from the stack
temporary. Type legalization then stops with "Operand not processed?", and
release builds crash.

Skip the fold when the store address has the original node as a
predecessor.

Fixes #228828.

Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>


>From 7eeb72041af4457b5dba6567cee98ab3331f0004 Mon Sep 17 00:00:00 2001
From: waroad <dohanam2 at illinois.edu>
Date: Sun, 4 Oct 2026 01:40:51 -0500
Subject: [PATCH] [DAGCombiner] Don't fold GET_FPENV_MEM into a store whose
 address depends on it

visitGET_FPENV_MEM folds `get_fpenv_mem tmp; load tmp; store -> p` into
`get_fpenv_mem p`, and the new node replaces the original one. When the
store address depends on the original node, the DAG becomes cyclic. This
happens for `ret i256 @llvm.get.fpenv.i256()`: the hidden sret pointer is
read with a CopyFromReg that is chained after the load from the stack
temporary. Type legalization then stops with "Operand not processed?", and
release builds crash.

Skip the fold when the store address has the original node as a
predecessor.

Fixes #228828.

Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp |   6 ++
 llvm/test/CodeGen/X86/fpenv.ll                | 100 ++++++++++++++++++
 2 files changed, 106 insertions(+)

diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index c8a984a9dc56e..a1305a4f9b8e8 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -30579,6 +30579,12 @@ SDValue DAGCombiner::visitGET_FPENV_MEM(SDNode *N) {
       !StNode->getChain().reachesChainWithoutSideEffects(SDValue(LdNode, 1)))
     return SDValue();
 
+  // The new node replaces N, so the store address must not depend on N (for
+  // example through a CopyFromReg chained after the load), or the DAG would
+  // become cyclic.
+  if (StNode->getBasePtr()->hasPredecessor(N))
+    return SDValue();
+
   // Create new node GET_FPENV_MEM, which uses the store address to write FP
   // environment.
   SDValue Res = DAG.getGetFPEnv(Chain, SDLoc(N), StNode->getBasePtr(), MemVT,
diff --git a/llvm/test/CodeGen/X86/fpenv.ll b/llvm/test/CodeGen/X86/fpenv.ll
index 77eaaa1ca08db..ca81c27ccb809 100644
--- a/llvm/test/CodeGen/X86/fpenv.ll
+++ b/llvm/test/CodeGen/X86/fpenv.ll
@@ -74,6 +74,106 @@ entry:
   ret void
 }
 
+; The i256 result is returned through a hidden sret pointer that is copied out
+; of a virtual register after the load from the stack temporary. Folding the
+; store into GET_FPENV_MEM must not create a cycle.
+; https://github.com/llvm/llvm-project/issues/228828
+define i256 @get_fpenv_ret() nounwind {
+; X86-NOSSE-LABEL: get_fpenv_ret:
+; X86-NOSSE:       # %bb.0: # %entry
+; X86-NOSSE-NEXT:    pushl %ebp
+; X86-NOSSE-NEXT:    pushl %ebx
+; X86-NOSSE-NEXT:    pushl %edi
+; X86-NOSSE-NEXT:    pushl %esi
+; X86-NOSSE-NEXT:    subl $60, %esp
+; X86-NOSSE-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-NOSSE-NEXT:    fnstenv {{[0-9]+}}(%esp)
+; X86-NOSSE-NEXT:    fldenv {{[0-9]+}}(%esp)
+; X86-NOSSE-NEXT:    movl {{[0-9]+}}(%esp), %ecx
+; X86-NOSSE-NEXT:    movl %ecx, {{[-0-9]+}}(%e{{[sb]}}p) # 4-byte Spill
+; X86-NOSSE-NEXT:    movl {{[0-9]+}}(%esp), %ecx
+; X86-NOSSE-NEXT:    movl %ecx, {{[-0-9]+}}(%e{{[sb]}}p) # 4-byte Spill
+; X86-NOSSE-NEXT:    movl {{[0-9]+}}(%esp), %esi
+; X86-NOSSE-NEXT:    movl {{[0-9]+}}(%esp), %edi
+; X86-NOSSE-NEXT:    movl {{[0-9]+}}(%esp), %ebx
+; X86-NOSSE-NEXT:    movl {{[0-9]+}}(%esp), %ebp
+; X86-NOSSE-NEXT:    movl {{[0-9]+}}(%esp), %ecx
+; X86-NOSSE-NEXT:    movl {{[0-9]+}}(%esp), %edx
+; X86-NOSSE-NEXT:    movl %edx, 24(%eax)
+; X86-NOSSE-NEXT:    movl %ecx, 28(%eax)
+; X86-NOSSE-NEXT:    movl %ebp, 16(%eax)
+; X86-NOSSE-NEXT:    movl %ebx, 20(%eax)
+; X86-NOSSE-NEXT:    movl %edi, 8(%eax)
+; X86-NOSSE-NEXT:    movl %esi, 12(%eax)
+; X86-NOSSE-NEXT:    movl {{[-0-9]+}}(%e{{[sb]}}p), %ecx # 4-byte Reload
+; X86-NOSSE-NEXT:    movl %ecx, (%eax)
+; X86-NOSSE-NEXT:    movl {{[-0-9]+}}(%e{{[sb]}}p), %ecx # 4-byte Reload
+; X86-NOSSE-NEXT:    movl %ecx, 4(%eax)
+; X86-NOSSE-NEXT:    addl $60, %esp
+; X86-NOSSE-NEXT:    popl %esi
+; X86-NOSSE-NEXT:    popl %edi
+; X86-NOSSE-NEXT:    popl %ebx
+; X86-NOSSE-NEXT:    popl %ebp
+; X86-NOSSE-NEXT:    retl $4
+;
+; X86-SSE-LABEL: get_fpenv_ret:
+; X86-SSE:       # %bb.0: # %entry
+; X86-SSE-NEXT:    pushl %ebp
+; X86-SSE-NEXT:    pushl %ebx
+; X86-SSE-NEXT:    pushl %edi
+; X86-SSE-NEXT:    pushl %esi
+; X86-SSE-NEXT:    subl $60, %esp
+; X86-SSE-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-SSE-NEXT:    fnstenv {{[0-9]+}}(%esp)
+; X86-SSE-NEXT:    fldenv {{[0-9]+}}(%esp)
+; X86-SSE-NEXT:    stmxcsr {{[0-9]+}}(%esp)
+; X86-SSE-NEXT:    movl {{[0-9]+}}(%esp), %ecx
+; X86-SSE-NEXT:    movl %ecx, {{[-0-9]+}}(%e{{[sb]}}p) # 4-byte Spill
+; X86-SSE-NEXT:    movl {{[0-9]+}}(%esp), %ecx
+; X86-SSE-NEXT:    movl %ecx, {{[-0-9]+}}(%e{{[sb]}}p) # 4-byte Spill
+; X86-SSE-NEXT:    movl {{[0-9]+}}(%esp), %esi
+; X86-SSE-NEXT:    movl {{[0-9]+}}(%esp), %edi
+; X86-SSE-NEXT:    movl {{[0-9]+}}(%esp), %ebx
+; X86-SSE-NEXT:    movl {{[0-9]+}}(%esp), %ebp
+; X86-SSE-NEXT:    movl {{[0-9]+}}(%esp), %ecx
+; X86-SSE-NEXT:    movl {{[0-9]+}}(%esp), %edx
+; X86-SSE-NEXT:    movl %edx, 24(%eax)
+; X86-SSE-NEXT:    movl %ecx, 28(%eax)
+; X86-SSE-NEXT:    movl %ebp, 16(%eax)
+; X86-SSE-NEXT:    movl %ebx, 20(%eax)
+; X86-SSE-NEXT:    movl %edi, 8(%eax)
+; X86-SSE-NEXT:    movl %esi, 12(%eax)
+; X86-SSE-NEXT:    movl {{[-0-9]+}}(%e{{[sb]}}p), %ecx # 4-byte Reload
+; X86-SSE-NEXT:    movl %ecx, (%eax)
+; X86-SSE-NEXT:    movl {{[-0-9]+}}(%e{{[sb]}}p), %ecx # 4-byte Reload
+; X86-SSE-NEXT:    movl %ecx, 4(%eax)
+; X86-SSE-NEXT:    addl $60, %esp
+; X86-SSE-NEXT:    popl %esi
+; X86-SSE-NEXT:    popl %edi
+; X86-SSE-NEXT:    popl %ebx
+; X86-SSE-NEXT:    popl %ebp
+; X86-SSE-NEXT:    retl $4
+;
+; X64-LABEL: get_fpenv_ret:
+; X64:       # %bb.0: # %entry
+; X64-NEXT:    movq %rdi, %rax
+; X64-NEXT:    fnstenv -{{[0-9]+}}(%rsp)
+; X64-NEXT:    fldenv -{{[0-9]+}}(%rsp)
+; X64-NEXT:    stmxcsr -{{[0-9]+}}(%rsp)
+; X64-NEXT:    movq -{{[0-9]+}}(%rsp), %rcx
+; X64-NEXT:    movq -{{[0-9]+}}(%rsp), %rdx
+; X64-NEXT:    movq -{{[0-9]+}}(%rsp), %rsi
+; X64-NEXT:    movq -{{[0-9]+}}(%rsp), %rdi
+; X64-NEXT:    movq %rdi, 16(%rax)
+; X64-NEXT:    movq %rsi, 24(%rax)
+; X64-NEXT:    movq %rcx, (%rax)
+; X64-NEXT:    movq %rdx, 8(%rax)
+; X64-NEXT:    retq
+entry:
+  %env = call i256 @llvm.get.fpenv.i256()
+  ret i256 %env
+}
+
 define void @set_fpenv_01(ptr %ptr) #0 {
 ; X86-NOSSE-LABEL: set_fpenv_01:
 ; X86-NOSSE:       # %bb.0: # %entry



More information about the llvm-commits mailing list