Skip to content

[InstCombine] Reuse existing freeze when pushing freeze through a binop#202306

Open
yuyzhang512 wants to merge 1 commit into
llvm:mainfrom
yuyzhang512:instcombine-freeze-binop-reuse
Open

[InstCombine] Reuse existing freeze when pushing freeze through a binop#202306
yuyzhang512 wants to merge 1 commit into
llvm:mainfrom
yuyzhang512:instcombine-freeze-binop-reuse

Conversation

@yuyzhang512

@yuyzhang512 yuyzhang512 commented Jun 8, 2026

Copy link
Copy Markdown

pushFreezeToPreventPoisonFromPropagating bailed out of freeze(binop a b) when both operands were maybe-poison. freezeOtherUses would then migrate one operand to an existing freeze on a later iteration, and only then would this transform fire — a two-step convergence that tripped the InstCombine fixpoint verifier on e.g. AMDGPU MoE GEMM kernels that load a tile-stride scalar as <1 x i32>, extract, multiply, and use the result as a divisor.

Minimal repro:

define i1 @f(i32 %a, i32 %b) {
  %m  = mul   i32 %a,  %b
  %fm = freeze i32 %m
  %fb = freeze i32 %b
  %p  = mul   i32 %fm, %fb
  %r  = icmp eq i32 %p, 0
  ret i1 %r
}
opt --passes=instcombine f.ll
> LLVM ERROR: Instruction Combining on f did not reach a fixpoint after 1 iterations.

Extend the two-operand path: when we hit a second distinct maybe-poison operand, look for an existing FreezeInst on either operand and substitute it (hoisting past its operand's def to dominate when needed — mirrors freezeOtherUses' move logic). If found, fall through to the original single-maybe-poison-operand path. No extra freezes when no existing one is available — preserves the original conservative bail.

Correctness: the underlying "push freeze into a non-poison-creating one-use binop" transform is already in mainline. Substituting an operand with freeze operand is a refinement (narrows the value set when the operand is poison, no-op otherwise). Composition of refinements is a refinement.

Regression test added in freeze.ll (@reuse_existing_freeze_binop). Full Transforms/InstCombine/ lit suite passes (1809 tests).

pushFreezeToPreventPoisonFromPropagating bailed out of `freeze(binop a b)`
when both operands were maybe-poison. freezeOtherUses would then migrate
one operand to an existing freeze on a later iteration, and only then
would this transform fire -- a two-step convergence that tripped the
InstCombine fixpoint verifier.

Extend the two-operand path: look for an existing FreezeInst on either
operand, hoisting it past its operand's def to dominate when needed
(mirroring freezeOtherUses' move logic). If found, substitute and fall
through to the original single-maybe-poison-operand path. No extra
freezes when no existing one is available -- preserves the original bail.
@yuyzhang512 yuyzhang512 requested a review from nikic as a code owner June 8, 2026 10:05
@yuyzhang512

Copy link
Copy Markdown
Author

Alive2 proof: https://alive2.llvm.org/ce/z/YjzGSK

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

Hello @yuyzhang512 👋

Thank you for submitting a Pull Request (PR) to the LLVM Project. Since this is your first PR, here are a few useful links covering our main contribution policies and review practices.

  • All contributions to LLVM must follow our LLVM AI Tool Use Policy. In particular, if you used AI while working on this PR, remember to add a note to the PR description.
  • The LLVM Code-Review Policy and Practices document contains practical information about the PR process, including how patches are reviewed and accepted, and who can review a PR.
  • Our LLVM Developer Policy describes our expectations for code quality, commit summaries and contains notes on our CI system.
  • The InstCombine Contributor Guide lays out a series of rules that contributions to InstCombine (and other middle-end components) should follow.

Please reply to this message to confirm that you have read these policies, especially the LLVM AI Tool Use Policy, and that any AI tool usage has been noted in the PR description.


Frequently asked questions

How do I add reviewers?

This PR will be automatically labeled, and the relevant teams will be notified. For some parts of the project, reviewers may also be added automatically.

You can also add reviewers manually using the Reviewers section on this page. If you cannot use that section, it is probably because you do not have write permissions for the repository. In that case, you can request a review by tagging reviewers in a comment using @ followed by their GitHub username.

What if there are no comments?

If you have not received any comments on your PR after a week, you can request a review by pinging the PR with a comment such as “Ping”. The common courtesy ping rate is once a week. Please remember that you are asking for volunteer time from other developers.

Are any special GitHub settings required to contribute to LLVM?

We only require contributors to have a public email address associated with their GitHub commits, see this section of LLVM Developer Policy for details.


If you have questions, feel free to leave a comment on this PR, or ask on LLVM Discord or LLVM Discourse.

Thank you,
The LLVM Community

@llvmorg-github-actions llvmorg-github-actions Bot added llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms labels Jun 8, 2026
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-llvm-transforms

Author: Yuyang Zhang (yuyzhang512)

Changes

pushFreezeToPreventPoisonFromPropagating bailed out of freeze(binop a b) when both operands were maybe-poison. freezeOtherUses would then migrate one operand to an existing freeze on a later iteration, and only then would this transform fire -- a two-step convergence that tripped the InstCombine fixpoint verifier.

Extend the two-operand path: look for an existing FreezeInst on either operand, hoisting it past its operand's def to dominate when needed (mirroring freezeOtherUses' move logic). If found, substitute and fall through to the original single-maybe-poison-operand path. No extra freezes when no existing one is available -- preserves the original bail.


Full diff: https://github.com/llvm/llvm-project/pull/202306.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/InstCombine/InstructionCombining.cpp (+55-4)
  • (modified) llvm/test/Transforms/InstCombine/freeze.ll (+25)
diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index 89bf4bf713421..976262849d5ff 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -5257,16 +5257,67 @@ InstCombinerImpl::pushFreezeToPreventPoisonFromPropagating(FreezeInst &OrigFI) {
   // If operand is guaranteed not to be poison, there is no need to add freeze
   // to the operand. So we first find the operand that is not guaranteed to be
   // poison.
+  //
+  // Helper: when more than one operand is maybe-poison, look for an existing
+  // freeze of that operand anywhere in the function and substitute it,
+  // hoisting it to dominate `OrigOpInst` if needed.  This avoids a fixpoint
+  // failure where `freezeOtherUses` would migrate the alias-replacement on a
+  // later iteration -- and only THEN would this transform become applicable
+  // (a single maybe-poison operand remaining).  Doing the substitution here
+  // lets the rewrite converge in a single InstCombine iteration without
+  // creating extra freezes.
+  auto reuseExistingFreezeFor = [&](Value *V) -> Value * {
+    for (User *U : V->users()) {
+      auto *FI = dyn_cast<FreezeInst>(U);
+      if (!FI || FI == &OrigFI)
+        continue;
+      if (DT.dominates(FI, OrigOpInst))
+        return FI;
+      // Hoist the freeze to immediately after its operand's def so it
+      // dominates `OrigOpInst`.  Mirror `freezeOtherUses`' move logic.
+      BasicBlock::iterator MoveBefore;
+      if (isa<Argument>(V)) {
+        MoveBefore = OrigOpInst->getFunction()
+                         ->getEntryBlock()
+                         .getFirstNonPHIOrDbgOrAlloca();
+      } else {
+        auto Opt = cast<Instruction>(V)->getInsertionPointAfterDef();
+        if (!Opt)
+          continue;
+        MoveBefore = *Opt;
+      }
+      MoveBefore.setHeadBit(false);
+      if (FI != &*MoveBefore)
+        FI->moveBefore(*MoveBefore->getParent(), MoveBefore);
+      if (DT.dominates(FI, OrigOpInst))
+        return FI;
+    }
+    return nullptr;
+  };
+
   Value *MaybePoisonOperand = nullptr;
-  for (Value *V : OrigOpInst->operands()) {
+  for (Use &U : OrigOpInst->operands()) {
+    Value *V = U.get();
     if (isa<MetadataAsValue>(V) || isGuaranteedNotToBeUndefOrPoison(V) ||
         // Treat identical operands as a single operand.
         (MaybePoisonOperand && MaybePoisonOperand == V))
       continue;
-    if (!MaybePoisonOperand)
+    if (!MaybePoisonOperand) {
       MaybePoisonOperand = V;
-    else
-      return nullptr;
+      continue;
+    }
+    // Two distinct maybe-poison operands.  Try to reuse an existing freeze
+    // on one of them to collapse back to a single maybe-poison operand.
+    if (Value *Existing = reuseExistingFreezeFor(V)) {
+      U.set(Existing);
+      continue;
+    }
+    if (Value *Existing = reuseExistingFreezeFor(MaybePoisonOperand)) {
+      OrigOpInst->replaceUsesOfWith(MaybePoisonOperand, Existing);
+      MaybePoisonOperand = V;
+      continue;
+    }
+    return nullptr;
   }
 
   OrigOpInst->dropPoisonGeneratingAnnotations();
diff --git a/llvm/test/Transforms/InstCombine/freeze.ll b/llvm/test/Transforms/InstCombine/freeze.ll
index 3a401acb6d3ee..1c9f94d7ff735 100644
--- a/llvm/test/Transforms/InstCombine/freeze.ll
+++ b/llvm/test/Transforms/InstCombine/freeze.ll
@@ -1733,6 +1733,31 @@ define float @freeze_fabs_nofpclass(float %a) {
   ret float %x.fr
 }
 
+; A single-use binop has two maybe-poison operands, but one of them
+; (%b) already has a freeze in the function (%fb).  The freeze-into-binop
+; transform reuses %fb (hoisting it past %b's def in the entry block so
+; it dominates %m), substitutes it in %m, and falls through to the
+; single-maybe-poison-operand path which freezes %a and removes %fm.
+; This must converge in one InstCombine iteration (regression test for
+; the freeze-binop fixpoint failure).
+define i1 @reuse_existing_freeze_binop(i32 %a, i32 %b) {
+; CHECK-LABEL: define i1 @reuse_existing_freeze_binop(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[FB:%.*]] = freeze i32 [[B]]
+; CHECK-NEXT:    [[A_FR:%.*]] = freeze i32 [[A]]
+; CHECK-NEXT:    [[M:%.*]] = mul i32 [[A_FR]], [[FB]]
+; CHECK-NEXT:    [[P:%.*]] = mul i32 [[M]], [[FB]]
+; CHECK-NEXT:    [[R:%.*]] = icmp eq i32 [[P]], 0
+; CHECK-NEXT:    ret i1 [[R]]
+;
+  %m  = mul i32 %a, %b
+  %fm = freeze i32 %m
+  %fb = freeze i32 %b
+  %p  = mul i32 %fm, %fb
+  %r  = icmp eq i32 %p, 0
+  ret i1 %r
+}
+
 !0 = !{}
 !1 = !{i64 4}
 !2 = !{i32 0, i32 100}

@dtcxzyw

dtcxzyw commented Jun 8, 2026

Copy link
Copy Markdown
Member

The following correctness issue was found by llvm-hackme.

This comment is generated by an automated correctness checking service designed to help identify critical correctness bugs (opt crashes or Alive2 miscompilations) and improve PR review efficiency under limited reviewer bandwidth.

Reproducer

Kind: crash

IR Reproducer:

; RUN: opt -passes=instcombine<no-verify-fixpoint> -S
define {i32} @test(i32 %a) {
  %iv = insertvalue {i32} undef, i32 %a, 0
  %fr = freeze {i32} %iv
  ret {i32} %fr
}

Stacktrace:

opt: ../llvm-project-pr/llvm/include/llvm/IR/Value.h:395: llvm::Value::user_iterator llvm::Value::materialized_user_begin(): Assertion `hasUseList()' failed.
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace and instructions to reproduce the bug.
Stack dump:
0.	Program arguments: /llvm-hackme/work/llvm-hackme/llvm-build-pr/bin/opt -S -o /dev/null /tmp/tmp4o3i5cl8.ll -passes=instcombine<no-verify-fixpoint>
1.	Running pass "function(instcombine<max-iterations=1;no-verify-fixpoint>)" on module "/tmp/tmp4o3i5cl8.ll"
2.	Running pass "instcombine<max-iterations=1;no-verify-fixpoint>" on function "test"
 #0 0x00007ffff7e1a2d2 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/lib/Support/Unix/Signals.inc:885:3
 #1 0x00007ffff7e16d9c llvm::sys::RunSignalHandlers() /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/lib/Support/Signals.cpp:108:20
 #2 0x00007ffff7e16fa1 SignalHandler(int, siginfo_t*, void*) /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/lib/Support/Unix/Signals.inc:448:14
 #3 0x00007ffff7a19520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
 #4 0x00007ffff7a6d9fc pthread_kill (/lib/x86_64-linux-gnu/libc.so.6+0x969fc)
 #5 0x00007ffff7a19476 gsignal (/lib/x86_64-linux-gnu/libc.so.6+0x42476)
 #6 0x00007ffff79ff7f3 abort (/lib/x86_64-linux-gnu/libc.so.6+0x287f3)
 #7 0x00007ffff79ff71b (/lib/x86_64-linux-gnu/libc.so.6+0x2871b)
 #8 0x00007ffff7a10e96 (/lib/x86_64-linux-gnu/libc.so.6+0x39e96)
 #9 0x00007fffef2340b5 (/llvm-hackme/work/llvm-hackme/llvm-build-pr/bin/../lib/../lib/libLLVMInstCombine.so.23.0git+0x340b5)
#10 0x00007fffef238b29 llvm::InstCombinerImpl::pushFreezeToPreventPoisonFromPropagating(llvm::FreezeInst&)::'lambda'(llvm::Value*)::operator()(llvm::Value*) const /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp:5296:3
#11 0x00007fffef240edd llvm::InstCombinerImpl::pushFreezeToPreventPoisonFromPropagating(llvm::FreezeInst&) /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp:5315:5
#12 0x00007fffef2660f2 llvm::InstCombinerImpl::visitFreeze(llvm::FreezeInst&) /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp:5481:59
#13 0x00007fffef266d52 llvm::InstCombinerImpl::run() /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp:5980:36
#14 0x00007fffef267d72 combineInstructionsOverFunction(llvm::Function&, llvm::InstructionWorklist&, llvm::AAResults*, llvm::AssumptionCache&, llvm::TargetLibraryInfo&, llvm::TargetTransformInfo&, llvm::DominatorTree&, llvm::OptimizationRemarkEmitter&, llvm::BlockFrequencyInfo*, llvm::BranchProbabilityInfo*, llvm::ProfileSummaryInfo*, llvm::InstCombineOptions const&) /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp:6284:5
#15 0x00007fffef268cbc llvm::InstCombinePass::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp:6345:3
#16 0x00007ffff14fcb46 llvm::detail::PassModel<llvm::Function, llvm::InstCombinePass, llvm::AnalysisManager<llvm::Function> >::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/include/llvm/IR/PassManagerInternal.h:94:3
#17 0x00007fffee0fe5ed llvm::PassManager<llvm::Function, llvm::AnalysisManager<llvm::Function> >::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/include/llvm/IR/PassManagerImpl.h:80:18
#18 0x00007ffff58c8446 llvm::detail::PassModel<llvm::Function, llvm::PassManager<llvm::Function, llvm::AnalysisManager<llvm::Function> >, llvm::AnalysisManager<llvm::Function> >::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/include/llvm/IR/PassManagerInternal.h:94:3
#19 0x00007fffee0fd091 llvm::ModuleToFunctionPassAdaptor::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/lib/IR/PassManager.cpp:132:41
#20 0x00007ffff7f85556 llvm::detail::PassModel<llvm::Module, llvm::ModuleToFunctionPassAdaptor, llvm::AnalysisManager<llvm::Module> >::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/include/llvm/IR/PassManagerInternal.h:94:3
#21 0x00007fffee0fd6b7 llvm::PassManager<llvm::Module, llvm::AnalysisManager<llvm::Module> >::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/include/llvm/IR/PassManagerImpl.h:80:18
#22 0x00007ffff7f9289f llvm::SmallPtrSetImplBase::~SmallPtrSetImplBase() /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/include/llvm/ADT/SmallPtrSet.h:89:5
#23 0x00007ffff7f9289f llvm::SmallPtrSetImpl<llvm::AnalysisKey*>::~SmallPtrSetImpl() /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/include/llvm/ADT/SmallPtrSet.h:366:35
#24 0x00007ffff7f9289f llvm::SmallPtrSet<llvm::AnalysisKey*, 2u>::~SmallPtrSet() /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/include/llvm/ADT/SmallPtrSet.h:533:7
#25 0x00007ffff7f9289f llvm::PreservedAnalyses::~PreservedAnalyses() /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/include/llvm/IR/Analysis.h:112:7
#26 0x00007ffff7f9289f llvm::runPassPipeline(llvm::StringRef, llvm::Module&, llvm::TargetMachine*, llvm::TargetLibraryInfoImpl*, llvm::ToolOutputFile*, llvm::ToolOutputFile*, llvm::ToolOutputFile*, llvm::StringRef, llvm::ArrayRef<llvm::PassPlugin>, llvm::ArrayRef<std::function<void (llvm::PassBuilder&)> >, llvm::opt_tool::OutputKind, llvm::opt_tool::VerifierKind, bool, bool, bool, bool, bool, bool, bool, bool) /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/tools/opt/NewPMDriver.cpp:574:10
#27 0x00007ffff7f9f414 optMain /llvm-hackme/work/llvm-hackme/llvm-build-pr/../llvm-project-pr/llvm/tools/opt/optdriver.cpp:798:25
#28 0x00007ffff7a00d90 (/lib/x86_64-linux-gnu/libc.so.6+0x29d90)
#29 0x00007ffff7a00e40 __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e40)
#30 0x0000555555555095 _start (/llvm-hackme/work/llvm-hackme/llvm-build-pr/bin/opt+0x1095)

Baseline Revision: 60dd90f3047bc1e0d727696b802da8fabd349dfc
PR Head SHA: 4a366d6d7fc8ce579a4fe435af01103d036b66a4
Patch SHA256: 04296efba25b7f98993eed5da77884a145b5008b4bc1505bdd5d91877b8898d4

@dtcxzyw dtcxzyw left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A correctness issue was identified by llvm-hackme. Please see the issue comment for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants