[llvm] [SLP] Make getSameOpcode support interchangeable instructions. (PR #127450)
Alexey Bataev via llvm-commits
llvm-commits at lists.llvm.org
Mon Feb 24 06:40:33 PST 2025
================
@@ -810,6 +810,285 @@ static std::optional<unsigned> getExtractIndex(Instruction *E) {
namespace {
+/// Base class for representing instructions that can be interchanged with other
+/// equivalent forms. For example, multiplication by a power of 2 can be
+/// interchanged with a left shift.
+///
+/// Derived classes implement specific interchange patterns by overriding the
+/// virtual methods to define their interchange logic.
+///
+/// The class maintains a reference to the main instruction (MainOp) and
+/// provides methods to:
+/// - Check if another instruction is interchangeable (isSame)
+/// - Get the opcode for the interchangeable form (getOpcode)
+/// - Get the operands for the interchangeable form (getOperand)
+class InterchangeableInstruction {
+protected:
+ Instruction *const MainOp = nullptr;
+
+public:
+ InterchangeableInstruction(Instruction *MainOp) : MainOp(MainOp) {}
+ virtual bool isSame(Instruction *I) const {
+ return MainOp->getOpcode() == I->getOpcode();
+ }
+ virtual unsigned getOpcode() const { return MainOp->getOpcode(); }
+ virtual SmallVector<Value *> getOperand(Instruction *I) const {
+ assert(MainOp->getOpcode() == I->getOpcode() &&
+ "Cannot convert the instruction.");
+ return SmallVector<Value *>(MainOp->operands());
+ }
+ virtual ~InterchangeableInstruction() = default;
+};
+
+class InterchangeableBinOp final : public InterchangeableInstruction {
+ using MaskType = std::uint_fast8_t;
+ // Sort SupportedOp because it is used by binary_search.
+ constexpr static std::initializer_list<unsigned> SupportedOp = {
+ Instruction::Add, Instruction::Sub, Instruction::Mul, Instruction::Shl,
+ Instruction::AShr, Instruction::And, Instruction::Or, Instruction::Xor};
+ enum : MaskType {
+ SHL_BIT = 0b1,
+ AShr_BIT = 0b10,
+ Mul_BIT = 0b100,
+ Add_BIT = 0b1000,
+ Sub_BIT = 0b10000,
+ And_BIT = 0b100000,
+ Or_BIT = 0b1000000,
+ Xor_BIT = 0b10000000,
+ };
+ // The bit it sets represents whether MainOp can be converted to.
+ mutable MaskType Mask = Xor_BIT | Or_BIT | And_BIT | Sub_BIT | Add_BIT |
+ Mul_BIT | AShr_BIT | SHL_BIT;
+ // We cannot create an interchangeable instruction that does not exist in VL.
+ // For example, VL [x + 0, y * 1] can be converted to [x << 0, y << 0], but
+ // 'shl' does not exist in VL. In the end, we convert VL to [x * 1, y * 1].
+ // SeenBefore is used to know what operations have been seen before.
+ mutable MaskType SeenBefore = 0;
+
+ /// Return a non-nullptr if either operand of I is a ConstantInt.
+ static std::pair<ConstantInt *, unsigned>
+ isBinOpWithConstantInt(Instruction *I) {
+ unsigned Opcode = I->getOpcode();
+ unsigned Pos = 1;
+ Constant *C;
+ if (!match(I, m_BinOp(m_Value(), m_Constant(C)))) {
+ if (Opcode == Instruction::Sub || Opcode == Instruction::Shl ||
+ Opcode == Instruction::AShr)
+ return std::make_pair(nullptr, Pos);
+ if (!match(I, m_BinOp(m_Constant(C), m_Value())))
+ return std::make_pair(nullptr, Pos);
+ Pos = 0;
+ }
+ if (auto *CI = dyn_cast<ConstantInt>(C))
+ return std::make_pair(CI, Pos);
+ if (auto *CDV = dyn_cast<ConstantDataVector>(C)) {
+ if (auto *CI = dyn_cast_if_present<ConstantInt>(CDV->getSplatValue()))
+ return std::make_pair(CI, Pos);
+ }
+ return std::make_pair(nullptr, Pos);
+ }
+
+ static MaskType opcodeToMask(unsigned Opcode) {
+ switch (Opcode) {
+ case Instruction::Shl:
+ return SHL_BIT;
+ case Instruction::AShr:
+ return AShr_BIT;
+ case Instruction::Mul:
+ return Mul_BIT;
+ case Instruction::Add:
+ return Add_BIT;
+ case Instruction::Sub:
+ return Sub_BIT;
+ case Instruction::And:
+ return And_BIT;
+ case Instruction::Or:
+ return Or_BIT;
+ case Instruction::Xor:
+ return Xor_BIT;
+ }
+ llvm_unreachable("Unsupported opcode.");
+ }
+
+ bool tryAnd(MaskType X) const {
+ if (Mask & X) {
+ Mask &= X;
+ return true;
+ }
+ return false;
+ }
+
+public:
+ using InterchangeableInstruction::InterchangeableInstruction;
+ bool isSame(Instruction *I) const override {
+ unsigned Opcode = I->getOpcode();
+ if (!binary_search(SupportedOp, Opcode))
+ return false;
+ SeenBefore |= opcodeToMask(Opcode);
+ ConstantInt *CI = isBinOpWithConstantInt(I).first;
+ if (CI) {
+ const APInt &CIValue = CI->getValue();
+ switch (Opcode) {
+ case Instruction::Shl:
+ if (CIValue.isZero())
+ return true;
+ return tryAnd(Mul_BIT | SHL_BIT);
+ case Instruction::Mul:
+ if (CIValue.isOne())
+ return true;
+ if (CIValue.isPowerOf2())
+ return tryAnd(Mul_BIT | SHL_BIT);
+ break;
+ case Instruction::And:
+ if (CIValue.isAllOnes())
+ return true;
+ break;
+ default:
+ if (CIValue.isZero())
+ return true;
+ break;
+ }
+ }
+ return tryAnd(opcodeToMask(Opcode));
+ }
+ unsigned getOpcode() const override {
+ MaskType Candidate = Mask & SeenBefore;
+ if (Candidate & SHL_BIT)
+ return Instruction::Shl;
+ if (Candidate & AShr_BIT)
+ return Instruction::AShr;
+ if (Candidate & Mul_BIT)
+ return Instruction::Mul;
+ if (Candidate & Add_BIT)
+ return Instruction::Add;
+ if (Candidate & Sub_BIT)
+ return Instruction::Sub;
+ if (Candidate & And_BIT)
+ return Instruction::And;
+ if (Candidate & Or_BIT)
+ return Instruction::Or;
+ if (Candidate & Xor_BIT)
+ return Instruction::Xor;
+ llvm_unreachable("Cannot find interchangeable instruction.");
+ }
+ SmallVector<Value *> getOperand(Instruction *I) const override {
+ unsigned ToOpcode = I->getOpcode();
+ assert(binary_search(SupportedOp, ToOpcode) && "Unsupported opcode.");
+ unsigned FromOpcode = MainOp->getOpcode();
+ if (FromOpcode == ToOpcode)
+ return SmallVector<Value *>(MainOp->operands());
+ auto [CI, Pos] = isBinOpWithConstantInt(MainOp);
+ const APInt &FromCIValue = CI->getValue();
+ unsigned FromCIValueBitWidth = FromCIValue.getBitWidth();
+ APInt ToCIValue;
+ switch (FromOpcode) {
+ case Instruction::Shl:
+ if (ToOpcode == Instruction::Mul) {
+ ToCIValue = APInt::getOneBitSet(FromCIValueBitWidth,
+ FromCIValue.getZExtValue());
+ } else {
+ assert(FromCIValue.isZero() && "Cannot convert the instruction.");
+ ToCIValue = ToOpcode == Instruction::And
+ ? APInt::getAllOnes(FromCIValueBitWidth)
+ : APInt::getZero(FromCIValueBitWidth);
+ }
+ break;
+ case Instruction::Mul:
+ assert(FromCIValue.isPowerOf2() && "Cannot convert the instruction.");
+ if (ToOpcode == Instruction::Shl) {
+ ToCIValue = APInt(FromCIValueBitWidth, FromCIValue.logBase2());
+ } else {
+ assert(FromCIValue.isOne() && "Cannot convert the instruction.");
+ ToCIValue = ToOpcode == Instruction::And
+ ? APInt::getAllOnes(FromCIValueBitWidth)
+ : APInt::getZero(FromCIValueBitWidth);
+ }
+ break;
+ case Instruction::And:
+ assert(FromCIValue.isAllOnes() && "Cannot convert the instruction.");
+ ToCIValue = ToOpcode == Instruction::Mul
+ ? APInt::getOneBitSet(FromCIValueBitWidth, 0)
+ : APInt::getZero(FromCIValueBitWidth);
+ break;
+ default:
+ ToCIValue = APInt::getZero(FromCIValueBitWidth);
+ break;
+ }
+ Value *LHS = MainOp->getOperand(1 - Pos);
+ Constant *RHS =
+ ConstantInt::get(MainOp->getOperand(Pos)->getType(), ToCIValue);
+ if (Pos == 1)
+ return SmallVector<Value *>({LHS, RHS});
+ return SmallVector<Value *>({RHS, LHS});
+ }
+};
+
+static SmallVector<std::unique_ptr<InterchangeableInstruction>>
+getInterchangeableInstruction(Instruction *MainOp) {
+ SmallVector<std::unique_ptr<InterchangeableInstruction>> Candidate;
+ Candidate.push_back(std::make_unique<InterchangeableInstruction>(MainOp));
+ if (MainOp->isBinaryOp())
+ Candidate.push_back(std::make_unique<InterchangeableBinOp>(MainOp));
+ return Candidate;
+}
+
+static bool getInterchangeableInstruction(
+ SmallVector<std::unique_ptr<InterchangeableInstruction>> &Candidate,
+ Instruction *I) {
+ auto Iter = std::stable_partition(
----------------
alexey-bataev wrote:
```suggestion
auto *Iter = std::stable_partition(
```
https://github.com/llvm/llvm-project/pull/127450
More information about the llvm-commits
mailing list