[VectorCombine] Support pattern bitop(bitcast(x), C) -> bitcast(bitop(x, InvC)) (#155216)

Resolves #154797.
This patch adds the fold `bitop(bitcast(x), C) -> bitop(bitcast(x),
cast(InvC)) -> bitcast(bitop(x, InvC))`.
The helper function `getLosslessInvCast` tries to calculate the constant
`InvC`, satisfying `castop(InvC) == C`, and will try its best to keep
the poison-generated flags of the cast operation.
This commit is contained in:
XChy 2025-09-02 23:54:12 +08:00 committed by GitHub
parent 8dee9e465b
commit cb80fa756c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 303 additions and 0 deletions

View File

@ -122,6 +122,7 @@ private:
bool foldInsExtBinop(Instruction &I);
bool foldInsExtVectorToShuffle(Instruction &I);
bool foldBitOpOfCastops(Instruction &I);
bool foldBitOpOfCastConstant(Instruction &I);
bool foldBitcastShuffle(Instruction &I);
bool scalarizeOpOrCmp(Instruction &I);
bool scalarizeVPIntrinsic(Instruction &I);
@ -937,6 +938,146 @@ bool VectorCombine::foldBitOpOfCastops(Instruction &I) {
return true;
}
struct PreservedCastFlags {
bool NNeg = false;
bool NUW = false;
bool NSW = false;
};
// Try to cast C to InvC losslessly, satisfying CastOp(InvC) == C.
// Will try best to preserve the flags.
static Constant *getLosslessInvCast(Constant *C, Type *InvCastTo,
Instruction::CastOps CastOp,
const DataLayout &DL,
PreservedCastFlags &Flags) {
switch (CastOp) {
case Instruction::BitCast:
// Bitcast is always lossless.
return ConstantFoldCastOperand(Instruction::BitCast, C, InvCastTo, DL);
case Instruction::Trunc: {
auto *ZExtC = ConstantFoldCastOperand(Instruction::ZExt, C, InvCastTo, DL);
auto *SExtC = ConstantFoldCastOperand(Instruction::SExt, C, InvCastTo, DL);
// Truncation back on ZExt value is always NUW.
Flags.NUW = true;
// Test positivity of C.
Flags.NSW = ZExtC == SExtC;
return ZExtC;
}
case Instruction::SExt:
case Instruction::ZExt: {
auto *InvC = ConstantExpr::getTrunc(C, InvCastTo);
auto *CastInvC = ConstantFoldCastOperand(CastOp, InvC, C->getType(), DL);
// Must satisfy CastOp(InvC) == C.
if (!CastInvC || CastInvC != C)
return nullptr;
if (CastOp == Instruction::ZExt) {
auto *SExtInvC =
ConstantFoldCastOperand(Instruction::SExt, InvC, C->getType(), DL);
// Test positivity of InvC.
Flags.NNeg = CastInvC == SExtInvC;
}
return InvC;
}
default:
return nullptr;
}
}
/// Match:
// bitop(castop(x), C) ->
// bitop(castop(x), castop(InvC)) ->
// castop(bitop(x, InvC))
// Supports: bitcast
bool VectorCombine::foldBitOpOfCastConstant(Instruction &I) {
Instruction *LHS;
Constant *C;
// Check if this is a bitwise logic operation
if (!match(&I, m_c_BitwiseLogic(m_Instruction(LHS), m_Constant(C))))
return false;
// Get the cast instructions
auto *LHSCast = dyn_cast<CastInst>(LHS);
if (!LHSCast)
return false;
Instruction::CastOps CastOpcode = LHSCast->getOpcode();
// Only handle supported cast operations
switch (CastOpcode) {
case Instruction::BitCast:
break;
default:
return false;
}
Value *LHSSrc = LHSCast->getOperand(0);
// Only handle vector types with integer elements
auto *SrcVecTy = dyn_cast<FixedVectorType>(LHSSrc->getType());
auto *DstVecTy = dyn_cast<FixedVectorType>(I.getType());
if (!SrcVecTy || !DstVecTy)
return false;
if (!SrcVecTy->getScalarType()->isIntegerTy() ||
!DstVecTy->getScalarType()->isIntegerTy())
return false;
// Find the constant InvC, such that castop(InvC) equals to C.
PreservedCastFlags RHSFlags;
Constant *InvC = getLosslessInvCast(C, SrcVecTy, CastOpcode, *DL, RHSFlags);
if (!InvC)
return false;
// Cost Check :
// OldCost = bitlogic + cast
// NewCost = bitlogic + cast
// Calculate specific costs for each cast with instruction context
InstructionCost LHSCastCost =
TTI.getCastInstrCost(CastOpcode, DstVecTy, SrcVecTy,
TTI::CastContextHint::None, CostKind, LHSCast);
InstructionCost OldCost =
TTI.getArithmeticInstrCost(I.getOpcode(), DstVecTy, CostKind) +
LHSCastCost;
// For new cost, we can't provide an instruction (it doesn't exist yet)
InstructionCost GenericCastCost = TTI.getCastInstrCost(
CastOpcode, DstVecTy, SrcVecTy, TTI::CastContextHint::None, CostKind);
InstructionCost NewCost =
TTI.getArithmeticInstrCost(I.getOpcode(), SrcVecTy, CostKind) +
GenericCastCost;
// Account for multi-use casts using specific costs
if (!LHSCast->hasOneUse())
NewCost += LHSCastCost;
LLVM_DEBUG(dbgs() << "foldBitOpOfCastConstant: OldCost=" << OldCost
<< " NewCost=" << NewCost << "\n");
if (NewCost > OldCost)
return false;
// Create the operation on the source type
Value *NewOp = Builder.CreateBinOp((Instruction::BinaryOps)I.getOpcode(),
LHSSrc, InvC, I.getName() + ".inner");
if (auto *NewBinOp = dyn_cast<BinaryOperator>(NewOp))
NewBinOp->copyIRFlags(&I);
Worklist.pushValue(NewOp);
// Create the cast operation directly to ensure we get a new instruction
Instruction *NewCast = CastInst::Create(CastOpcode, NewOp, I.getType());
// Insert the new instruction
Value *Result = Builder.Insert(NewCast);
replaceValue(I, *Result);
return true;
}
/// If this is a bitcast of a shuffle, try to bitcast the source vector to the
/// destination type followed by shuffle. This can enable further transforms by
/// moving bitcasts or shuffles together.
@ -4474,6 +4615,8 @@ bool VectorCombine::run() {
case Instruction::Xor:
if (foldBitOpOfCastops(I))
return true;
if (foldBitOpOfCastConstant(I))
return true;
break;
case Instruction::PHI:
if (shrinkPhiOfShuffles(I))

View File

@ -260,3 +260,163 @@ define <4 x i32> @or_zext_nneg(<4 x i16> %a, <4 x i16> %b) {
%or = or <4 x i32> %z1, %z2
ret <4 x i32> %or
}
; Test bitwise operations with integer-to-integer bitcast with one constant
define <2 x i32> @or_bitcast_v4i16_to_v2i32_constant(<4 x i16> %a) {
; CHECK-LABEL: @or_bitcast_v4i16_to_v2i32_constant(
; CHECK-NEXT: [[A:%.*]] = or <4 x i16> [[A1:%.*]], <i16 16960, i16 15, i16 -31616, i16 30>
; CHECK-NEXT: [[BC1:%.*]] = bitcast <4 x i16> [[A]] to <2 x i32>
; CHECK-NEXT: ret <2 x i32> [[BC1]]
;
%bc1 = bitcast <4 x i16> %a to <2 x i32>
%or = or <2 x i32> %bc1, <i32 1000000, i32 2000000>
ret <2 x i32> %or
}
define <2 x i32> @or_bitcast_v4i16_to_v2i32_constant_commuted(<4 x i16> %a) {
; CHECK-LABEL: @or_bitcast_v4i16_to_v2i32_constant_commuted(
; CHECK-NEXT: [[A:%.*]] = or <4 x i16> [[A1:%.*]], <i16 16960, i16 15, i16 -31616, i16 30>
; CHECK-NEXT: [[BC1:%.*]] = bitcast <4 x i16> [[A]] to <2 x i32>
; CHECK-NEXT: ret <2 x i32> [[BC1]]
;
%bc1 = bitcast <4 x i16> %a to <2 x i32>
%or = or <2 x i32> <i32 1000000, i32 2000000>, %bc1
ret <2 x i32> %or
}
; Test bitwise operations with truncate and one constant
define <4 x i16> @or_trunc_v4i32_to_v4i16_constant(<4 x i32> %a) {
; CHECK-LABEL: @or_trunc_v4i32_to_v4i16_constant(
; CHECK-NEXT: [[T1:%.*]] = trunc <4 x i32> [[A:%.*]] to <4 x i16>
; CHECK-NEXT: [[OR:%.*]] = or <4 x i16> [[T1]], <i16 1, i16 2, i16 3, i16 4>
; CHECK-NEXT: ret <4 x i16> [[OR]]
;
%t1 = trunc <4 x i32> %a to <4 x i16>
%or = or <4 x i16> %t1, <i16 1, i16 2, i16 3, i16 4>
ret <4 x i16> %or
}
; Test bitwise operations with zero extend and one constant
define <4 x i32> @or_zext_v4i16_to_v4i32_constant(<4 x i16> %a) {
; CHECK-LABEL: @or_zext_v4i16_to_v4i32_constant(
; CHECK-NEXT: [[Z1:%.*]] = zext <4 x i16> [[A:%.*]] to <4 x i32>
; CHECK-NEXT: [[OR:%.*]] = or <4 x i32> [[Z1]], <i32 1, i32 2, i32 3, i32 4>
; CHECK-NEXT: ret <4 x i32> [[OR]]
;
%z1 = zext <4 x i16> %a to <4 x i32>
%or = or <4 x i32> %z1, <i32 1, i32 2, i32 3, i32 4>
ret <4 x i32> %or
}
define <4 x i32> @or_zext_v4i8_to_v4i32_constant_with_loss(<4 x i8> %a) {
; CHECK-LABEL: @or_zext_v4i8_to_v4i32_constant_with_loss(
; CHECK-NEXT: [[Z1:%.*]] = zext <4 x i8> [[A:%.*]] to <4 x i32>
; CHECK-NEXT: [[OR:%.*]] = or <4 x i32> [[Z1]], <i32 1024, i32 129, i32 3, i32 4>
; CHECK-NEXT: ret <4 x i32> [[OR]]
;
%z1 = zext <4 x i8> %a to <4 x i32>
%or = or <4 x i32> %z1, <i32 1024, i32 129, i32 3, i32 4>
ret <4 x i32> %or
}
; Test bitwise operations with sign extend and one constant
define <4 x i32> @or_sext_v4i8_to_v4i32_positive_constant(<4 x i8> %a) {
; CHECK-LABEL: @or_sext_v4i8_to_v4i32_positive_constant(
; CHECK-NEXT: [[S1:%.*]] = sext <4 x i8> [[A:%.*]] to <4 x i32>
; CHECK-NEXT: [[OR:%.*]] = or <4 x i32> [[S1]], <i32 1, i32 2, i32 3, i32 4>
; CHECK-NEXT: ret <4 x i32> [[OR]]
;
%s1 = sext <4 x i8> %a to <4 x i32>
%or = or <4 x i32> %s1, <i32 1, i32 2, i32 3, i32 4>
ret <4 x i32> %or
}
define <4 x i32> @or_sext_v4i8_to_v4i32_minus_constant(<4 x i8> %a) {
; CHECK-LABEL: @or_sext_v4i8_to_v4i32_minus_constant(
; CHECK-NEXT: [[S1:%.*]] = sext <4 x i8> [[A:%.*]] to <4 x i32>
; CHECK-NEXT: [[OR:%.*]] = or <4 x i32> [[S1]], <i32 -1, i32 -2, i32 -3, i32 -4>
; CHECK-NEXT: ret <4 x i32> [[OR]]
;
%s1 = sext <4 x i8> %a to <4 x i32>
%or = or <4 x i32> %s1, <i32 -1, i32 -2, i32 -3, i32 -4>
ret <4 x i32> %or
}
define <4 x i32> @or_sext_v4i8_to_v4i32_constant_with_loss(<4 x i8> %a) {
; CHECK-LABEL: @or_sext_v4i8_to_v4i32_constant_with_loss(
; CHECK-NEXT: [[Z1:%.*]] = sext <4 x i8> [[A:%.*]] to <4 x i32>
; CHECK-NEXT: [[OR:%.*]] = or <4 x i32> [[Z1]], <i32 -10000, i32 2, i32 3, i32 4>
; CHECK-NEXT: ret <4 x i32> [[OR]]
;
%z1 = sext <4 x i8> %a to <4 x i32>
%or = or <4 x i32> %z1, <i32 -10000, i32 2, i32 3, i32 4>
ret <4 x i32> %or
}
; Test truncate with flag preservation and one constant
define <4 x i16> @and_trunc_nuw_nsw_constant(<4 x i32> %a) {
; CHECK-LABEL: @and_trunc_nuw_nsw_constant(
; CHECK-NEXT: [[T1:%.*]] = trunc nuw nsw <4 x i32> [[A:%.*]] to <4 x i16>
; CHECK-NEXT: [[AND:%.*]] = and <4 x i16> [[T1]], <i16 1, i16 2, i16 3, i16 4>
; CHECK-NEXT: ret <4 x i16> [[AND]]
;
%t1 = trunc nuw nsw <4 x i32> %a to <4 x i16>
%and = and <4 x i16> %t1, <i16 1, i16 2, i16 3, i16 4>
ret <4 x i16> %and
}
define <4 x i8> @and_trunc_nuw_nsw_minus_constant(<4 x i32> %a) {
; CHECK-LABEL: @and_trunc_nuw_nsw_minus_constant(
; CHECK-NEXT: [[T1:%.*]] = trunc nuw nsw <4 x i32> [[A:%.*]] to <4 x i8>
; CHECK-NEXT: [[AND:%.*]] = and <4 x i8> [[T1]], <i8 -16, i8 -15, i8 -14, i8 -13>
; CHECK-NEXT: ret <4 x i8> [[AND]]
;
%t1 = trunc nuw nsw <4 x i32> %a to <4 x i8>
%and = and <4 x i8> %t1, <i8 240, i8 241, i8 242, i8 243>
ret <4 x i8> %and
}
define <4 x i8> @and_trunc_nuw_nsw_multiconstant(<4 x i32> %a) {
; CHECK-LABEL: @and_trunc_nuw_nsw_multiconstant(
; CHECK-NEXT: [[T1:%.*]] = trunc nuw nsw <4 x i32> [[A:%.*]] to <4 x i8>
; CHECK-NEXT: [[AND:%.*]] = and <4 x i8> [[T1]], <i8 -16, i8 1, i8 -14, i8 3>
; CHECK-NEXT: ret <4 x i8> [[AND]]
;
%t1 = trunc nuw nsw <4 x i32> %a to <4 x i8>
%and = and <4 x i8> %t1, <i8 240, i8 1, i8 242, i8 3>
ret <4 x i8> %and
}
; Test sign extend with nneg flag and one constant
define <4 x i32> @or_zext_nneg_constant(<4 x i16> %a) {
; CHECK-LABEL: @or_zext_nneg_constant(
; CHECK-NEXT: [[Z1:%.*]] = zext nneg <4 x i16> [[A:%.*]] to <4 x i32>
; CHECK-NEXT: [[OR:%.*]] = or <4 x i32> [[Z1]], <i32 1, i32 2, i32 3, i32 4>
; CHECK-NEXT: ret <4 x i32> [[OR]]
;
%z1 = zext nneg <4 x i16> %a to <4 x i32>
%or = or <4 x i32> %z1, <i32 1, i32 2, i32 3, i32 4>
ret <4 x i32> %or
}
define <4 x i32> @or_zext_nneg_minus_constant(<4 x i8> %a) {
; CHECK-LABEL: @or_zext_nneg_minus_constant(
; CHECK-NEXT: [[Z1:%.*]] = zext nneg <4 x i8> [[A:%.*]] to <4 x i32>
; CHECK-NEXT: [[OR:%.*]] = or <4 x i32> [[Z1]], <i32 240, i32 241, i32 242, i32 243>
; CHECK-NEXT: ret <4 x i32> [[OR]]
;
%z1 = zext nneg <4 x i8> %a to <4 x i32>
%or = or <4 x i32> %z1, <i32 240, i32 241, i32 242, i32 243>
ret <4 x i32> %or
}
define <4 x i32> @or_zext_nneg_multiconstant(<4 x i8> %a) {
; CHECK-LABEL: @or_zext_nneg_multiconstant(
; CHECK-NEXT: [[Z1:%.*]] = zext nneg <4 x i8> [[A:%.*]] to <4 x i32>
; CHECK-NEXT: [[OR:%.*]] = or <4 x i32> [[Z1]], <i32 240, i32 1, i32 242, i32 3>
; CHECK-NEXT: ret <4 x i32> [[OR]]
;
%z1 = zext nneg <4 x i8> %a to <4 x i32>
%or = or <4 x i32> %z1, <i32 240, i32 1, i32 242, i32 3>
ret <4 x i32> %or
}