[llvm] [CVP] Replace constant call args with known-equal function params (PR #196715)
via llvm-commits
llvm-commits at lists.llvm.org
Sat May 9 05:13:35 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Aayush Shrivastava (iamaayushrivastava)
<details>
<summary>Changes</summary>
Main Reason: `CorrelatedValuePropagation::processCallSite` already replaces *variable* arguments with their known-constant values. It did not do the reverse: replace a *constant* argument `C` with a function parameter known to equal `C` at the call site.
Before IR: call @<!-- -->fdecl_0(i32 noundef 0, i64 noundef 0)
After IR: call @<!-- -->fdecl_0(i32 noundef %a, i64 noundef 0)
(`i64 0` is unchanged - type mismatch with the `i32` parameter.)
## x86-64 codegen improvement
```asm
; Before (extra xor edi, edi) ; After (matches GCC)
xor edi, edi xor esi, esi
xor esi, esi jmp fdecl_0@<!-- -->PLT
jmp fdecl_0@<!-- -->PLT
[Added new lit testcases at `Transforms/CorrelatedValuePropagation/call-arg-known-param.ll` covering basic zero, non-zero constant, type mismatch, and two-parameter cases.
---
Full diff: https://github.com/llvm/llvm-project/pull/196715.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp (+79-49)
- (added) llvm/test/Transforms/CorrelatedValuePropagation/call-arg-known-param.ll (+82)
``````````diff
diff --git a/llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp b/llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp
index ff0b70b51e5f7..0ef1bd2cb07f1 100644
--- a/llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp
+++ b/llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp
@@ -49,43 +49,43 @@ using namespace llvm;
#define DEBUG_TYPE "correlated-value-propagation"
-STATISTIC(NumPhis, "Number of phis propagated");
+STATISTIC(NumPhis, "Number of phis propagated");
STATISTIC(NumPhiCommon, "Number of phis deleted via common incoming value");
-STATISTIC(NumSelects, "Number of selects propagated");
-STATISTIC(NumCmps, "Number of comparisons propagated");
-STATISTIC(NumReturns, "Number of return values propagated");
+STATISTIC(NumSelects, "Number of selects propagated");
+STATISTIC(NumCmps, "Number of comparisons propagated");
+STATISTIC(NumReturns, "Number of return values propagated");
STATISTIC(NumDeadCases, "Number of switch cases removed");
STATISTIC(NumSDivSRemsNarrowed,
"Number of sdivs/srems whose width was decreased");
-STATISTIC(NumSDivs, "Number of sdiv converted to udiv");
+STATISTIC(NumSDivs, "Number of sdiv converted to udiv");
STATISTIC(NumUDivURemsNarrowed,
"Number of udivs/urems whose width was decreased");
STATISTIC(NumAShrsConverted, "Number of ashr converted to lshr");
STATISTIC(NumAShrsRemoved, "Number of ashr removed");
-STATISTIC(NumSRems, "Number of srem converted to urem");
-STATISTIC(NumSExt, "Number of sext converted to zext");
-STATISTIC(NumSIToFP, "Number of sitofp converted to uitofp");
-STATISTIC(NumSICmps, "Number of signed icmp preds simplified to unsigned");
-STATISTIC(NumAnd, "Number of ands removed");
-STATISTIC(NumNW, "Number of no-wrap deductions");
-STATISTIC(NumNSW, "Number of no-signed-wrap deductions");
-STATISTIC(NumNUW, "Number of no-unsigned-wrap deductions");
-STATISTIC(NumAddNW, "Number of no-wrap deductions for add");
-STATISTIC(NumAddNSW, "Number of no-signed-wrap deductions for add");
-STATISTIC(NumAddNUW, "Number of no-unsigned-wrap deductions for add");
-STATISTIC(NumSubNW, "Number of no-wrap deductions for sub");
-STATISTIC(NumSubNSW, "Number of no-signed-wrap deductions for sub");
-STATISTIC(NumSubNUW, "Number of no-unsigned-wrap deductions for sub");
-STATISTIC(NumMulNW, "Number of no-wrap deductions for mul");
-STATISTIC(NumMulNSW, "Number of no-signed-wrap deductions for mul");
-STATISTIC(NumMulNUW, "Number of no-unsigned-wrap deductions for mul");
-STATISTIC(NumShlNW, "Number of no-wrap deductions for shl");
-STATISTIC(NumShlNSW, "Number of no-signed-wrap deductions for shl");
-STATISTIC(NumShlNUW, "Number of no-unsigned-wrap deductions for shl");
-STATISTIC(NumAbs, "Number of llvm.abs intrinsics removed");
+STATISTIC(NumSRems, "Number of srem converted to urem");
+STATISTIC(NumSExt, "Number of sext converted to zext");
+STATISTIC(NumSIToFP, "Number of sitofp converted to uitofp");
+STATISTIC(NumSICmps, "Number of signed icmp preds simplified to unsigned");
+STATISTIC(NumAnd, "Number of ands removed");
+STATISTIC(NumNW, "Number of no-wrap deductions");
+STATISTIC(NumNSW, "Number of no-signed-wrap deductions");
+STATISTIC(NumNUW, "Number of no-unsigned-wrap deductions");
+STATISTIC(NumAddNW, "Number of no-wrap deductions for add");
+STATISTIC(NumAddNSW, "Number of no-signed-wrap deductions for add");
+STATISTIC(NumAddNUW, "Number of no-unsigned-wrap deductions for add");
+STATISTIC(NumSubNW, "Number of no-wrap deductions for sub");
+STATISTIC(NumSubNSW, "Number of no-signed-wrap deductions for sub");
+STATISTIC(NumSubNUW, "Number of no-unsigned-wrap deductions for sub");
+STATISTIC(NumMulNW, "Number of no-wrap deductions for mul");
+STATISTIC(NumMulNSW, "Number of no-signed-wrap deductions for mul");
+STATISTIC(NumMulNUW, "Number of no-unsigned-wrap deductions for mul");
+STATISTIC(NumShlNW, "Number of no-wrap deductions for shl");
+STATISTIC(NumShlNSW, "Number of no-signed-wrap deductions for shl");
+STATISTIC(NumShlNUW, "Number of no-unsigned-wrap deductions for shl");
+STATISTIC(NumAbs, "Number of llvm.abs intrinsics removed");
STATISTIC(NumOverflows, "Number of overflow checks removed");
STATISTIC(NumSaturating,
- "Number of saturating arithmetics converted to normal arithmetics");
+ "Number of saturating arithmetics converted to normal arithmetics");
STATISTIC(NumNonNull, "Number of function pointer arguments marked non-null");
STATISTIC(NumCmpIntr, "Number of llvm.[us]cmp intrinsics removed");
STATISTIC(NumMinMax, "Number of llvm.[us]{min,max} intrinsics removed");
@@ -94,6 +94,8 @@ STATISTIC(NumSMinMax,
STATISTIC(NumUDivURemsNarrowedExpanded,
"Number of bound udiv's/urem's expanded");
STATISTIC(NumNNeg, "Number of zext/uitofp non-negative deductions");
+STATISTIC(NumCallArgReplacedWithParam,
+ "Number of call args replaced with known-equal function params");
static Constant *getConstantAt(Value *V, Instruction *At, LazyValueInfo *LVI) {
if (Constant *C = LVI->getConstant(V, At))
@@ -261,7 +263,8 @@ static bool processPHI(PHINode *P, LazyValueInfo *LVI, DominatorTree *DT,
BasicBlock *BB = P->getParent();
for (unsigned i = 0, e = P->getNumIncomingValues(); i < e; ++i) {
Value *Incoming = P->getIncomingValue(i);
- if (isa<Constant>(Incoming)) continue;
+ if (isa<Constant>(Incoming))
+ continue;
Value *V = getValueOnEdge(LVI, Incoming, P->getIncomingBlock(i), BB, P);
if (V) {
@@ -372,7 +375,7 @@ static bool processSwitch(SwitchInst *I, LazyValueInfo *LVI,
// Analyse each switch case in turn.
bool Changed = false;
- DenseMap<BasicBlock*, int> SuccessorsCount;
+ DenseMap<BasicBlock *, int> SuccessorsCount;
for (auto *Succ : successors(BB))
SuccessorsCount[Succ]++;
@@ -409,8 +412,8 @@ static bool processSwitch(SwitchInst *I, LazyValueInfo *LVI,
CI = SI.removeCase(CI);
CE = SI->case_end();
- // The condition can be modified by removePredecessor's PHI simplification
- // logic.
+ // The condition can be modified by removePredecessor's PHI
+ // simplification logic.
Cond = SI->getCondition();
++NumDeadCases;
@@ -646,9 +649,9 @@ static bool processOverflowIntrinsic(WithOverflowInst *WO, LazyValueInfo *LVI) {
setDeducedOverflowingFlags(NewOp, Opcode, NSW, NUW);
StructType *ST = cast<StructType>(WO->getType());
- Constant *Struct = ConstantStruct::get(ST,
- { PoisonValue::get(ST->getElementType(0)),
- ConstantInt::getFalse(ST->getElementType(1)) });
+ Constant *Struct =
+ ConstantStruct::get(ST, {PoisonValue::get(ST->getElementType(0)),
+ ConstantInt::getFalse(ST->getElementType(1))});
Value *NewI = B.CreateInsertValue(Struct, NewOp, 0);
WO->replaceAllUsesWith(NewI);
WO->eraseFromParent();
@@ -716,13 +719,16 @@ static bool processCallSite(CallBase &CB, LazyValueInfo *LVI) {
// we may have a conditional fact with which LVI can fold.
if (auto DeoptBundle = CB.getOperandBundle(LLVMContext::OB_deopt)) {
for (const Use &ConstU : DeoptBundle->Inputs) {
- Use &U = const_cast<Use&>(ConstU);
+ Use &U = const_cast<Use &>(ConstU);
Value *V = U.get();
- if (V->getType()->isVectorTy()) continue;
- if (isa<Constant>(V)) continue;
+ if (V->getType()->isVectorTy())
+ continue;
+ if (isa<Constant>(V))
+ continue;
Constant *C = LVI->getConstant(V, &CB);
- if (!C) continue;
+ if (!C)
+ continue;
U.set(C);
Changed = true;
}
@@ -748,17 +754,39 @@ static bool processCallSite(CallBase &CB, LazyValueInfo *LVI) {
assert(ArgNo == CB.arg_size() && "Call arguments not processed correctly.");
- if (ArgNos.empty())
- return Changed;
+ if (!ArgNos.empty()) {
+ NumNonNull += ArgNos.size();
+ AttributeList AS = CB.getAttributes();
+ LLVMContext &Ctx = CB.getContext();
+ AS = AS.addParamAttribute(Ctx, ArgNos,
+ Attribute::get(Ctx, Attribute::NonNull));
+ CB.setAttributes(AS);
+ Changed = true;
+ }
- NumNonNull += ArgNos.size();
- AttributeList AS = CB.getAttributes();
- LLVMContext &Ctx = CB.getContext();
- AS = AS.addParamAttribute(Ctx, ArgNos,
- Attribute::get(Ctx, Attribute::NonNull));
- CB.setAttributes(AS);
+ // If a call argument is a constant C, and a function parameter is known to
+ // equal C at this call site, replace C with the parameter. This lets the
+ // backend reuse the already-loaded register instead of materializing C again.
+ // Example: if (a == 0) foo(0) → if (a == 0) foo(a), saving "xor edi, edi".
+ Function *F = CB.getParent()->getParent();
+ for (unsigned I = 0, E = CB.arg_size(); I != E; ++I) {
+ auto *CI = dyn_cast<ConstantInt>(CB.getArgOperand(I));
+ if (!CI)
+ continue;
+ for (Argument &Param : F->args()) {
+ if (Param.getType() != CI->getType())
+ continue;
+ Constant *Known = LVI->getConstant(&Param, &CB);
+ if (Known != CI)
+ continue;
+ CB.setArgOperand(I, &Param);
+ ++NumCallArgReplacedWithParam;
+ Changed = true;
+ break;
+ }
+ }
- return true;
+ return Changed;
}
enum class Domain { NonNegative, NonPositive, Unknown };
@@ -1341,13 +1369,15 @@ static bool runImpl(Function &F, LazyValueInfo *LVI, DominatorTree *DT,
// simplify the writing of unit tests, but also helps to enable IPO by
// constant folding the return values of callees.
auto *RetVal = RI->getReturnValue();
- if (!RetVal) break; // handle "ret void"
+ if (!RetVal)
+ break; // handle "ret void"
if (RetRange && !RetRange->isFullSet())
RetRange =
RetRange->unionWith(LVI->getConstantRange(RetVal, RI,
/*UndefAllowed=*/false));
- if (isa<Constant>(RetVal)) break; // nothing to do
+ if (isa<Constant>(RetVal))
+ break; // nothing to do
if (auto *C = getConstantAt(RetVal, RI, LVI)) {
++NumReturns;
RI->replaceUsesOfWith(RetVal, C);
diff --git a/llvm/test/Transforms/CorrelatedValuePropagation/call-arg-known-param.ll b/llvm/test/Transforms/CorrelatedValuePropagation/call-arg-known-param.ll
new file mode 100644
index 0000000000000..511e127796b20
--- /dev/null
+++ b/llvm/test/Transforms/CorrelatedValuePropagation/call-arg-known-param.ll
@@ -0,0 +1,82 @@
+; RUN: opt -passes=correlated-propagation -S < %s | FileCheck %s
+
+; When a call argument is a constant C and a function parameter is known to
+; equal C at the call site (due to a guarding icmp eq), replace C with the
+; parameter so the backend can reuse the already-loaded register.
+; Issue: https://github.com/llvm/llvm-project/issues/195907
+
+; Basic case: i32 0 replaced with %a (same type, known equal).
+; i64 0 is NOT replaced because %a is i32 (type mismatch).
+define void @test_basic(i32 %a) {
+; CHECK-LABEL: @test_basic(
+; CHECK: if.then:
+; CHECK-NEXT: call void @fdecl_0(i32 %a, i64 0)
+entry:
+ %cmp = icmp eq i32 %a, 0
+ br i1 %cmp, label %if.then, label %if.end
+if.then:
+ call void @fdecl_0(i32 0, i64 0)
+ br label %if.end
+if.end:
+ ret void
+}
+
+; Non-zero constant: %a == 5, so 5 is replaced with %a.
+define void @test_nonzero(i32 %a) {
+; CHECK-LABEL: @test_nonzero(
+; CHECK: if.then:
+; CHECK-NEXT: call void @fdecl_i32(i32 %a)
+entry:
+ %cmp = icmp eq i32 %a, 5
+ br i1 %cmp, label %if.then, label %if.end
+if.then:
+ call void @fdecl_i32(i32 5)
+ br label %if.end
+if.end:
+ ret void
+}
+
+; Negative: no condition guards the constant -- leave it alone.
+define void @test_no_condition(i32 %a) {
+; CHECK-LABEL: @test_no_condition(
+; CHECK: call void @fdecl_i32(i32 0)
+ call void @fdecl_i32(i32 0)
+ ret void
+}
+
+; Negative: type mismatch -- %a is i32 but call arg is i64.
+define void @test_type_mismatch(i32 %a) {
+; CHECK-LABEL: @test_type_mismatch(
+; CHECK: if.then:
+; CHECK-NEXT: call void @fdecl_i64(i64 0)
+entry:
+ %cmp = icmp eq i32 %a, 0
+ br i1 %cmp, label %if.then, label %if.end
+if.then:
+ call void @fdecl_i64(i64 0)
+ br label %if.end
+if.end:
+ ret void
+}
+
+; Two params: both replaceable when the branch depends on both being equal.
+define void @test_two_params(i32 %a, i32 %b) {
+; CHECK-LABEL: @test_two_params(
+; CHECK: if.then:
+; CHECK-NEXT: call void @fdecl_two_i32(i32 %a, i32 %b)
+entry:
+ %cmp_a = icmp eq i32 %a, 1
+ %cmp_b = icmp eq i32 %b, 2
+ %and = and i1 %cmp_a, %cmp_b
+ br i1 %and, label %if.then, label %if.end
+if.then:
+ call void @fdecl_two_i32(i32 1, i32 2)
+ br label %if.end
+if.end:
+ ret void
+}
+
+declare void @fdecl_0(i32, i64)
+declare void @fdecl_i32(i32)
+declare void @fdecl_i64(i64)
+declare void @fdecl_two_i32(i32, i32)
\ No newline at end of file
``````````
</details>
https://github.com/llvm/llvm-project/pull/196715
More information about the llvm-commits
mailing list