blob: 565e2436096a9ed9ac086b09c009b641f754e998 [file]
//===- SLPCompatibilityAnalysis.cpp - SLP same-opcode helpers -------------===//
//
// 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 "SLPCompatibilityAnalysis.h"
#include "SLPUtils.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Value.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ErrorHandling.h"
#include <utility>
using namespace llvm;
namespace llvm::slpvectorizer {
bool isValidForAlternation(unsigned Opcode) {
return !Instruction::isIntDivRem(Opcode);
}
std::pair<Constant *, unsigned>
BinOpSameOpcodeHelper::isBinOpWithConstant(const Instruction *I) {
[[maybe_unused]] unsigned Opcode = I->getOpcode();
assert(binary_search(SupportedOp, Opcode) && "Unsupported opcode.");
(void)SupportedOp;
auto *BinOp = cast<BinaryOperator>(I);
auto GetConstant = [](Value *V) -> Constant * {
if (auto *CI = dyn_cast<ConstantInt>(V))
return CI;
return dyn_cast<ConstantFP>(V);
};
if (Constant *C = GetConstant(BinOp->getOperand(1)))
return {C, 1};
if (!isCommutative(I))
return {nullptr, 0};
if (Constant *C = GetConstant(BinOp->getOperand(0)))
return {C, 0};
return {nullptr, 0};
}
bool BinOpSameOpcodeHelper::InterchangeableInfo::trySet(
MaskType OpcodeInMaskForm, MaskType InterchangeableMask) {
if (Mask & InterchangeableMask) {
SeenBefore |= OpcodeInMaskForm;
Mask &= InterchangeableMask;
return true;
}
return false;
}
unsigned BinOpSameOpcodeHelper::InterchangeableInfo::getOpcode() const {
MaskType Candidate = Mask & SeenBefore;
if (Candidate & MainOpBIT)
return I->getOpcode();
if (Candidate & ShlBIT)
return Instruction::Shl;
if (Candidate & AShrBIT)
return Instruction::AShr;
if (Candidate & MulBIT)
return Instruction::Mul;
if (Candidate & AddBIT)
return Instruction::Add;
if (Candidate & SubBIT)
return Instruction::Sub;
if (Candidate & FAddBIT)
return Instruction::FAdd;
if (Candidate & FSubBIT)
return Instruction::FSub;
if (Candidate & AndBIT)
return Instruction::And;
if (Candidate & OrBIT)
return Instruction::Or;
if (Candidate & XorBIT)
return Instruction::Xor;
llvm_unreachable("Cannot find interchangeable instruction.");
}
bool BinOpSameOpcodeHelper::InterchangeableInfo::hasCandidateOpcode(
unsigned Opcode) const {
MaskType Candidate = Mask & SeenBefore;
switch (Opcode) {
case Instruction::Shl:
return Candidate & ShlBIT;
case Instruction::AShr:
return Candidate & AShrBIT;
case Instruction::Mul:
return Candidate & MulBIT;
case Instruction::Add:
return Candidate & AddBIT;
case Instruction::Sub:
return Candidate & SubBIT;
case Instruction::And:
return Candidate & AndBIT;
case Instruction::Or:
return Candidate & OrBIT;
case Instruction::Xor:
return Candidate & XorBIT;
case Instruction::FAdd:
return Candidate & FAddBIT;
case Instruction::FSub:
return Candidate & FSubBIT;
case Instruction::LShr:
case Instruction::FMul:
case Instruction::SDiv:
case Instruction::UDiv:
case Instruction::FDiv:
case Instruction::SRem:
case Instruction::URem:
case Instruction::FRem:
return false;
default:
break;
}
llvm_unreachable("Cannot find interchangeable instruction.");
}
SmallVector<Value *> BinOpSameOpcodeHelper::InterchangeableInfo::getOperand(
const Instruction *To) const {
unsigned ToOpcode = To->getOpcode();
unsigned FromOpcode = I->getOpcode();
if (FromOpcode == ToOpcode)
return SmallVector<Value *>(I->operands());
assert(binary_search(SupportedOp, ToOpcode) && "Unsupported opcode.");
auto [C, Pos] = isBinOpWithConstant(I);
Type *RHSType = I->getOperand(Pos)->getType();
Constant *RHS;
if (auto *CFP = dyn_cast<ConstantFP>(C)) {
// fsub(x, c) == fadd(x, -c) for every FP constant c, since IEEE 754
// defines subtraction as addition of the negated operand.
assert(is_contained({Instruction::FAdd, Instruction::FSub}, ToOpcode) &&
"Cannot convert the instruction.");
RHS = ConstantFP::get(RHSType, -CFP->getValueAPF());
} else {
auto *CI = cast<ConstantInt>(C);
const APInt &FromCIValue = CI->getValue();
unsigned FromCIValueBitWidth = FromCIValue.getBitWidth();
switch (FromOpcode) {
case Instruction::Shl:
if (ToOpcode == Instruction::Add && FromCIValue.isOne())
return {I->getOperand(0), I->getOperand(0)};
if (ToOpcode == Instruction::Mul) {
RHS = ConstantInt::get(RHSType,
APInt::getOneBitSet(FromCIValueBitWidth,
FromCIValue.getZExtValue()));
} else {
assert(FromCIValue.isZero() && "Cannot convert the instruction.");
RHS = ConstantExpr::getBinOpIdentity(ToOpcode, RHSType,
/*AllowRHSConstant=*/true);
}
break;
case Instruction::Mul:
assert(FromCIValue.isPowerOf2() && "Cannot convert the instruction.");
if (ToOpcode == Instruction::Shl) {
RHS = ConstantInt::get(
RHSType, APInt(FromCIValueBitWidth, FromCIValue.logBase2()));
} else {
assert(FromCIValue.isOne() && "Cannot convert the instruction.");
RHS = ConstantExpr::getBinOpIdentity(ToOpcode, RHSType,
/*AllowRHSConstant=*/true);
}
break;
case Instruction::Add:
case Instruction::Sub:
if (FromCIValue.isZero()) {
RHS = ConstantExpr::getBinOpIdentity(ToOpcode, RHSType,
/*AllowRHSConstant=*/true);
} else {
assert(is_contained({Instruction::Add, Instruction::Sub}, ToOpcode) &&
"Cannot convert the instruction.");
APInt NegatedVal = APInt(FromCIValue);
NegatedVal.negate();
RHS = ConstantInt::get(RHSType, NegatedVal);
}
break;
case Instruction::And:
assert(FromCIValue.isAllOnes() && "Cannot convert the instruction.");
RHS = ConstantExpr::getBinOpIdentity(ToOpcode, RHSType,
/*AllowRHSConstant=*/true);
break;
default:
assert(FromCIValue.isZero() && "Cannot convert the instruction.");
RHS = ConstantExpr::getBinOpIdentity(ToOpcode, RHSType,
/*AllowRHSConstant=*/true);
break;
}
}
Value *LHS = I->getOperand(1 - Pos);
// If the target opcode is non-commutative (e.g., shl, sub),
// force the variable to the left and the constant to the right.
if (Pos == 1 || !Instruction::isCommutative(ToOpcode))
return SmallVector<Value *>({LHS, RHS});
return SmallVector<Value *>({RHS, LHS});
}
bool BinOpSameOpcodeHelper::isValidForAlternation(const Instruction *I) const {
return slpvectorizer::isValidForAlternation(MainOp.I->getOpcode()) &&
slpvectorizer::isValidForAlternation(I->getOpcode());
}
bool BinOpSameOpcodeHelper::initializeAltOp(const Instruction *I) {
if (AltOp.I)
return true;
if (!isValidForAlternation(I))
return false;
AltOp.I = I;
return true;
}
bool BinOpSameOpcodeHelper::add(const Instruction *I) {
assert(isa<BinaryOperator>(I) &&
"BinOpSameOpcodeHelper only accepts BinaryOperator.");
unsigned Opcode = I->getOpcode();
MaskType OpcodeInMaskForm;
// Prefer Shl, AShr, Mul, Add, Sub, And, Or, Xor, FAdd and FSub over
// MainOp.
switch (Opcode) {
case Instruction::Shl:
OpcodeInMaskForm = ShlBIT;
break;
case Instruction::AShr:
OpcodeInMaskForm = AShrBIT;
break;
case Instruction::Mul:
OpcodeInMaskForm = MulBIT;
break;
case Instruction::Add:
OpcodeInMaskForm = AddBIT;
break;
case Instruction::Sub:
OpcodeInMaskForm = SubBIT;
break;
case Instruction::And:
OpcodeInMaskForm = AndBIT;
break;
case Instruction::Or:
OpcodeInMaskForm = OrBIT;
break;
case Instruction::Xor:
OpcodeInMaskForm = XorBIT;
break;
case Instruction::FAdd:
OpcodeInMaskForm = FAddBIT;
break;
case Instruction::FSub:
OpcodeInMaskForm = FSubBIT;
break;
default:
return MainOp.equal(Opcode) || (initializeAltOp(I) && AltOp.equal(Opcode));
}
MaskType InterchangeableMask = OpcodeInMaskForm;
auto [C, Pos] = isBinOpWithConstant(I);
if (auto *CI = dyn_cast_or_null<ConstantInt>(C)) {
constexpr MaskType CanBeAll =
XorBIT | OrBIT | AndBIT | SubBIT | AddBIT | MulBIT | AShrBIT | ShlBIT;
const APInt &CIValue = CI->getValue();
switch (Opcode) {
case Instruction::Shl:
if (CIValue.ult(CIValue.getBitWidth()))
InterchangeableMask = CIValue.isZero() ? CanBeAll : MulBIT | ShlBIT;
if (CIValue.isOne())
InterchangeableMask |= AddBIT;
break;
case Instruction::Mul:
if (CIValue.isOne()) {
InterchangeableMask = CanBeAll;
break;
}
if (CIValue.isPowerOf2())
InterchangeableMask = MulBIT | ShlBIT;
break;
case Instruction::Add:
case Instruction::Sub:
InterchangeableMask = CIValue.isZero() ? CanBeAll : SubBIT | AddBIT;
break;
case Instruction::And:
if (CIValue.isAllOnes())
InterchangeableMask = CanBeAll;
break;
case Instruction::Xor:
if (CIValue.isZero())
InterchangeableMask = XorBIT | OrBIT | SubBIT | AddBIT;
break;
default:
if (CIValue.isZero())
InterchangeableMask = CanBeAll;
break;
}
} else if (C && Pos == 1) {
// FAdd/FSub with a constant RHS: negating the constant always
// converts one into the other, so no value check is needed. A
// constant LHS (Pos == 0, e.g. "0.0 - x") is excluded: unlike a
// constant RHS, it cannot be moved to the other opcode without also
// swapping the variable operand, which would misalign it against
// lanes that keep their native opcode (their variable operand stays
// on the other side).
InterchangeableMask = FSubBIT | FAddBIT;
}
return MainOp.trySet(OpcodeInMaskForm, InterchangeableMask) ||
(initializeAltOp(I) &&
AltOp.trySet(OpcodeInMaskForm, InterchangeableMask));
}
} // namespace llvm::slpvectorizer