Skip to content

Conversation

@pfusik
Copy link
Contributor

@pfusik pfusik commented Mar 17, 2025

Undoes a negation being hoisted out of a loop, so that it can be fold
into an inverted bitwise operation in the loop.

Implements #108840 on RISC-V

@llvmbot
Copy link
Member

llvmbot commented Mar 17, 2025

@llvm/pr-subscribers-backend-risc-v

Author: Piotr Fusik (pfusik)

Changes

Undoes a negation being hoisted out of a loop, so that it can be fold
into an inverted bitwise operation in the loop.

Implements #108840 on RISC-V


Patch is 50.78 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/131632.diff

4 Files Affected:

  • (modified) llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp (+33)
  • (modified) llvm/test/CodeGen/RISCV/rv32zbb-zbkb.ll (+354)
  • (modified) llvm/test/CodeGen/RISCV/rv64zbb-zbkb.ll (+336)
  • (modified) llvm/test/CodeGen/RISCV/rvv/vandn-sdnode.ll (+663-4)
diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
index 11a658758a9cb..1060093043278 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
@@ -2779,6 +2779,39 @@ bool RISCVTTIImpl::isProfitableToSinkOperands(
     Instruction *I, SmallVectorImpl<Use *> &Ops) const {
   using namespace llvm::PatternMatch;
 
+  if (I->isBitwiseLogicOp()) {
+    if (!I->getType()->isVectorTy()) {
+      if (ST->hasStdExtZbb() || ST->hasStdExtZbkb()) {
+        for (auto &Op : I->operands()) {
+          // (and/or/xor X, (not Y)) -> (andn/orn/xnor X, Y)
+          if (match(Op.get(), m_Not(m_Value()))) {
+            Ops.push_back(&Op);
+            return true;
+          }
+        }
+      }
+    } else if (I->getOpcode() == Instruction::And && ST->hasStdExtZvkb()) {
+      for (auto &Op : I->operands()) {
+        // (and X, (not Y)) -> (vandn.vv X, Y)
+        if (match(Op.get(), m_Not(m_Value()))) {
+          Ops.push_back(&Op);
+          return true;
+        }
+        // (and X, (splat (not Y))) -> (vandn.vx X, Y)
+        if (match(Op.get(), m_Shuffle(m_InsertElt(m_Value(), m_Not(m_Value()),
+                                                  m_ZeroInt()),
+                                      m_Value(), m_ZeroMask()))) {
+          Use &InsertElt = cast<Instruction>(Op)->getOperandUse(0);
+          Use &Not = cast<Instruction>(InsertElt)->getOperandUse(1);
+          Ops.push_back(&Not);
+          Ops.push_back(&InsertElt);
+          Ops.push_back(&Op);
+          return true;
+        }
+      }
+    }
+  }
+
   if (!I->getType()->isVectorTy() || !ST->hasVInstructions())
     return false;
 
diff --git a/llvm/test/CodeGen/RISCV/rv32zbb-zbkb.ll b/llvm/test/CodeGen/RISCV/rv32zbb-zbkb.ll
index b6344f88cddaa..88bb19f499ab5 100644
--- a/llvm/test/CodeGen/RISCV/rv32zbb-zbkb.ll
+++ b/llvm/test/CodeGen/RISCV/rv32zbb-zbkb.ll
@@ -438,3 +438,357 @@ define i1 @andn_snez_i64(i64 %a, i64 %b) nounwind {
   %cmpeq = icmp ne i64 %and, %b
   ret i1 %cmpeq
 }
+
+define i32 @and_hoisted_not_i32(i32 %x, i32 %m, i1 zeroext %cond) {
+; RV32I-LABEL: and_hoisted_not_i32:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    beqz a2, .LBB24_2
+; RV32I-NEXT:  # %bb.1: # %mask
+; RV32I-NEXT:    not a1, a1
+; RV32I-NEXT:    and a0, a1, a0
+; RV32I-NEXT:  .LBB24_2: # %identity
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: and_hoisted_not_i32:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    beqz a2, .LBB24_2
+; RV32ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV32ZBB-ZBKB-NEXT:    andn a0, a0, a1
+; RV32ZBB-ZBKB-NEXT:  .LBB24_2: # %identity
+; RV32ZBB-ZBKB-NEXT:    ret
+  %a = xor i32 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = and i32 %a, %x
+  ret i32 %masked
+
+identity:
+  ret i32 %x
+}
+
+define i32 @and_hoisted_not_i32_swapped(i32 %x, i32 %m, i1 zeroext %cond) {
+; RV32I-LABEL: and_hoisted_not_i32_swapped:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    beqz a2, .LBB25_2
+; RV32I-NEXT:  # %bb.1: # %mask
+; RV32I-NEXT:    not a1, a1
+; RV32I-NEXT:    and a0, a0, a1
+; RV32I-NEXT:  .LBB25_2: # %identity
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: and_hoisted_not_i32_swapped:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    beqz a2, .LBB25_2
+; RV32ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV32ZBB-ZBKB-NEXT:    andn a0, a0, a1
+; RV32ZBB-ZBKB-NEXT:  .LBB25_2: # %identity
+; RV32ZBB-ZBKB-NEXT:    ret
+  %a = xor i32 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = and i32 %x, %a
+  ret i32 %masked
+
+identity:
+  ret i32 %x
+}
+
+define i64 @and_hoisted_not_i64(i64 %x, i64 %m, i1 zeroext %cond) {
+; RV32I-LABEL: and_hoisted_not_i64:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    beqz a4, .LBB26_2
+; RV32I-NEXT:  # %bb.1: # %mask
+; RV32I-NEXT:    not a3, a3
+; RV32I-NEXT:    not a2, a2
+; RV32I-NEXT:    and a0, a2, a0
+; RV32I-NEXT:    and a1, a3, a1
+; RV32I-NEXT:  .LBB26_2: # %identity
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: and_hoisted_not_i64:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    beqz a4, .LBB26_2
+; RV32ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV32ZBB-ZBKB-NEXT:    andn a0, a0, a2
+; RV32ZBB-ZBKB-NEXT:    andn a1, a1, a3
+; RV32ZBB-ZBKB-NEXT:  .LBB26_2: # %identity
+; RV32ZBB-ZBKB-NEXT:    ret
+  %a = xor i64 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = and i64 %a, %x
+  ret i64 %masked
+
+identity:
+  ret i64 %x
+}
+
+define i64 @and_hoisted_not_i64_swapped(i64 %x, i64 %m, i1 zeroext %cond) {
+; RV32I-LABEL: and_hoisted_not_i64_swapped:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    beqz a4, .LBB27_2
+; RV32I-NEXT:  # %bb.1: # %mask
+; RV32I-NEXT:    not a3, a3
+; RV32I-NEXT:    not a2, a2
+; RV32I-NEXT:    and a0, a0, a2
+; RV32I-NEXT:    and a1, a1, a3
+; RV32I-NEXT:  .LBB27_2: # %identity
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: and_hoisted_not_i64_swapped:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    beqz a4, .LBB27_2
+; RV32ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV32ZBB-ZBKB-NEXT:    andn a0, a0, a2
+; RV32ZBB-ZBKB-NEXT:    andn a1, a1, a3
+; RV32ZBB-ZBKB-NEXT:  .LBB27_2: # %identity
+; RV32ZBB-ZBKB-NEXT:    ret
+  %a = xor i64 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = and i64 %x, %a
+  ret i64 %masked
+
+identity:
+  ret i64 %x
+}
+
+define i32 @or_hoisted_not_i32(i32 %x, i32 %m, i1 zeroext %cond) {
+; RV32I-LABEL: or_hoisted_not_i32:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    beqz a2, .LBB28_2
+; RV32I-NEXT:  # %bb.1: # %mask
+; RV32I-NEXT:    not a1, a1
+; RV32I-NEXT:    or a0, a1, a0
+; RV32I-NEXT:  .LBB28_2: # %identity
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: or_hoisted_not_i32:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    beqz a2, .LBB28_2
+; RV32ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV32ZBB-ZBKB-NEXT:    orn a0, a0, a1
+; RV32ZBB-ZBKB-NEXT:  .LBB28_2: # %identity
+; RV32ZBB-ZBKB-NEXT:    ret
+  %a = xor i32 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = or i32 %a, %x
+  ret i32 %masked
+
+identity:
+  ret i32 %x
+}
+
+define i32 @or_hoisted_not_i32_swapped(i32 %x, i32 %m, i1 zeroext %cond) {
+; RV32I-LABEL: or_hoisted_not_i32_swapped:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    beqz a2, .LBB29_2
+; RV32I-NEXT:  # %bb.1: # %mask
+; RV32I-NEXT:    not a1, a1
+; RV32I-NEXT:    or a0, a0, a1
+; RV32I-NEXT:  .LBB29_2: # %identity
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: or_hoisted_not_i32_swapped:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    beqz a2, .LBB29_2
+; RV32ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV32ZBB-ZBKB-NEXT:    orn a0, a0, a1
+; RV32ZBB-ZBKB-NEXT:  .LBB29_2: # %identity
+; RV32ZBB-ZBKB-NEXT:    ret
+  %a = xor i32 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = or i32 %x, %a
+  ret i32 %masked
+
+identity:
+  ret i32 %x
+}
+
+define i64 @or_hoisted_not_i64(i64 %x, i64 %m, i1 zeroext %cond) {
+; RV32I-LABEL: or_hoisted_not_i64:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    beqz a4, .LBB30_2
+; RV32I-NEXT:  # %bb.1: # %mask
+; RV32I-NEXT:    not a3, a3
+; RV32I-NEXT:    not a2, a2
+; RV32I-NEXT:    or a0, a2, a0
+; RV32I-NEXT:    or a1, a3, a1
+; RV32I-NEXT:  .LBB30_2: # %identity
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: or_hoisted_not_i64:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    beqz a4, .LBB30_2
+; RV32ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV32ZBB-ZBKB-NEXT:    orn a0, a0, a2
+; RV32ZBB-ZBKB-NEXT:    orn a1, a1, a3
+; RV32ZBB-ZBKB-NEXT:  .LBB30_2: # %identity
+; RV32ZBB-ZBKB-NEXT:    ret
+  %a = xor i64 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = or i64 %a, %x
+  ret i64 %masked
+
+identity:
+  ret i64 %x
+}
+
+define i64 @or_hoisted_not_i64_swapped(i64 %x, i64 %m, i1 zeroext %cond) {
+; RV32I-LABEL: or_hoisted_not_i64_swapped:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    beqz a4, .LBB31_2
+; RV32I-NEXT:  # %bb.1: # %mask
+; RV32I-NEXT:    not a3, a3
+; RV32I-NEXT:    not a2, a2
+; RV32I-NEXT:    or a0, a0, a2
+; RV32I-NEXT:    or a1, a1, a3
+; RV32I-NEXT:  .LBB31_2: # %identity
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: or_hoisted_not_i64_swapped:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    beqz a4, .LBB31_2
+; RV32ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV32ZBB-ZBKB-NEXT:    orn a0, a0, a2
+; RV32ZBB-ZBKB-NEXT:    orn a1, a1, a3
+; RV32ZBB-ZBKB-NEXT:  .LBB31_2: # %identity
+; RV32ZBB-ZBKB-NEXT:    ret
+  %a = xor i64 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = or i64 %x, %a
+  ret i64 %masked
+
+identity:
+  ret i64 %x
+}
+
+define i32 @xor_hoisted_not_i32(i32 %x, i32 %m, i1 zeroext %cond) {
+; RV32I-LABEL: xor_hoisted_not_i32:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    beqz a2, .LBB32_2
+; RV32I-NEXT:  # %bb.1: # %mask
+; RV32I-NEXT:    not a1, a1
+; RV32I-NEXT:    xor a0, a1, a0
+; RV32I-NEXT:  .LBB32_2: # %identity
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: xor_hoisted_not_i32:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    beqz a2, .LBB32_2
+; RV32ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV32ZBB-ZBKB-NEXT:    xnor a0, a1, a0
+; RV32ZBB-ZBKB-NEXT:  .LBB32_2: # %identity
+; RV32ZBB-ZBKB-NEXT:    ret
+  %a = xor i32 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = xor i32 %a, %x
+  ret i32 %masked
+
+identity:
+  ret i32 %x
+}
+
+define i32 @xor_hoisted_not_i32_swapped(i32 %x, i32 %m, i1 zeroext %cond) {
+; RV32I-LABEL: xor_hoisted_not_i32_swapped:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    beqz a2, .LBB33_2
+; RV32I-NEXT:  # %bb.1: # %mask
+; RV32I-NEXT:    not a1, a1
+; RV32I-NEXT:    xor a0, a0, a1
+; RV32I-NEXT:  .LBB33_2: # %identity
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: xor_hoisted_not_i32_swapped:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    beqz a2, .LBB33_2
+; RV32ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV32ZBB-ZBKB-NEXT:    xnor a0, a1, a0
+; RV32ZBB-ZBKB-NEXT:  .LBB33_2: # %identity
+; RV32ZBB-ZBKB-NEXT:    ret
+  %a = xor i32 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = xor i32 %x, %a
+  ret i32 %masked
+
+identity:
+  ret i32 %x
+}
+
+define i64 @xor_hoisted_not_i64(i64 %x, i64 %m, i1 zeroext %cond) {
+; RV32I-LABEL: xor_hoisted_not_i64:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    beqz a4, .LBB34_2
+; RV32I-NEXT:  # %bb.1: # %mask
+; RV32I-NEXT:    not a3, a3
+; RV32I-NEXT:    not a2, a2
+; RV32I-NEXT:    xor a0, a2, a0
+; RV32I-NEXT:    xor a1, a3, a1
+; RV32I-NEXT:  .LBB34_2: # %identity
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: xor_hoisted_not_i64:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    beqz a4, .LBB34_2
+; RV32ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV32ZBB-ZBKB-NEXT:    xnor a0, a2, a0
+; RV32ZBB-ZBKB-NEXT:    xnor a1, a3, a1
+; RV32ZBB-ZBKB-NEXT:  .LBB34_2: # %identity
+; RV32ZBB-ZBKB-NEXT:    ret
+  %a = xor i64 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = xor i64 %a, %x
+  ret i64 %masked
+
+identity:
+  ret i64 %x
+}
+
+define i64 @xor_hoisted_not_i64_swapped(i64 %x, i64 %m, i1 zeroext %cond) {
+; RV32I-LABEL: xor_hoisted_not_i64_swapped:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:    beqz a4, .LBB35_2
+; RV32I-NEXT:  # %bb.1: # %mask
+; RV32I-NEXT:    not a3, a3
+; RV32I-NEXT:    not a2, a2
+; RV32I-NEXT:    xor a0, a0, a2
+; RV32I-NEXT:    xor a1, a1, a3
+; RV32I-NEXT:  .LBB35_2: # %identity
+; RV32I-NEXT:    ret
+;
+; RV32ZBB-ZBKB-LABEL: xor_hoisted_not_i64_swapped:
+; RV32ZBB-ZBKB:       # %bb.0:
+; RV32ZBB-ZBKB-NEXT:    beqz a4, .LBB35_2
+; RV32ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV32ZBB-ZBKB-NEXT:    xnor a0, a2, a0
+; RV32ZBB-ZBKB-NEXT:    xnor a1, a3, a1
+; RV32ZBB-ZBKB-NEXT:  .LBB35_2: # %identity
+; RV32ZBB-ZBKB-NEXT:    ret
+  %a = xor i64 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = xor i64 %x, %a
+  ret i64 %masked
+
+identity:
+  ret i64 %x
+}
diff --git a/llvm/test/CodeGen/RISCV/rv64zbb-zbkb.ll b/llvm/test/CodeGen/RISCV/rv64zbb-zbkb.ll
index bf077364c9c7a..4ca637b788a45 100644
--- a/llvm/test/CodeGen/RISCV/rv64zbb-zbkb.ll
+++ b/llvm/test/CodeGen/RISCV/rv64zbb-zbkb.ll
@@ -554,3 +554,339 @@ define i1 @andn_snez_i64(i64 %a, i64 %b) nounwind {
   %cmpeq = icmp ne i64 %and, %b
   ret i1 %cmpeq
 }
+
+define i32 @and_hoisted_not_i32(i32 %x, i32 %m, i1 zeroext %cond) {
+; RV64I-LABEL: and_hoisted_not_i32:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    beqz a2, .LBB32_2
+; RV64I-NEXT:  # %bb.1: # %mask
+; RV64I-NEXT:    not a1, a1
+; RV64I-NEXT:    and a0, a1, a0
+; RV64I-NEXT:  .LBB32_2: # %identity
+; RV64I-NEXT:    ret
+;
+; RV64ZBB-ZBKB-LABEL: and_hoisted_not_i32:
+; RV64ZBB-ZBKB:       # %bb.0:
+; RV64ZBB-ZBKB-NEXT:    beqz a2, .LBB32_2
+; RV64ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV64ZBB-ZBKB-NEXT:    andn a0, a0, a1
+; RV64ZBB-ZBKB-NEXT:  .LBB32_2: # %identity
+; RV64ZBB-ZBKB-NEXT:    ret
+  %a = xor i32 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = and i32 %a, %x
+  ret i32 %masked
+
+identity:
+  ret i32 %x
+}
+
+define i32 @and_hoisted_not_i32_swapped(i32 %x, i32 %m, i1 zeroext %cond) {
+; RV64I-LABEL: and_hoisted_not_i32_swapped:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    beqz a2, .LBB33_2
+; RV64I-NEXT:  # %bb.1: # %mask
+; RV64I-NEXT:    not a1, a1
+; RV64I-NEXT:    and a0, a0, a1
+; RV64I-NEXT:  .LBB33_2: # %identity
+; RV64I-NEXT:    ret
+;
+; RV64ZBB-ZBKB-LABEL: and_hoisted_not_i32_swapped:
+; RV64ZBB-ZBKB:       # %bb.0:
+; RV64ZBB-ZBKB-NEXT:    beqz a2, .LBB33_2
+; RV64ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV64ZBB-ZBKB-NEXT:    andn a0, a0, a1
+; RV64ZBB-ZBKB-NEXT:  .LBB33_2: # %identity
+; RV64ZBB-ZBKB-NEXT:    ret
+  %a = xor i32 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = and i32 %x, %a
+  ret i32 %masked
+
+identity:
+  ret i32 %x
+}
+
+define i64 @and_hoisted_not_i64(i64 %x, i64 %m, i1 zeroext %cond) {
+; RV64I-LABEL: and_hoisted_not_i64:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    beqz a2, .LBB34_2
+; RV64I-NEXT:  # %bb.1: # %mask
+; RV64I-NEXT:    not a1, a1
+; RV64I-NEXT:    and a0, a1, a0
+; RV64I-NEXT:  .LBB34_2: # %identity
+; RV64I-NEXT:    ret
+;
+; RV64ZBB-ZBKB-LABEL: and_hoisted_not_i64:
+; RV64ZBB-ZBKB:       # %bb.0:
+; RV64ZBB-ZBKB-NEXT:    beqz a2, .LBB34_2
+; RV64ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV64ZBB-ZBKB-NEXT:    andn a0, a0, a1
+; RV64ZBB-ZBKB-NEXT:  .LBB34_2: # %identity
+; RV64ZBB-ZBKB-NEXT:    ret
+  %a = xor i64 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = and i64 %a, %x
+  ret i64 %masked
+
+identity:
+  ret i64 %x
+}
+
+define i64 @and_hoisted_not_i64_swapped(i64 %x, i64 %m, i1 zeroext %cond) {
+; RV64I-LABEL: and_hoisted_not_i64_swapped:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    beqz a2, .LBB35_2
+; RV64I-NEXT:  # %bb.1: # %mask
+; RV64I-NEXT:    not a1, a1
+; RV64I-NEXT:    and a0, a0, a1
+; RV64I-NEXT:  .LBB35_2: # %identity
+; RV64I-NEXT:    ret
+;
+; RV64ZBB-ZBKB-LABEL: and_hoisted_not_i64_swapped:
+; RV64ZBB-ZBKB:       # %bb.0:
+; RV64ZBB-ZBKB-NEXT:    beqz a2, .LBB35_2
+; RV64ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV64ZBB-ZBKB-NEXT:    andn a0, a0, a1
+; RV64ZBB-ZBKB-NEXT:  .LBB35_2: # %identity
+; RV64ZBB-ZBKB-NEXT:    ret
+  %a = xor i64 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = and i64 %x, %a
+  ret i64 %masked
+
+identity:
+  ret i64 %x
+}
+
+define i32 @or_hoisted_not_i32(i32 %x, i32 %m, i1 zeroext %cond) {
+; RV64I-LABEL: or_hoisted_not_i32:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    beqz a2, .LBB36_2
+; RV64I-NEXT:  # %bb.1: # %mask
+; RV64I-NEXT:    not a1, a1
+; RV64I-NEXT:    or a0, a1, a0
+; RV64I-NEXT:  .LBB36_2: # %identity
+; RV64I-NEXT:    ret
+;
+; RV64ZBB-ZBKB-LABEL: or_hoisted_not_i32:
+; RV64ZBB-ZBKB:       # %bb.0:
+; RV64ZBB-ZBKB-NEXT:    beqz a2, .LBB36_2
+; RV64ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV64ZBB-ZBKB-NEXT:    orn a0, a0, a1
+; RV64ZBB-ZBKB-NEXT:  .LBB36_2: # %identity
+; RV64ZBB-ZBKB-NEXT:    ret
+  %a = xor i32 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = or i32 %a, %x
+  ret i32 %masked
+
+identity:
+  ret i32 %x
+}
+
+define i32 @or_hoisted_not_i32_swapped(i32 %x, i32 %m, i1 zeroext %cond) {
+; RV64I-LABEL: or_hoisted_not_i32_swapped:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    beqz a2, .LBB37_2
+; RV64I-NEXT:  # %bb.1: # %mask
+; RV64I-NEXT:    not a1, a1
+; RV64I-NEXT:    or a0, a0, a1
+; RV64I-NEXT:  .LBB37_2: # %identity
+; RV64I-NEXT:    ret
+;
+; RV64ZBB-ZBKB-LABEL: or_hoisted_not_i32_swapped:
+; RV64ZBB-ZBKB:       # %bb.0:
+; RV64ZBB-ZBKB-NEXT:    beqz a2, .LBB37_2
+; RV64ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV64ZBB-ZBKB-NEXT:    orn a0, a0, a1
+; RV64ZBB-ZBKB-NEXT:  .LBB37_2: # %identity
+; RV64ZBB-ZBKB-NEXT:    ret
+  %a = xor i32 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = or i32 %x, %a
+  ret i32 %masked
+
+identity:
+  ret i32 %x
+}
+
+define i64 @or_hoisted_not_i64(i64 %x, i64 %m, i1 zeroext %cond) {
+; RV64I-LABEL: or_hoisted_not_i64:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    beqz a2, .LBB38_2
+; RV64I-NEXT:  # %bb.1: # %mask
+; RV64I-NEXT:    not a1, a1
+; RV64I-NEXT:    or a0, a1, a0
+; RV64I-NEXT:  .LBB38_2: # %identity
+; RV64I-NEXT:    ret
+;
+; RV64ZBB-ZBKB-LABEL: or_hoisted_not_i64:
+; RV64ZBB-ZBKB:       # %bb.0:
+; RV64ZBB-ZBKB-NEXT:    beqz a2, .LBB38_2
+; RV64ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV64ZBB-ZBKB-NEXT:    orn a0, a0, a1
+; RV64ZBB-ZBKB-NEXT:  .LBB38_2: # %identity
+; RV64ZBB-ZBKB-NEXT:    ret
+  %a = xor i64 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = or i64 %a, %x
+  ret i64 %masked
+
+identity:
+  ret i64 %x
+}
+
+define i64 @or_hoisted_not_i64_swapped(i64 %x, i64 %m, i1 zeroext %cond) {
+; RV64I-LABEL: or_hoisted_not_i64_swapped:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    beqz a2, .LBB39_2
+; RV64I-NEXT:  # %bb.1: # %mask
+; RV64I-NEXT:    not a1, a1
+; RV64I-NEXT:    or a0, a0, a1
+; RV64I-NEXT:  .LBB39_2: # %identity
+; RV64I-NEXT:    ret
+;
+; RV64ZBB-ZBKB-LABEL: or_hoisted_not_i64_swapped:
+; RV64ZBB-ZBKB:       # %bb.0:
+; RV64ZBB-ZBKB-NEXT:    beqz a2, .LBB39_2
+; RV64ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV64ZBB-ZBKB-NEXT:    orn a0, a0, a1
+; RV64ZBB-ZBKB-NEXT:  .LBB39_2: # %identity
+; RV64ZBB-ZBKB-NEXT:    ret
+  %a = xor i64 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = or i64 %x, %a
+  ret i64 %masked
+
+identity:
+  ret i64 %x
+}
+
+define i32 @xor_hoisted_not_i32(i32 %x, i32 %m, i1 zeroext %cond) {
+; RV64I-LABEL: xor_hoisted_not_i32:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    beqz a2, .LBB40_2
+; RV64I-NEXT:  # %bb.1: # %mask
+; RV64I-NEXT:    not a1, a1
+; RV64I-NEXT:    xor a0, a1, a0
+; RV64I-NEXT:  .LBB40_2: # %identity
+; RV64I-NEXT:    ret
+;
+; RV64ZBB-ZBKB-LABEL: xor_hoisted_not_i32:
+; RV64ZBB-ZBKB:       # %bb.0:
+; RV64ZBB-ZBKB-NEXT:    beqz a2, .LBB40_2
+; RV64ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV64ZBB-ZBKB-NEXT:    xnor a0, a1, a0
+; RV64ZBB-ZBKB-NEXT:  .LBB40_2: # %identity
+; RV64ZBB-ZBKB-NEXT:    ret
+  %a = xor i32 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = xor i32 %a, %x
+  ret i32 %masked
+
+identity:
+  ret i32 %x
+}
+
+define i32 @xor_hoisted_not_i32_swapped(i32 %x, i32 %m, i1 zeroext %cond) {
+; RV64I-LABEL: xor_hoisted_not_i32_swapped:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    beqz a2, .LBB41_2
+; RV64I-NEXT:  # %bb.1: # %mask
+; RV64I-NEXT:    not a1, a1
+; RV64I-NEXT:    xor a0, a0, a1
+; RV64I-NEXT:  .LBB41_2: # %identity
+; RV64I-NEXT:    ret
+;
+; RV64ZBB-ZBKB-LABEL: xor_hoisted_not_i32_swapped:
+; RV64ZBB-ZBKB:       # %bb.0:
+; RV64ZBB-ZBKB-NEXT:    beqz a2, .LBB41_2
+; RV64ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV64ZBB-ZBKB-NEXT:    xnor a0, a1, a0
+; RV64ZBB-ZBKB-NEXT:  .LBB41_2: # %identity
+; RV64ZBB-ZBKB-NEXT:    ret
+  %a = xor i32 %m, -1
+  br i1 %cond, label %mask, label %identity
+
+mask:
+  %masked = xor i32 %x, %a
+  ret i32 %masked
+
+identity:
+  ret i32 %x
+}
+
+define i64 @xor_hoisted_not_i64(i64 %x, i64 %m, i1 zeroext %cond) {
+; RV64I-LABEL: xor_hoisted_not_i64:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    beqz a2, .LBB42_2
+; RV64I-NEXT:  # %bb.1: # %mask
+; RV64I-NEXT:    not a1, a1
+; RV64I-NEXT:    xor a0, a1, a0
+; RV64I-NEXT:  .LBB42_2: # %identity
+; RV64I-NEXT:    ret
+;
+; RV64ZBB-ZBKB-LABEL: xor_hoisted_not_i64:
+; RV64ZBB-ZBKB:       # %bb.0:
+; RV64ZBB-ZBKB-NEXT:    beqz a2, .LBB42_2
+; RV64ZBB-ZBKB-NEXT:  # %bb.1: # %mask
+; RV64ZBB-ZBKB-NEXT:    xnor a0, a1, a0
+; RV64ZBB-ZBKB-NEXT:  .LBB42_2: # %identity
+; RV6...
[truncated]


declare i64 @llvm.vscale.i64()

define void @vand_vx_loop_hoisted_not(ptr %a, i32 noundef signext %mask) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

void vand_vx_loop_hoisted_not(int *a, int mask) {
    for (int i = 0; i < 256; i++)
        a[i] &= ~mask;
}

Copy link
Member

@lenary lenary left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CodeGen changes here look good to me. I'm less familiar with how exactly the implementation should be written, but the implementation looks clear to me. Please wait another 24 hours in case there are other reviewers with stronger opinions than mine

Copy link
Collaborator

@topperc topperc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Undoes a negation being hoisted out of a loop, so that it can be fold
into an inverted bitwise operation in the loop.

Implements llvm#108840 on RISC-V
@pfusik
Copy link
Contributor Author

pfusik commented Mar 18, 2025

Tests merged into main branch as 6ada38b. PR rebased.

@pfusik pfusik merged commit 35cd6a4 into llvm:main Mar 19, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants