[X86] Optimized ADD + ADC to ADC (#173543)

This patch folds an `adc` followed by an `add` into a single `adc` instruction when adding constants.

Fixes #173408
This commit is contained in:
Jaydeep Chauhan 2026-03-11 06:02:17 -07:00 committed by GitHub
parent 33584b6250
commit 3a6aa13f51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 101 additions and 0 deletions

View File

@ -58631,6 +58631,28 @@ static SDValue combineX86AddSub(SDNode *N, SelectionDAG &DAG,
}
}
// Fold ADD(ADC(Y, C1, CF), C2) -> ADC(Y, C1 + C2, CF) and
// ADD(ADC(Y, 0, CF), X) -> ADC(Y, X, CF)
if (!IsSub && LHS.getOpcode() == X86ISD::ADC && LHS.hasOneUse() &&
!needCarryOrOverflowFlag(SDValue(N, 1))) {
auto *C1 = dyn_cast<ConstantSDNode>(LHS.getOperand(1));
auto *C2 = dyn_cast<ConstantSDNode>(RHS);
// Both are constants: fold C1 + C2
if (C1 && C2) {
APInt Sum = C1->getAPIntValue() + C2->getAPIntValue();
return DAG.getNode(X86ISD::ADC, DL, N->getVTList(), LHS.getOperand(0),
DAG.getConstant(Sum, DL, VT), LHS.getOperand(2));
}
// Case: ADC(Y, 0, CF) + X -> ADC(Y, X, CF)
if (C1 && C1->isZero()) {
return DAG.getNode(X86ISD::ADC, DL, N->getVTList(), LHS.getOperand(0),
RHS, LHS.getOperand(2));
}
}
// TODO: Can we drop the ZeroSecondOpOnly limit? This is to guarantee that the
// EFLAGS result doesn't change.
return combineAddOrSubToADCOrSBB(IsSub, DL, VT, LHS, RHS, DAG,

View File

@ -561,3 +561,82 @@ define i64 @add_notx_x(i64 %v0) nounwind {
%y = add i64 %x, %v0
ret i64 %y
}
; Basic positive test
define i32 @add_adc_to_adc(i32 %0, i32 %1, i32 %2) nounwind {
; CHECK-LABEL: add_adc_to_adc:
; CHECK: # %bb.0:
; CHECK-NEXT: movl %edi, %eax
; CHECK-NEXT: cmpl %esi, %edi
; CHECK-NEXT: adcl $42, %edx
; CHECK-NEXT: cmovsl %esi, %eax
; CHECK-NEXT: retq
%4 = icmp ult i32 %0, %1
%5 = zext i1 %4 to i32
%6 = add i32 %2, 42
%7 = add i32 %6, %5
%8 = icmp slt i32 %7, 0
%9 = select i1 %8, i32 %1, i32 %0
ret i32 %9
}
; positive test: nonconst
define i32 @add_adc_to_adc_nonconst(i32 %0, i32 %1, i32 %2, i32 %extra) nounwind {
; CHECK-LABEL: add_adc_to_adc_nonconst:
; CHECK: # %bb.0:
; CHECK-NEXT: movl %edi, %eax
; CHECK-NEXT: cmpl %esi, %edi
; CHECK-NEXT: adcl %ecx, %edx
; CHECK-NEXT: cmovsl %esi, %eax
; CHECK-NEXT: retq
%c = icmp ult i32 %0, %1
%carry = zext i1 %c to i32
%adc = add i32 %2, %carry
%final = add i32 %adc, %extra
%neg = icmp slt i32 %final, 0
%sel = select i1 %neg, i32 %1, i32 %0
ret i32 %sel
}
; Negative test: Carry or overflow flag is used
define i32 @add_adc_wrong_flags(i32 %0, i32 %1, i32 %2) nounwind {
; CHECK-LABEL: add_adc_wrong_flags:
; CHECK: # %bb.0:
; CHECK-NEXT: movl %esi, %eax
; CHECK-NEXT: cmpl %esi, %edi
; CHECK-NEXT: adcl $10, %edx
; CHECK-NEXT: addl $32, %edx
; CHECK-NEXT: cmovbl %edi, %eax
; CHECK-NEXT: retq
%4 = icmp ult i32 %0, %1
%5 = zext i1 %4 to i32
%6 = add i32 %2, 10
%7 = add i32 %6, %5
%8 = tail call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 %7, i32 32)
%9 = extractvalue { i32, i1 } %8, 1
%10 = select i1 %9, i32 %0, i32 %1
ret i32 %10
}
; Negative test: Multi-use
define i32 @add_adc_multi_use(i32 %0, i32 %1, i32 %2) nounwind {
; CHECK-LABEL: add_adc_multi_use:
; CHECK: # %bb.0:
; CHECK-NEXT: # kill: def $edx killed $edx def $rdx
; CHECK-NEXT: # kill: def $esi killed $esi def $rsi
; CHECK-NEXT: cmpl %esi, %edi
; CHECK-NEXT: adcl $0, %edx
; CHECK-NEXT: movl %edx, %eax
; CHECK-NEXT: addl $42, %eax
; CHECK-NEXT: cmovsl %edi, %esi
; CHECK-NEXT: leal (%rsi,%rdx), %eax
; CHECK-NEXT: retq
%4 = icmp ult i32 %0, %1
%5 = zext i1 %4 to i32
%6 = add i32 %2, %5
%7 = add i32 %6, 42
%8 = icmp slt i32 %7, 0
%9 = select i1 %8, i32 %0, i32 %1
%10 = add i32 %9, %6
ret i32 %10
}