Skip to content

Conversation

nikic
Copy link
Contributor

@nikic nikic commented Sep 17, 2025

This adds a new pass for dropping assumes that are unlikely to be useful for further optimization.

It works by discarding any assumes whose affected values are one-use (which implies that they are only used by the assume, i.e. ephemeral).

This pass currently runs at the start of the module optimization pipeline, that is post-inline and post-link. Before that point, it is more likely for previously "useless" assumes to become useful again, e.g. because an additional user of the value is introduced after inlining + CSE.

llvm-opt-benchmark: dtcxzyw/llvm-opt-benchmark#2829

@nikic nikic requested review from fhahn and dtcxzyw September 17, 2025 17:08
@llvmbot llvmbot added clang Clang issues not falling into any other category llvm:transforms labels Sep 17, 2025
@llvmbot
Copy link
Member

llvmbot commented Sep 17, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-llvm-transforms

Author: Nikita Popov (nikic)

Changes

This adds a new pass for dropping assumes that are unlikely to be useful for further optimization.

It works by discarding any assumes whose affected values are one-use (which implies that they are only used by the assume, i.e. ephemeral).

This pass currently runs at the start of the module optimization pipeline, that is post-inline and post-link. Before that point, it is more likely for previously "useless" assumes to become useful again, e.g. because an additional user of the value is introduced after inlining + CSE.

llvm-opt-benchmark: dtcxzyw/llvm-opt-benchmark#2829


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

16 Files Affected:

  • (modified) clang/test/CodeGen/inline-asm-x86-flag-output.c (+1-1)
  • (added) llvm/include/llvm/Transforms/Scalar/DropUnnecessaryAssumes.h (+28)
  • (modified) llvm/lib/Passes/PassBuilder.cpp (+1)
  • (modified) llvm/lib/Passes/PassBuilderPipelines.cpp (+4)
  • (modified) llvm/lib/Passes/PassRegistry.def (+1)
  • (modified) llvm/lib/Transforms/Scalar/CMakeLists.txt (+1)
  • (added) llvm/lib/Transforms/Scalar/DropUnnecessaryAssumes.cpp (+59)
  • (modified) llvm/test/Other/new-pm-defaults.ll (+1)
  • (modified) llvm/test/Other/new-pm-thinlto-postlink-defaults.ll (+1)
  • (modified) llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll (+1)
  • (modified) llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll (+1)
  • (added) llvm/test/Transforms/DropUnnecessaryAssumes/basic.ll (+96)
  • (modified) llvm/test/Transforms/PhaseOrdering/AArch64/constraint-elimination-placement.ll (+5-30)
  • (modified) llvm/test/Transforms/PhaseOrdering/pr45682.ll (-2)
  • (modified) llvm/test/Transforms/PhaseOrdering/pr45687.ll (-3)
  • (modified) llvm/test/Transforms/PhaseOrdering/pr98799-inline-simplifycfg-ub.ll (-2)
diff --git a/clang/test/CodeGen/inline-asm-x86-flag-output.c b/clang/test/CodeGen/inline-asm-x86-flag-output.c
index 243dc3716ca13..330036512e705 100644
--- a/clang/test/CodeGen/inline-asm-x86-flag-output.c
+++ b/clang/test/CodeGen/inline-asm-x86-flag-output.c
@@ -389,7 +389,7 @@ int test_assume_boolean_flag(long nr, volatile long *addr) {
       : "=@cca"(x), "=@ccae"(y), "=m"(*(volatile long *)(addr))
       : "r"(nr)
       : "cc");
-  if (x)
+  if (x && y)
     return 0;
   return 1;
 }
diff --git a/llvm/include/llvm/Transforms/Scalar/DropUnnecessaryAssumes.h b/llvm/include/llvm/Transforms/Scalar/DropUnnecessaryAssumes.h
new file mode 100644
index 0000000000000..192139cbfd94d
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Scalar/DropUnnecessaryAssumes.h
@@ -0,0 +1,28 @@
+//===------------------------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Pass that drops assumes that are unlikely to be useful.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_SCALAR_DROPUNNECESSARYASSUMES_H
+#define LLVM_TRANSFORMS_SCALAR_DROPUNNECESSARYASSUMES_H
+
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+
+class DropUnnecessaryAssumesPass
+    : public PassInfoMixin<DropUnnecessaryAssumesPass> {
+public:
+  PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+};
+
+} // end namespace llvm
+
+#endif // LLVM_TRANSFORMS_SCALAR_DROPUNNECESSARYASSUMES_H
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 8cf277657a54a..53319031b1aba 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -273,6 +273,7 @@
 #include "llvm/Transforms/Scalar/DFAJumpThreading.h"
 #include "llvm/Transforms/Scalar/DeadStoreElimination.h"
 #include "llvm/Transforms/Scalar/DivRemPairs.h"
+#include "llvm/Transforms/Scalar/DropUnnecessaryAssumes.h"
 #include "llvm/Transforms/Scalar/EarlyCSE.h"
 #include "llvm/Transforms/Scalar/FlattenCFG.h"
 #include "llvm/Transforms/Scalar/Float2Int.h"
diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp
index c3f35f0f5e7fa..3b75a5a83dd9a 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -92,6 +92,7 @@
 #include "llvm/Transforms/Scalar/DFAJumpThreading.h"
 #include "llvm/Transforms/Scalar/DeadStoreElimination.h"
 #include "llvm/Transforms/Scalar/DivRemPairs.h"
+#include "llvm/Transforms/Scalar/DropUnnecessaryAssumes.h"
 #include "llvm/Transforms/Scalar/EarlyCSE.h"
 #include "llvm/Transforms/Scalar/Float2Int.h"
 #include "llvm/Transforms/Scalar/GVN.h"
@@ -1498,6 +1499,9 @@ PassBuilder::buildModuleOptimizationPipeline(OptimizationLevel Level,
   invokeOptimizerEarlyEPCallbacks(MPM, Level, LTOPhase);
 
   FunctionPassManager OptimizePM;
+  if (!isLTOPreLink(LTOPhase))
+    OptimizePM.addPass(DropUnnecessaryAssumesPass());
+
   // Scheduling LoopVersioningLICM when inlining is over, because after that
   // we may see more accurate aliasing. Reason to run this late is that too
   // early versioning may prevent further inlining due to increase of code
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 1d015971dfbdf..924aa3eb5d492 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -425,6 +425,7 @@ FUNCTION_PASS("dot-post-dom", PostDomPrinter())
 FUNCTION_PASS("dot-post-dom-only", PostDomOnlyPrinter())
 FUNCTION_PASS("dse", DSEPass())
 FUNCTION_PASS("dwarf-eh-prepare", DwarfEHPreparePass(TM))
+FUNCTION_PASS("drop-unnecessary-assumes", DropUnnecessaryAssumesPass())
 FUNCTION_PASS("expand-large-div-rem", ExpandLargeDivRemPass(TM))
 FUNCTION_PASS("expand-memcmp", ExpandMemCmpPass(TM))
 FUNCTION_PASS("expand-reductions", ExpandReductionsPass())
diff --git a/llvm/lib/Transforms/Scalar/CMakeLists.txt b/llvm/lib/Transforms/Scalar/CMakeLists.txt
index 765059d0c3b20..37dbb34605646 100644
--- a/llvm/lib/Transforms/Scalar/CMakeLists.txt
+++ b/llvm/lib/Transforms/Scalar/CMakeLists.txt
@@ -11,6 +11,7 @@ add_llvm_component_library(LLVMScalarOpts
   DeadStoreElimination.cpp
   DFAJumpThreading.cpp
   DivRemPairs.cpp
+  DropUnnecessaryAssumes.cpp
   EarlyCSE.cpp
   FlattenCFGPass.cpp
   Float2Int.cpp
diff --git a/llvm/lib/Transforms/Scalar/DropUnnecessaryAssumes.cpp b/llvm/lib/Transforms/Scalar/DropUnnecessaryAssumes.cpp
new file mode 100644
index 0000000000000..847be47a050c9
--- /dev/null
+++ b/llvm/lib/Transforms/Scalar/DropUnnecessaryAssumes.cpp
@@ -0,0 +1,59 @@
+//===------------------------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Scalar/DropUnnecessaryAssumes.h"
+#include "llvm/Analysis/AssumptionCache.h"
+#include "llvm/Analysis/ValueTracking.h"
+#include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/PatternMatch.h"
+#include "llvm/Transforms/Utils/Local.h"
+
+using namespace llvm;
+
+PreservedAnalyses
+DropUnnecessaryAssumesPass::run(Function &F, FunctionAnalysisManager &FAM) {
+  AssumptionCache &AC = FAM.getResult<AssumptionAnalysis>(F);
+  bool Changed = false;
+
+  for (AssumptionCache::ResultElem &Elem : AC.assumptions()) {
+    auto *Assume = cast_or_null<AssumeInst>(Elem.Assume);
+    if (!Assume)
+      continue;
+
+    // TODO: Handle assumes with operand bundles.
+    if (Assume->hasOperandBundles())
+      continue;
+
+    Value *Cond = Assume->getArgOperand(0);
+    // Don't drop type tests, which have special semantics.
+    if (match(Cond, PatternMatch::m_Intrinsic<Intrinsic::type_test>()))
+      continue;
+
+    SmallPtrSet<Value *, 8> Affected;
+    findValuesAffectedByCondition(Cond, /*IsAssume=*/true,
+                                  [&](Value *A) { Affected.insert(A); });
+
+    // If all the affected uses have only one use (part of the assume), then
+    // the assume does not provide useful information. Note that additional
+    // users may appear as a result of inlining and CSE, so we should only
+    // make this assumption late in the optimization pipeline.
+    // TODO: Handle dead cyclic usages.
+    if (!all_of(Affected, [](Value *V) { return V->hasOneUse(); }))
+      continue;
+
+    Assume->eraseFromParent();
+    RecursivelyDeleteTriviallyDeadInstructions(Cond);
+  }
+
+  if (Changed) {
+    PreservedAnalyses PA;
+    PA.preserveSet<CFGAnalyses>();
+    return PA;
+  }
+  return PreservedAnalyses::all();
+}
diff --git a/llvm/test/Other/new-pm-defaults.ll b/llvm/test/Other/new-pm-defaults.ll
index c554fdbf4c799..94e860b8ce304 100644
--- a/llvm/test/Other/new-pm-defaults.ll
+++ b/llvm/test/Other/new-pm-defaults.ll
@@ -244,6 +244,7 @@
 ; CHECK-O-NEXT: Running pass: ReversePostOrderFunctionAttrsPass
 ; CHECK-O-NEXT: Running pass: RecomputeGlobalsAAPass
 ; CHECK-EP-OPTIMIZER-EARLY: Running pass: NoOpModulePass
+; CHECK-DEFAULT-NEXT: Running pass: DropUnnecessaryAssumesPass
 ; CHECK-O-NEXT: Running pass: Float2IntPass
 ; CHECK-O-NEXT: Running pass: LowerConstantIntrinsicsPass on foo
 ; CHECK-MATRIX: Running pass: LowerMatrixIntrinsicsPass on f
diff --git a/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll b/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll
index 62bb02d9b3c40..a08a140a35166 100644
--- a/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll
+++ b/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll
@@ -167,6 +167,7 @@
 ; CHECK-POSTLINK-O-NEXT: Running pass: ReversePostOrderFunctionAttrsPass
 ; CHECK-POSTLINK-O-NEXT: Running pass: RecomputeGlobalsAAPass
 ; CHECK-POST-EP-OPT-EARLY-NEXT: Running pass: NoOpModulePass
+; CHECK-POSTLINK-O-NEXT: Running pass: DropUnnecessaryAssumesPass
 ; CHECK-POSTLINK-O-NEXT: Running pass: Float2IntPass
 ; CHECK-POSTLINK-O-NEXT: Running pass: LowerConstantIntrinsicsPass
 ; CHECK-POSTLINK-O3-NEXT: Running pass: ControlHeightReductionPass
diff --git a/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll b/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll
index 0da7a9f73bdce..d9e2dd37a7985 100644
--- a/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll
+++ b/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll
@@ -150,6 +150,7 @@
 ; CHECK-O-NEXT: Running pass: EliminateAvailableExternallyPass
 ; CHECK-O-NEXT: Running pass: ReversePostOrderFunctionAttrsPass
 ; CHECK-O-NEXT: Running pass: RecomputeGlobalsAAPass
+; CHECK-O-NEXT: Running pass: DropUnnecessaryAssumesPass
 ; CHECK-O-NEXT: Running pass: Float2IntPass
 ; CHECK-O-NEXT: Running pass: LowerConstantIntrinsicsPass
 ; CHECK-O3-NEXT: Running pass: ControlHeightReductionPass
diff --git a/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll b/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll
index 38b7890682783..2f6fa4b27d354 100644
--- a/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll
+++ b/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll
@@ -159,6 +159,7 @@
 ; CHECK-O-NEXT: Running pass: EliminateAvailableExternallyPass
 ; CHECK-O-NEXT: Running pass: ReversePostOrderFunctionAttrsPass
 ; CHECK-O-NEXT: Running pass: RecomputeGlobalsAAPass
+; CHECK-O-NEXT: Running pass: DropUnnecessaryAssumesPass
 ; CHECK-O-NEXT: Running pass: Float2IntPass
 ; CHECK-O-NEXT: Running pass: LowerConstantIntrinsicsPass
 ; CHECK-O3-NEXT: Running pass: ControlHeightReductionPass
diff --git a/llvm/test/Transforms/DropUnnecessaryAssumes/basic.ll b/llvm/test/Transforms/DropUnnecessaryAssumes/basic.ll
new file mode 100644
index 0000000000000..ea0d5d3fca8ff
--- /dev/null
+++ b/llvm/test/Transforms/DropUnnecessaryAssumes/basic.ll
@@ -0,0 +1,96 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=drop-unnecessary-assumes < %s | FileCheck %s
+
+define void @basic_dead(i32 %x) {
+; CHECK-LABEL: define void @basic_dead(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    ret void
+;
+  %cond = icmp sge i32 %x, 0
+  call void @llvm.assume(i1 %cond)
+  ret void
+}
+
+define i32 @basic_live(i32 %x) {
+; CHECK-LABEL: define i32 @basic_live(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[COND:%.*]] = icmp sge i32 [[X]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[COND]])
+; CHECK-NEXT:    ret i32 [[X]]
+;
+  %cond = icmp sge i32 %x, 0
+  call void @llvm.assume(i1 %cond)
+  ret i32 %x
+}
+
+; Affected value is not direct operand of the condition.
+define i32 @complex_live(i32 %x) {
+; CHECK-LABEL: define i32 @complex_live(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[AND:%.*]] = and i32 [[X]], 1
+; CHECK-NEXT:    [[COND:%.*]] = icmp ne i32 [[AND]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[COND]])
+; CHECK-NEXT:    ret i32 [[X]]
+;
+  %and = and i32 %x, 1
+  %cond = icmp ne i32 %and, 0
+  call void @llvm.assume(i1 %cond)
+  ret i32 %x
+}
+
+; There are multiple affected values, and not all are one-use.
+define i32 @multiple_live1(i32 %x, i32 %y) {
+; CHECK-LABEL: define i32 @multiple_live1(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[COND:%.*]] = icmp eq i32 [[X]], [[Y]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[COND]])
+; CHECK-NEXT:    ret i32 [[X]]
+;
+  %cond = icmp eq i32 %x, %y
+  call void @llvm.assume(i1 %cond)
+  ret i32 %x
+}
+
+define i32 @multiple_live2(i32 %x, i32 %y) {
+; CHECK-LABEL: define i32 @multiple_live2(
+; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) {
+; CHECK-NEXT:    [[COND:%.*]] = icmp eq i32 [[X]], [[Y]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[COND]])
+; CHECK-NEXT:    ret i32 [[Y]]
+;
+  %cond = icmp eq i32 %x, %y
+  call void @llvm.assume(i1 %cond)
+  ret i32 %y
+}
+
+define void @operand_bundle_dead(ptr %x) {
+; CHECK-LABEL: define void @operand_bundle_dead(
+; CHECK-SAME: ptr [[X:%.*]]) {
+; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "align"(ptr [[X]], i64 8) ]
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.assume(i1 true) ["align"(ptr %x, i64 8)]
+  ret void
+}
+
+define ptr @operand_bundle_live(ptr %x) {
+; CHECK-LABEL: define ptr @operand_bundle_live(
+; CHECK-SAME: ptr [[X:%.*]]) {
+; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "align"(ptr [[X]], i64 8) ]
+; CHECK-NEXT:    ret ptr [[X]]
+;
+  call void @llvm.assume(i1 true) ["align"(ptr %x, i64 8)]
+  ret ptr %x
+}
+
+define void @type_test(ptr %x) {
+; CHECK-LABEL: define void @type_test(
+; CHECK-SAME: ptr [[X:%.*]]) {
+; CHECK-NEXT:    [[TEST:%.*]] = call i1 @llvm.type.test(ptr [[X]], metadata !"typeid")
+; CHECK-NEXT:    call void @llvm.assume(i1 [[TEST]])
+; CHECK-NEXT:    ret void
+;
+  %test = call i1 @llvm.type.test(ptr %x, metadata !"typeid")
+  call void @llvm.assume(i1 %test)
+  ret void
+}
diff --git a/llvm/test/Transforms/PhaseOrdering/AArch64/constraint-elimination-placement.ll b/llvm/test/Transforms/PhaseOrdering/AArch64/constraint-elimination-placement.ll
index bbdbd95c6017a..f60812f2f39be 100644
--- a/llvm/test/Transforms/PhaseOrdering/AArch64/constraint-elimination-placement.ll
+++ b/llvm/test/Transforms/PhaseOrdering/AArch64/constraint-elimination-placement.ll
@@ -98,25 +98,11 @@ define void @test2(ptr %this) #0 {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[CALL1_I_I:%.*]] = tail call i1 @test2_fn4(i8 undef)
 ; CHECK-NEXT:    [[CALL2_I_I:%.*]] = load i64, ptr inttoptr (i64 8 to ptr), align 8
-; CHECK-NEXT:    [[COND_I_I:%.*]] = select i1 [[CALL1_I_I]], i64 [[CALL2_I_I]], i64 0
-; CHECK-NEXT:    switch i64 [[COND_I_I]], label [[COMMON_RET:%.*]] [
-; CHECK-NEXT:      i64 11, label [[IF_END_I:%.*]]
-; CHECK-NEXT:      i64 13, label [[TEST2_FN2_EXIT12:%.*]]
-; CHECK-NEXT:      i64 17, label [[IF_END_I31:%.*]]
-; CHECK-NEXT:    ]
-; CHECK:       if.end.i:
-; CHECK-NEXT:    [[CALL8_I_I:%.*]] = tail call fastcc noundef i32 @test2_fn6()
-; CHECK-NEXT:    [[TRUNC_I_I:%.*]] = trunc i32 [[CALL8_I_I]] to i8
-; CHECK-NEXT:    [[CALL1_I1_I:%.*]] = tail call i1 @test2_fn4(i8 [[TRUNC_I_I]])
-; CHECK-NEXT:    [[TMP0:%.*]] = xor i1 [[CALL1_I1_I]], true
-; CHECK-NEXT:    tail call void @llvm.assume(i1 [[TMP0]])
-; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK-NEXT:    [[COND38:%.*]] = icmp eq i64 [[CALL2_I_I]], 13
+; CHECK-NEXT:    [[COND:%.*]] = select i1 [[CALL1_I_I]], i1 [[COND38]], i1 false
+; CHECK-NEXT:    br i1 [[COND]], label [[TEST2_FN2_EXIT12:%.*]], label [[COMMON_RET:%.*]]
 ; CHECK:       test2_fn2.exit12:
 ; CHECK-NEXT:    [[CALL8_I_I8:%.*]] = tail call fastcc noundef i32 @test2_fn6()
-; CHECK-NEXT:    [[TRUNC_I_I9:%.*]] = trunc i32 [[CALL8_I_I8]] to i8
-; CHECK-NEXT:    [[CALL1_I1_I10:%.*]] = tail call i1 @test2_fn4(i8 [[TRUNC_I_I9]])
-; CHECK-NEXT:    [[TMP1:%.*]] = xor i1 [[CALL1_I1_I10]], true
-; CHECK-NEXT:    tail call void @llvm.assume(i1 [[TMP1]])
 ; CHECK-NEXT:    [[CMP4_I11:%.*]] = icmp eq i32 [[CALL8_I_I8]], 0
 ; CHECK-NEXT:    br i1 [[CMP4_I11]], label [[TEST2_FN2_EXIT24:%.*]], label [[COMMON_RET]]
 ; CHECK:       common.ret:
@@ -124,13 +110,6 @@ define void @test2(ptr %this) #0 {
 ; CHECK:       test2_fn2.exit24:
 ; CHECK-NEXT:    store i8 0, ptr [[THIS]], align 4
 ; CHECK-NEXT:    br label [[COMMON_RET]]
-; CHECK:       if.end.i31:
-; CHECK-NEXT:    [[CALL8_I_I32:%.*]] = tail call fastcc noundef i32 @test2_fn6()
-; CHECK-NEXT:    [[TRUNC_I_I33:%.*]] = trunc i32 [[CALL8_I_I32]] to i8
-; CHECK-NEXT:    [[CALL1_I1_I34:%.*]] = tail call i1 @test2_fn4(i8 [[TRUNC_I_I33]])
-; CHECK-NEXT:    [[TMP2:%.*]] = xor i1 [[CALL1_I1_I34]], true
-; CHECK-NEXT:    tail call void @llvm.assume(i1 [[TMP2]])
-; CHECK-NEXT:    br label [[COMMON_RET]]
 ;
 entry:
   %call16 = call i1 @test2_fn2(ptr @.str.78)
@@ -163,10 +142,6 @@ define i1 @test2_fn2(ptr %__rhs) #0 {
 ; CHECK-NEXT:    br i1 [[CMP2_NOT]], label [[IF_END:%.*]], label [[CLEANUP:%.*]]
 ; CHECK:       if.end:
 ; CHECK-NEXT:    [[CALL8_I:%.*]] = tail call fastcc noundef i32 @test2_fn6()
-; CHECK-NEXT:    [[TRUNC_I:%.*]] = trunc i32 [[CALL8_I]] to i8
-; CHECK-NEXT:    [[CALL1_I1:%.*]] = tail call i1 @test2_fn4(i8 [[TRUNC_I]])
-; CHECK-NEXT:    [[TMP0:%.*]] = xor i1 [[CALL1_I1]], true
-; CHECK-NEXT:    tail call void @llvm.assume(i1 [[TMP0]])
 ; CHECK-NEXT:    [[CMP4:%.*]] = icmp eq i32 [[CALL8_I]], 0
 ; CHECK-NEXT:    br label [[CLEANUP]]
 ; CHECK:       cleanup:
@@ -210,7 +185,7 @@ cond.end:                                         ; preds = %cond.true, %entry
 
 define i1 @test2_fn4(i8 %bf.load) {
 ; CHECK-LABEL: define i1 @test2_fn4(
-; CHECK-SAME: i8 [[BF_LOAD:%.*]]) local_unnamed_addr #[[ATTR5:[0-9]+]] {
+; CHECK-SAME: i8 [[BF_LOAD:%.*]]) local_unnamed_addr #[[ATTR4:[0-9]+]] {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[TOBOOL:%.*]] = icmp slt i8 [[BF_LOAD]], 0
 ; CHECK-NEXT:    ret i1 [[TOBOOL]]
@@ -232,7 +207,7 @@ entry:
 
 define internal i32 @test2_fn6() {
 ; CHECK-LABEL: define internal fastcc noundef i32 @test2_fn6(
-; CHECK-SAME: ) unnamed_addr #[[ATTR5]] {
+; CHECK-SAME: ) unnamed_addr #[[ATTR4]] {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    ret i32 0
 ;
diff --git a/llvm/test/Transforms/PhaseOrdering/pr45682.ll b/llvm/test/Transforms/PhaseOrdering/pr45682.ll
index 46ee19178e356..50e31f1bf632d 100644
--- a/llvm/test/Transforms/PhaseOrdering/pr45682.ll
+++ b/llvm/test/Transforms/PhaseOrdering/pr45682.ll
@@ -5,8 +5,6 @@
 define void @PR45682(i32 %x, i32 %y) {
 ; CHECK-LABEL: @PR45682(
 ; CHECK-NEXT:  entry:
-; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i32 [[Y:%.*]], 0
-; CHECK-NEXT:    tail call void @llvm.assume(i1 [[CMP]])
 ; CHECK-NEXT:    ret void
 ;
 entry:
diff --git a/llvm/test/Transforms/PhaseOrdering/pr45687.ll b/llvm/test/Transforms/PhaseOrdering/pr45687.ll
index f0064b58820e4..36d7c24f2bd79 100644
--- a/llvm/test/Transforms/PhaseOrdering/pr45687.ll
+++ b/llvm/test/Transforms/PhaseOrdering/pr45687.ll
@@ -3,9 +3,6 @@
 
 define void @PR45687(i32 %0)  {
 ; CHECK-LABEL: @PR45687(
-; CHECK-NEXT:    [[TMP2:%.*]] = add i32 [[TMP0:%.*]], 1
-; CHECK-NEXT:    [[TMP3:%.*]] = icmp ult i32 [[TMP2]], 3
-; CHECK-NEXT:    tail call void @llvm.assume(i1 [[TMP3]])
 ; CHECK-NEXT:    ret void
 ;
   %2 = add i32 %0, 1
diff --git a/llvm/test/Transforms/PhaseOrdering/pr98799-inline-simplifycfg-ub.ll b/llvm/test/Transforms/PhaseOrdering/pr98799-inline-simplifycfg-ub.ll
index 2bb78dd5ec773..5c4ada38bd302 100644
--- a/llvm/test/Transforms/PhaseOrdering/pr98799-inline-simplifycfg-ub.ll
+++ b/llvm/test/Transforms/PhaseOrdering/pr98799-inline-simplifycfg-ub.ll
@@ -34,8 +34,6 @@ define i32 @foo(ptr %arg, i1 %arg1) {
 ; O2-LABEL: define i32 @foo(
 ; O2-SAME: ptr captures(none) [[ARG:%.*]], i1 [[ARG1:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
 ; O2-NEXT:  [[BB:.*:]]
-; O2-NEXT:    [[TMP0:%.*]] = xor i1 [[ARG1]], true
-; O2-NEXT:    tail call void @llvm.assume(i1 [[TMP0]])
 ; O2-NEXT:    [[I_I:%.*]] = load ptr, ptr [[ARG]], align 8, !nonnull [[META0:![0-9]+]], !noundef [[META0]]
 ; O2-NEXT:    [[I3_I:%.*]] = getelementptr inbounds nuw i8, ptr [[I_I]], i64 1
 ; O2-NEXT:    store ptr [[I3_I]], ptr [[ARG]], align 8

Copy link
Contributor

@fhahn fhahn left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

Hopefully that is one more step to allow us to make more and better use of assumes

@andjo403
Copy link
Contributor

andjo403 commented Sep 17, 2025

Do you think that this pass in the future can be a good place for dropping assumes by setting eg. ranges to arguments something similar to

// assume( (load addr) != null ) -> add 'nonnull' metadata to load
// (if assume is valid at the load)
Instruction *LHS;
if (match(IIOperand, m_SpecificICmp(ICmpInst::ICMP_NE, m_Instruction(LHS),
m_Zero())) &&
LHS->getOpcode() == Instruction::Load &&
LHS->getType()->isPointerTy() &&
isValidAssumeForContext(II, LHS, &DT)) {
MDNode *MD = MDNode::get(II->getContext(), {});
LHS->setMetadata(LLVMContext::MD_nonnull, MD);
LHS->setMetadata(LLVMContext::MD_noundef, MD);
return RemoveConditionFromAssume(II);
// TODO: apply nonnull return attributes to calls and invokes
// TODO: apply range metadata for range check patterns?
}

I have tried to add handling for ranges there but there was regressions due to new uses after inlining

Copy link
Contributor

@artagnon artagnon left a comment

Choose a reason for hiding this comment

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

Could a possible follow-up drop identical assumes? If so, would it be possible to schedule this pass late? We see some redundant identical assumes in LV.

This adds a new pass for dropping assumes that are unlikely to
be useful for further optimization.

It works by discarding any assumes whose affected values are
ephemeral (that is, only used by the assume).

This pass currently runs at the start of the module optimization
pipeline, that is post-inline (and post-link). Before that point,
it is more likely for previously "useless" assumes to become useful
again, e.g. because an additional user of the value is introduced
after inlining + CSE.
@nikic nikic force-pushed the drop-unnecessary-assumes branch from 4a818d7 to 27d9746 Compare September 18, 2025 07:40
Copy link
Contributor

@artagnon artagnon left a comment

Choose a reason for hiding this comment

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

LGTM, thanks! I will investigate a follow-up related to my question.

@nikic
Copy link
Contributor Author

nikic commented Sep 18, 2025

Could a possible follow-up drop identical assumes? If so, would it be possible to schedule this pass late? We see some redundant identical assumes in LV.

The responsibility for removing redundant assumes falls largely to InstCombine, and to a lesser degree other passes that propagate assumes (like GVN, SCCP).

@nikic
Copy link
Contributor Author

nikic commented Sep 18, 2025

Do you think that this pass in the future can be a good place for dropping assumes by setting eg. ranges to arguments something similar to

// assume( (load addr) != null ) -> add 'nonnull' metadata to load
// (if assume is valid at the load)
Instruction *LHS;
if (match(IIOperand, m_SpecificICmp(ICmpInst::ICMP_NE, m_Instruction(LHS),
m_Zero())) &&
LHS->getOpcode() == Instruction::Load &&
LHS->getType()->isPointerTy() &&
isValidAssumeForContext(II, LHS, &DT)) {
MDNode *MD = MDNode::get(II->getContext(), {});
LHS->setMetadata(LLVMContext::MD_nonnull, MD);
LHS->setMetadata(LLVMContext::MD_noundef, MD);
return RemoveConditionFromAssume(II);
// TODO: apply nonnull return attributes to calls and invokes
// TODO: apply range metadata for range check patterns?
}

I have tried to add handling for ranges there but there was regressions due to new uses after inlining

I think that ideally we would do this in InstCombine, and then materialize the assume again if the instruction/function the metadata/attribute was attached to gets removed. We do this for nonnull is SROA:

static void addAssumeNonNull(AssumptionCache *AC, LoadInst *LI) {
Function *AssumeIntrinsic =
Intrinsic::getOrInsertDeclaration(LI->getModule(), Intrinsic::assume);
ICmpInst *LoadNotNull = new ICmpInst(ICmpInst::ICMP_NE, LI,
Constant::getNullValue(LI->getType()));
LoadNotNull->insertAfter(LI->getIterator());
CallInst *CI = CallInst::Create(AssumeIntrinsic, {LoadNotNull});
CI->insertAfter(LoadNotNull->getIterator());
AC->registerAssumption(cast<AssumeInst>(CI));
}
static void convertMetadataToAssumes(LoadInst *LI, Value *Val,
const DataLayout &DL, AssumptionCache *AC,
const DominatorTree *DT) {
if (isa<UndefValue>(Val) && LI->hasMetadata(LLVMContext::MD_noundef)) {
// Insert non-terminator unreachable.
LLVMContext &Ctx = LI->getContext();
new StoreInst(ConstantInt::getTrue(Ctx),
PoisonValue::get(PointerType::getUnqual(Ctx)),
/*isVolatile=*/false, Align(1), LI->getIterator());
return;
}
// If the load was marked as nonnull we don't want to lose that information
// when we erase this Load. So we preserve it with an assume. As !nonnull
// returns poison while assume violations are immediate undefined behavior,
// we can only do this if the value is known non-poison.
if (AC && LI->getMetadata(LLVMContext::MD_nonnull) &&
LI->getMetadata(LLVMContext::MD_noundef) &&
!isKnownNonZero(Val, SimplifyQuery(DL, DT, AC, LI)))
addAssumeNonNull(AC, LI);
}
But haven't extended this further due to the usual problem where assumes interfere too much with other optimizations for this to be overall worthwhile.

I hope that more aggressively dropping useless assumes will make this approach more viable...

Copy link
Member

@dtcxzyw dtcxzyw left a comment

Choose a reason for hiding this comment

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

LG. Thanks.
FYI I did a similar attempt last year to drop useless assumptions (inserted by rustc): main...dtcxzyw:llvm-project:perf/remove-dead-assume. It removes assumptions in leaf nodes of the dominator tree. Unfortunately it didn't work because SimplifyCFG may remove other edges and make them useful again.

invokeOptimizerEarlyEPCallbacks(MPM, Level, LTOPhase);

FunctionPassManager OptimizePM;
if (!isLTOPreLink(LTOPhase))
Copy link
Member

Choose a reason for hiding this comment

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

It deserves a comment here to explain why it only runs at post-inline and post-link.

@nikic nikic enabled auto-merge (squash) September 18, 2025 14:11
@nikic nikic merged commit 902ddda into llvm:main Sep 18, 2025
9 checks passed
@nikic nikic deleted the drop-unnecessary-assumes branch September 18, 2025 14:44
@llvm-ci
Copy link
Collaborator

llvm-ci commented Sep 18, 2025

LLVM Buildbot has detected a new failure on builder openmp-s390x-linux running on systemz-1 while building clang,llvm at step 6 "test-openmp".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/88/builds/16220

Here is the relevant piece of the build log for the reference
Step 6 (test-openmp) failure: test (failure)
******************** TEST 'libomp :: tasking/issue-94260-2.c' FAILED ********************
Exit Code: -11

Command Output (stdout):
--
# RUN: at line 1
/home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.build/./bin/clang -fopenmp   -I /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.build/runtimes/runtimes-bins/openmp/runtime/src -I /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.src/openmp/runtime/test -L /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.build/runtimes/runtimes-bins/openmp/runtime/src  -fno-omit-frame-pointer -mbackchain -I /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.src/openmp/runtime/test/ompt /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.src/openmp/runtime/test/tasking/issue-94260-2.c -o /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.build/runtimes/runtimes-bins/openmp/runtime/test/tasking/Output/issue-94260-2.c.tmp -lm -latomic && /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.build/runtimes/runtimes-bins/openmp/runtime/test/tasking/Output/issue-94260-2.c.tmp
# executed command: /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.build/./bin/clang -fopenmp -I /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.build/runtimes/runtimes-bins/openmp/runtime/src -I /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.src/openmp/runtime/test -L /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.build/runtimes/runtimes-bins/openmp/runtime/src -fno-omit-frame-pointer -mbackchain -I /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.src/openmp/runtime/test/ompt /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.src/openmp/runtime/test/tasking/issue-94260-2.c -o /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.build/runtimes/runtimes-bins/openmp/runtime/test/tasking/Output/issue-94260-2.c.tmp -lm -latomic
# executed command: /home/uweigand/sandbox/buildbot/openmp-s390x-linux/llvm.build/runtimes/runtimes-bins/openmp/runtime/test/tasking/Output/issue-94260-2.c.tmp
# note: command had no output on stdout or stderr
# error: command failed with exit status: -11

--

********************


@llvm-ci
Copy link
Collaborator

llvm-ci commented Sep 18, 2025

LLVM Buildbot has detected a new failure on builder ppc64le-lld-multistage-test running on ppc64le-lld-multistage-test while building clang,llvm at step 12 "build-stage2-unified-tree".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/168/builds/16217

Here is the relevant piece of the build log for the reference
Step 12 (build-stage2-unified-tree) failure: build (failure) (timed out)
...
3136.794 [6/6/6694] Linking CXX shared library lib/libclang.so.22.0.0git
3136.918 [5/6/6695] Creating library symlink lib/libclang.so.22.0git lib/libclang.so
3137.967 [2/8/6696] Linking CXX executable tools/clang/unittests/libclang/CrashTests/libclangCrashTests
3138.207 [2/7/6697] Linking CXX executable tools/clang/unittests/Interpreter/ClangReplInterpreterTests
3139.035 [2/6/6698] Linking CXX executable bin/clang-22
3139.318 [1/6/6699] Creating executable symlink bin/clang
3140.444 [1/5/6700] Linking CXX executable bin/clang-repl
3140.933 [1/4/6701] Linking CXX executable bin/c-index-test
3142.304 [1/3/6702] Linking CXX executable tools/clang/unittests/Interpreter/ExceptionTests/ClangReplInterpreterExceptionTests
3153.674 [1/2/6703] Linking CXX executable tools/clang/unittests/AllClangUnitTests
command timed out: 1200 seconds without output running [b'ninja'], attempting to kill
process killed by signal 9
program finished with exit code -1
elapsedTime=4354.197414

kimsh02 pushed a commit to kimsh02/llvm-project that referenced this pull request Sep 19, 2025
This adds a new pass for dropping assumes that are unlikely to be useful
for further optimization.

It works by discarding any assumes whose affected values are one-use
(which implies that they are only used by the assume, i.e. ephemeral).

This pass currently runs at the start of the module optimization
pipeline, that is post-inline and post-link. Before that point, it is
more likely for previously "useless" assumes to become useful again,
e.g. because an additional user of the value is introduced after
inlining + CSE.
SeongjaeP pushed a commit to SeongjaeP/llvm-project that referenced this pull request Sep 23, 2025
This adds a new pass for dropping assumes that are unlikely to be useful
for further optimization.

It works by discarding any assumes whose affected values are one-use
(which implies that they are only used by the assume, i.e. ephemeral).

This pass currently runs at the start of the module optimization
pipeline, that is post-inline and post-link. Before that point, it is
more likely for previously "useless" assumes to become useful again,
e.g. because an additional user of the value is introduced after
inlining + CSE.
aarongable pushed a commit to chromium/chromium that referenced this pull request Sep 24, 2025
llvm/llvm-project#159403 adds a pass which
causes the tests in question to fail because it removes several calls to
`llvm.assume(...)`. The fix at
rust-lang/rust#146732 effectively neuters the
test, so we may as well temporarily disable it on our side.

Bug: 446928953
Change-Id: Iade597d144e68bf384076673b75f2f3f616de5b8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6977874
Auto-Submit: Alan Zhao <[email protected]>
Reviewed-by: Hans Wennborg <[email protected]>
Commit-Queue: Hans Wennborg <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1519796}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category llvm:transforms
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants