[llvm] [llubi] Initial support for vector predication intrinsics (PR #212093)

Zhige Chen via llvm-commits llvm-commits at lists.llvm.org
Sat Jul 25 23:38:15 PDT 2026


https://github.com/nofe1248 created https://github.com/llvm/llvm-project/pull/212093

This PR implements `vp.select`, `vp.merge`, and integer binary intrinsics.

>From 429dc251c2b2a6d3defa055bbc2deb6cca26becd Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Sun, 26 Jul 2026 14:34:56 +0800
Subject: [PATCH] [llubi] Initial support for vector predication intrinsics

---
 llvm/test/tools/llubi/intr_vp_div_ub.ll   |   8 +
 llvm/test/tools/llubi/intr_vp_evl_ub.ll   |  10 +
 llvm/test/tools/llubi/intr_vp_fastmath.ll |  15 +
 llvm/test/tools/llubi/intr_vp_integer.ll  |  47 +++
 llvm/tools/llubi/lib/Interpreter.cpp      | 462 +++++++++++++++-------
 5 files changed, 410 insertions(+), 132 deletions(-)
 create mode 100644 llvm/test/tools/llubi/intr_vp_div_ub.ll
 create mode 100644 llvm/test/tools/llubi/intr_vp_evl_ub.ll
 create mode 100644 llvm/test/tools/llubi/intr_vp_fastmath.ll
 create mode 100644 llvm/test/tools/llubi/intr_vp_integer.ll

diff --git a/llvm/test/tools/llubi/intr_vp_div_ub.ll b/llvm/test/tools/llubi/intr_vp_div_ub.ll
new file mode 100644
index 0000000000000..abbf543db23a7
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_vp_div_ub.ll
@@ -0,0 +1,8 @@
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %res = call <4 x i32> @llvm.vp.sdiv.v4i32(<4 x i32> splat (i32 10), <4 x i32> <i32 2, i32 0, i32 0, i32 0>, <4 x i1> <i1 true, i1 true, i1 false, i1 false>, i32 2)
+  ret void
+}
+
+; CHECK: Immediate UB detected: Division by zero.
diff --git a/llvm/test/tools/llubi/intr_vp_evl_ub.ll b/llvm/test/tools/llubi/intr_vp_evl_ub.ll
new file mode 100644
index 0000000000000..3609d27e0279a
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_vp_evl_ub.ll
@@ -0,0 +1,10 @@
+; RUN: sed 's/EVL_ARG/i32 5/g' %s | not llubi --verbose 2>&1 | FileCheck %s --check-prefix=TOO-LARGE
+; RUN: sed 's/EVL_ARG/i32 poison/g' %s | not llubi --verbose 2>&1 | FileCheck %s --check-prefix=POISON
+
+define void @main() {
+  %res = call <4 x i32> @llvm.vp.add.v4i32(<4 x i32> zeroinitializer, <4 x i32> zeroinitializer, <4 x i1> splat (i1 true), EVL_ARG)
+  ret void
+}
+
+; TOO-LARGE: Immediate UB detected: VP intrinsic explicit vector length exceeds the runtime vector length. EVL: 5, Vector length: 4.
+; POISON: Immediate UB detected: VP intrinsic with poison explicit vector length.
diff --git a/llvm/test/tools/llubi/intr_vp_fastmath.ll b/llvm/test/tools/llubi/intr_vp_fastmath.ll
new file mode 100644
index 0000000000000..5167e3f558551
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_vp_fastmath.ll
@@ -0,0 +1,15 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %select = call nnan ninf <4 x float> @llvm.vp.select.v4f32(<4 x i1> <i1 false, i1 true, i1 false, i1 true>, <4 x float> <float +qnan, float +qnan, float +inf, float 4.0>, <4 x float> <float 1.0, float 2.0, float 3.0, float +inf>, i32 4)
+  %merge = call nnan ninf <4 x float> @llvm.vp.merge.v4f32(<4 x i1> <i1 true, i1 false, i1 poison, i1 poison>, <4 x float> <float +qnan, float +inf, float 3.0, float 4.0>, <4 x float> <float 1.0, float 2.0, float +qnan, float +inf>, i32 2)
+  %select_nsz = call nsz <4 x float> @llvm.vp.select.v4f32(<4 x i1> splat (i1 true), <4 x float> splat (float -0.0), <4 x float> zeroinitializer, i32 4)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %select = call nnan ninf <4 x float> @llvm.vp.select.v4f32(<4 x i1> <i1 false, i1 true, i1 false, i1 true>, <4 x float> <float +qnan, float +qnan, float +inf, float 4.000000e+00>, <4 x float> <float 1.000000e+00, float 2.000000e+00, float 3.000000e+00, float +inf>, i32 4) => { float 1.000000e+00, poison, float 3.000000e+00, float 4.000000e+00 }
+; CHECK-NEXT:   %merge = call nnan ninf <4 x float> @llvm.vp.merge.v4f32(<4 x i1> <i1 true, i1 false, i1 poison, i1 poison>, <4 x float> <float +qnan, float +inf, float 3.000000e+00, float 4.000000e+00>, <4 x float> <float 1.000000e+00, float 2.000000e+00, float +qnan, float +inf>, i32 2) => { poison, float 2.000000e+00, poison, poison }
+; CHECK-NEXT:   %select_nsz = call nsz <4 x float> @llvm.vp.select.v4f32(<4 x i1> splat (i1 true), <4 x float> splat (float -0.000000e+00), <4 x float> zeroinitializer, i32 4) => { float -0.000000e+00, float 0.000000e+00, float 0.000000e+00, float -0.000000e+00 }
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_vp_integer.ll b/llvm/test/tools/llubi/intr_vp_integer.ll
new file mode 100644
index 0000000000000..b9ef1ec60aab5
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_vp_integer.ll
@@ -0,0 +1,47 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose --deterministic --vscale=2 < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %select = call <4 x i32> @llvm.vp.select.v4i32(<4 x i1> <i1 true, i1 false, i1 poison, i1 true>, <4 x i32> <i32 10, i32 11, i32 12, i32 13>, <4 x i32> <i32 20, i32 21, i32 22, i32 23>, i32 3)
+  %merge = call <4 x i32> @llvm.vp.merge.v4i32(<4 x i1> <i1 true, i1 poison, i1 false, i1 poison>, <4 x i32> <i32 10, i32 11, i32 12, i32 13>, <4 x i32> <i32 20, i32 21, i32 22, i32 23>, i32 2)
+
+  %add = call <4 x i32> @llvm.vp.add.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 3, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3)
+  %sub = call <4 x i32> @llvm.vp.sub.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 3, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3)
+  %mul = call <4 x i32> @llvm.vp.mul.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 3, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3)
+  %sdiv = call <4 x i32> @llvm.vp.sdiv.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 0, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3)
+  %udiv = call <4 x i32> @llvm.vp.udiv.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 0, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3)
+  %srem = call <4 x i32> @llvm.vp.srem.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 0, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3)
+  %urem = call <4 x i32> @llvm.vp.urem.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 0, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3)
+  %ashr = call <4 x i32> @llvm.vp.ashr.v4i32(<4 x i32> <i32 -16, i32 21, i32 32, i32 23>, <4 x i32> <i32 2, i32 poison, i32 4, i32 poison>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3)
+  %lshr = call <4 x i32> @llvm.vp.lshr.v4i32(<4 x i32> <i32 -16, i32 21, i32 32, i32 23>, <4 x i32> <i32 2, i32 poison, i32 4, i32 poison>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3)
+  %shl = call <4 x i32> @llvm.vp.shl.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 2, i32 poison, i32 4, i32 poison>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3)
+  %or = call <4 x i32> @llvm.vp.or.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 3, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3)
+  %and = call <4 x i32> @llvm.vp.and.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 3, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3)
+  %xor = call <4 x i32> @llvm.vp.xor.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 3, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3)
+
+  %evl_zero = call <4 x i32> @llvm.vp.add.v4i32(<4 x i32> poison, <4 x i32> poison, <4 x i1> poison, i32 0)
+  %evl_full = call <4 x i32> @llvm.vp.add.v4i32(<4 x i32> splat (i32 1), <4 x i32> splat (i32 2), <4 x i1> splat (i1 true), i32 4)
+  %scalable = call <vscale x 4 x i32> @llvm.vp.add.nxv4i32(<vscale x 4 x i32> splat (i32 3), <vscale x 4 x i32> splat (i32 7), <vscale x 4 x i1> splat (i1 true), i32 5)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %select = call <4 x i32> @llvm.vp.select.v4i32(<4 x i1> <i1 true, i1 false, i1 poison, i1 true>, <4 x i32> <i32 10, i32 11, i32 12, i32 13>, <4 x i32> <i32 20, i32 21, i32 22, i32 23>, i32 3) => { i32 10, i32 21, poison, i32 0 }
+; CHECK-NEXT:   %merge = call <4 x i32> @llvm.vp.merge.v4i32(<4 x i1> <i1 true, i1 poison, i1 false, i1 poison>, <4 x i32> <i32 10, i32 11, i32 12, i32 13>, <4 x i32> <i32 20, i32 21, i32 22, i32 23>, i32 2) => { i32 10, poison, i32 22, i32 23 }
+; CHECK-NEXT:   %add = call <4 x i32> @llvm.vp.add.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 3, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3) => { i32 30, poison, i32 26, poison }
+; CHECK-NEXT:   %sub = call <4 x i32> @llvm.vp.sub.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 3, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3) => { i32 10, poison, i32 18, poison }
+; CHECK-NEXT:   %mul = call <4 x i32> @llvm.vp.mul.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 3, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3) => { i32 200, poison, i32 88, poison }
+; CHECK-NEXT:   %sdiv = call <4 x i32> @llvm.vp.sdiv.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 0, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3) => { i32 2, poison, i32 5, poison }
+; CHECK-NEXT:   %udiv = call <4 x i32> @llvm.vp.udiv.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 0, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3) => { i32 2, poison, i32 5, poison }
+; CHECK-NEXT:   %srem = call <4 x i32> @llvm.vp.srem.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 0, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3) => { i32 0, poison, i32 2, poison }
+; CHECK-NEXT:   %urem = call <4 x i32> @llvm.vp.urem.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 0, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3) => { i32 0, poison, i32 2, poison }
+; CHECK-NEXT:   %ashr = call <4 x i32> @llvm.vp.ashr.v4i32(<4 x i32> <i32 -16, i32 21, i32 32, i32 23>, <4 x i32> <i32 2, i32 poison, i32 4, i32 poison>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3) => { i32 -4, poison, i32 2, poison }
+; CHECK-NEXT:   %lshr = call <4 x i32> @llvm.vp.lshr.v4i32(<4 x i32> <i32 -16, i32 21, i32 32, i32 23>, <4 x i32> <i32 2, i32 poison, i32 4, i32 poison>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3) => { i32 1073741820, poison, i32 2, poison }
+; CHECK-NEXT:   %shl = call <4 x i32> @llvm.vp.shl.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 2, i32 poison, i32 4, i32 poison>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3) => { i32 80, poison, i32 352, poison }
+; CHECK-NEXT:   %or = call <4 x i32> @llvm.vp.or.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 3, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3) => { i32 30, poison, i32 22, poison }
+; CHECK-NEXT:   %and = call <4 x i32> @llvm.vp.and.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 3, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3) => { i32 0, poison, i32 4, poison }
+; CHECK-NEXT:   %xor = call <4 x i32> @llvm.vp.xor.v4i32(<4 x i32> <i32 20, i32 21, i32 22, i32 23>, <4 x i32> <i32 10, i32 3, i32 4, i32 0>, <4 x i1> <i1 true, i1 false, i1 true, i1 poison>, i32 3) => { i32 30, poison, i32 18, poison }
+; CHECK-NEXT:   %evl_zero = call <4 x i32> @llvm.vp.add.v4i32(<4 x i32> poison, <4 x i32> poison, <4 x i1> poison, i32 0) => { poison, poison, poison, poison }
+; CHECK-NEXT:   %evl_full = call <4 x i32> @llvm.vp.add.v4i32(<4 x i32> splat (i32 1), <4 x i32> splat (i32 2), <4 x i1> splat (i1 true), i32 4) => { i32 3, i32 3, i32 3, i32 3 }
+; CHECK-NEXT:   %scalable = call <vscale x 4 x i32> @llvm.vp.add.nxv4i32(<vscale x 4 x i32> splat (i32 3), <vscale x 4 x i32> splat (i32 7), <vscale x 4 x i1> splat (i1 true), i32 5) => { i32 10, i32 10, i32 10, i32 10, i32 10, poison, poison, poison }
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 425215b4aee2e..b05f1af469249 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -19,6 +19,7 @@
 #include "llvm/Analysis/VectorUtils.h"
 #include "llvm/IR/InlineAsm.h"
 #include "llvm/IR/InstVisitor.h"
+#include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Intrinsics.h"
 #include "llvm/IR/Operator.h"
 #include "llvm/IR/PatternMatch.h"
@@ -410,6 +411,116 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     return ScalarFn(LHS, RHS);
   }
 
+  struct IntBinOpFlags {
+    bool HasNSW = false;
+    bool HasNUW = false;
+    bool IsExact = false;
+    bool IsDisjoint = false;
+  };
+
+  AnyValue evaluateIntBinOp(unsigned Opcode, const AnyValue &LHS,
+                            const AnyValue &RHS, IntBinOpFlags Flags) {
+    // Keep the same immediate-UB priority as the corresponding instruction
+    // visitors. In particular, a poison divisor may refine to zero.
+    switch (Opcode) {
+    case Instruction::SDiv:
+    case Instruction::SRem: {
+      if (RHS.isPoison()) {
+        reportImmediateUB() << "Division by zero (refine RHS to 0).";
+        return AnyValue::poison();
+      }
+      const APInt &RHSVal = RHS.asInteger();
+      if (RHSVal.isZero()) {
+        reportImmediateUB() << "Division by zero.";
+        return AnyValue::poison();
+      }
+      if (LHS.isPoison()) {
+        if (RHSVal.isAllOnes())
+          reportImmediateUB()
+              << "Signed division overflow (refine LHS to INT_MIN).";
+        return AnyValue::poison();
+      }
+      const APInt &LHSVal = LHS.asInteger();
+      if (LHSVal.isMinSignedValue() && RHSVal.isAllOnes()) {
+        if (Opcode == Instruction::SDiv)
+          reportImmediateUB() << "Signed division overflow.";
+        else
+          reportImmediateUB() << "Signed division overflow. LHS: " << LHSVal
+                              << ", RHS: " << RHSVal;
+        return AnyValue::poison();
+      }
+      if (Opcode == Instruction::SDiv && Flags.IsExact) {
+        APInt Q, R;
+        APInt::sdivrem(LHSVal, RHSVal, Q, R);
+        return R.isZero() ? AnyValue(Q) : AnyValue::poison();
+      }
+      return Opcode == Instruction::SDiv ? AnyValue(LHSVal.sdiv(RHSVal))
+                                         : AnyValue(LHSVal.srem(RHSVal));
+    }
+    case Instruction::UDiv:
+    case Instruction::URem: {
+      if (RHS.isPoison()) {
+        reportImmediateUB() << "Division by zero (refine RHS to 0).";
+        return AnyValue::poison();
+      }
+      const APInt &RHSVal = RHS.asInteger();
+      if (RHSVal.isZero()) {
+        reportImmediateUB() << "Division by zero.";
+        return AnyValue::poison();
+      }
+      if (LHS.isPoison())
+        return AnyValue::poison();
+      const APInt &LHSVal = LHS.asInteger();
+      if (Opcode == Instruction::UDiv && Flags.IsExact) {
+        APInt Q, R;
+        APInt::udivrem(LHSVal, RHSVal, Q, R);
+        return R.isZero() ? AnyValue(Q) : AnyValue::poison();
+      }
+      return Opcode == Instruction::UDiv ? AnyValue(LHSVal.udiv(RHSVal))
+                                         : AnyValue(LHSVal.urem(RHSVal));
+    }
+    default:
+      break;
+    }
+
+    if (LHS.isPoison() || RHS.isPoison())
+      return AnyValue::poison();
+
+    const APInt &LHSVal = LHS.asInteger();
+    const APInt &RHSVal = RHS.asInteger();
+    switch (Opcode) {
+    case Instruction::Add:
+      return addNoWrap(LHSVal, RHSVal, Flags.HasNSW, Flags.HasNUW);
+    case Instruction::Sub:
+      return subNoWrap(LHSVal, RHSVal, Flags.HasNSW, Flags.HasNUW);
+    case Instruction::Mul:
+      return mulNoWrap(LHSVal, RHSVal, Flags.HasNSW, Flags.HasNUW);
+    case Instruction::And:
+      return LHSVal & RHSVal;
+    case Instruction::Xor:
+      return LHSVal ^ RHSVal;
+    case Instruction::Or:
+      if (Flags.IsDisjoint && LHSVal.intersects(RHSVal))
+        return AnyValue::poison();
+      return LHSVal | RHSVal;
+    case Instruction::Shl:
+      if (RHSVal.uge(LHSVal.getBitWidth()) ||
+          (Flags.HasNSW && RHSVal.uge(LHSVal.getNumSignBits())) ||
+          (Flags.HasNUW && RHSVal.ugt(LHSVal.countl_zero())))
+        return AnyValue::poison();
+      return LHSVal.shl(RHSVal);
+    case Instruction::LShr:
+    case Instruction::AShr:
+      if (RHSVal.uge(LHSVal.getBitWidth()) ||
+          (Flags.IsExact && RHSVal.ugt(LHSVal.countr_zero())))
+        return AnyValue::poison();
+      return Opcode == Instruction::LShr ? AnyValue(LHSVal.lshr(RHSVal))
+                                         : AnyValue(LHSVal.ashr(RHSVal));
+    default:
+      llvm_unreachable("Unexpected integer binary operation");
+    }
+  }
+
   void visitBinOp(
       Instruction &I,
       function_ref<AnyValue(const AnyValue &, const AnyValue &)> ScalarFn) {
@@ -814,6 +925,170 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     return Mask.asAggregate()[I].asBoolean();
   }
 
+  std::optional<uint64_t> getVPVectorLength(VPIntrinsic &VPI,
+                                            ArrayRef<AnyValue> Args) {
+    const auto EVLPos =
+        VPIntrinsic::getVectorLengthParamPos(VPI.getIntrinsicID());
+    assert(EVLPos && "VP intrinsic without an explicit vector length");
+
+    const AnyValue &EVL = Args[*EVLPos];
+    if (EVL.isPoison()) {
+      reportImmediateUB() << "VP intrinsic with poison explicit vector length.";
+      return std::nullopt;
+    }
+
+    const uint64_t MaxEVL = Ctx.getEVL(VPI.getStaticVectorLength());
+    if (EVL.asInteger().ugt(MaxEVL)) {
+      reportImmediateUB() << "VP intrinsic explicit vector length exceeds "
+                             "the runtime vector length. EVL: "
+                          << EVL.asInteger() << ", Vector length: " << MaxEVL
+                          << ".";
+      return std::nullopt;
+    }
+    return EVL.asInteger().getZExtValue();
+  }
+
+  AnyValue getUndefinedScalar(Type *Ty) {
+    AnyValue Res = AnyValue::getPoisonValue(Ctx, Ty);
+    Ctx.freeze(Res, Ty);
+    return Res;
+  }
+
+  AnyValue applyVPFastMathFlags(VPIntrinsic &VPI, AnyValue Val) {
+    const FastMathFlags FMF = VPI.getFastMathFlagsOrNone();
+    if (!FMF.any())
+      return Val;
+    return handleFMFFlags(std::move(Val), FMF, /*IsInput=*/true);
+  }
+
+  AnyValue callVPSelectIntrinsic(VPIntrinsic &VPI, ArrayRef<AnyValue> Args) {
+    const auto EVL = getVPVectorLength(VPI, Args);
+    if (!EVL)
+      return AnyValue::getPoisonValue(Ctx, VPI.getType());
+
+    const auto &Cond = Args[0].asAggregate();
+    const auto &TrueVal = Args[1].asAggregate();
+    const auto &FalseVal = Args[2].asAggregate();
+    Type *ElemTy = cast<VectorType>(VPI.getType())->getElementType();
+
+    std::vector<AnyValue> Res;
+    Res.reserve(Cond.size());
+    for (size_t I = 0, E = Cond.size(); I != E; ++I) {
+      if (I >= *EVL) {
+        Res.push_back(getUndefinedScalar(ElemTy));
+        continue;
+      }
+
+      switch (Cond[I].asBoolean()) {
+      case BooleanKind::True:
+        Res.push_back(applyVPFastMathFlags(VPI, TrueVal[I]));
+        break;
+      case BooleanKind::False:
+        Res.push_back(applyVPFastMathFlags(VPI, FalseVal[I]));
+        break;
+      case BooleanKind::Poison:
+        Res.push_back(AnyValue::poison());
+        break;
+      }
+    }
+    return std::move(Res);
+  }
+
+  AnyValue callVPMergeIntrinsic(VPIntrinsic &VPI, ArrayRef<AnyValue> Args) {
+    const auto Pivot = getVPVectorLength(VPI, Args);
+    if (!Pivot)
+      return AnyValue::getPoisonValue(Ctx, VPI.getType());
+
+    const auto &Cond = Args[0].asAggregate();
+    const auto &TrueVal = Args[1].asAggregate();
+    const auto &FalseVal = Args[2].asAggregate();
+
+    std::vector<AnyValue> Res;
+    Res.reserve(Cond.size());
+    for (size_t I = 0, E = Cond.size(); I != E; ++I) {
+      if (I >= *Pivot) {
+        Res.push_back(applyVPFastMathFlags(VPI, FalseVal[I]));
+        continue;
+      }
+
+      switch (Cond[I].asBoolean()) {
+      case BooleanKind::True:
+        Res.push_back(applyVPFastMathFlags(VPI, TrueVal[I]));
+        break;
+      case BooleanKind::False:
+        Res.push_back(applyVPFastMathFlags(VPI, FalseVal[I]));
+        break;
+      case BooleanKind::Poison:
+        Res.push_back(AnyValue::poison());
+        break;
+      }
+    }
+    return std::move(Res);
+  }
+
+  AnyValue callVPIntegerBinaryIntrinsic(VPIntrinsic &VPI,
+                                        ArrayRef<AnyValue> Args) {
+    const auto EVL = getVPVectorLength(VPI, Args);
+    if (!EVL)
+      return AnyValue::getPoisonValue(Ctx, VPI.getType());
+
+    const auto MaskPos = VPIntrinsic::getMaskParamPos(VPI.getIntrinsicID());
+    assert(MaskPos && "VP integer binary intrinsic without a mask");
+    const auto &LHS = Args[0].asAggregate();
+    const auto &RHS = Args[1].asAggregate();
+    const AnyValue &Mask = Args[*MaskPos];
+    const unsigned Opcode = *VPI.getFunctionalOpcode();
+
+    std::vector<AnyValue> Res;
+    Res.reserve(LHS.size());
+    for (size_t I = 0, E = LHS.size(); I != E; ++I) {
+      // Check EVL first: a poison mask lane past EVL is disabled by the EVL
+      // mask and must not affect the result.
+      if (I >= *EVL) {
+        Res.push_back(AnyValue::poison());
+        continue;
+      }
+
+      switch (getMaskLane(Mask, I)) {
+      case BooleanKind::False:
+      case BooleanKind::Poison:
+        Res.push_back(AnyValue::poison());
+        break;
+      case BooleanKind::True:
+        Res.push_back(
+            evaluateIntBinOp(Opcode, LHS[I], RHS[I], IntBinOpFlags{}));
+        break;
+      }
+    }
+    return std::move(Res);
+  }
+
+  std::optional<AnyValue> callVPIntrinsic(VPIntrinsic &VPI,
+                                          ArrayRef<AnyValue> Args) {
+    switch (VPI.getIntrinsicID()) {
+    case Intrinsic::vp_select:
+      return callVPSelectIntrinsic(VPI, Args);
+    case Intrinsic::vp_merge:
+      return callVPMergeIntrinsic(VPI, Args);
+    case Intrinsic::vp_add:
+    case Intrinsic::vp_sub:
+    case Intrinsic::vp_mul:
+    case Intrinsic::vp_sdiv:
+    case Intrinsic::vp_udiv:
+    case Intrinsic::vp_srem:
+    case Intrinsic::vp_urem:
+    case Intrinsic::vp_ashr:
+    case Intrinsic::vp_lshr:
+    case Intrinsic::vp_shl:
+    case Intrinsic::vp_or:
+    case Intrinsic::vp_and:
+    case Intrinsic::vp_xor:
+      return callVPIntegerBinaryIntrinsic(VPI, Args);
+    default:
+      return std::nullopt;
+    }
+  }
+
   AnyValue callExperimentalVectorHistogramIntrinsic(CallBase &CB,
                                                     ArrayRef<AnyValue> Args,
                                                     Intrinsic::ID IID) {
@@ -1020,6 +1295,11 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     Type *RetTy = CB.getType();
     const FastMathFlags FMF = CB.getFastMathFlagsOrNone();
 
+    if (VPIntrinsic::isVPIntrinsic(IID))
+      if (auto *VPI = dyn_cast<VPIntrinsic>(&CB))
+        if (auto Res = callVPIntrinsic(*VPI, Args))
+          return std::move(*Res);
+
     switch (IID) {
     case Intrinsic::assume:
       switch (Args[0].asBoolean()) {
@@ -2176,132 +2456,53 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   }
 
   void visitAdd(BinaryOperator &I) {
-    visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
-      return addNoWrap(LHS, RHS, I.hasNoSignedWrap(), I.hasNoUnsignedWrap());
+    IntBinOpFlags Flags{I.hasNoSignedWrap(), I.hasNoUnsignedWrap()};
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) {
+      return evaluateIntBinOp(Instruction::Add, LHS, RHS, Flags);
     });
   }
 
   void visitSub(BinaryOperator &I) {
-    visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
-      return subNoWrap(LHS, RHS, I.hasNoSignedWrap(), I.hasNoUnsignedWrap());
+    IntBinOpFlags Flags{I.hasNoSignedWrap(), I.hasNoUnsignedWrap()};
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) {
+      return evaluateIntBinOp(Instruction::Sub, LHS, RHS, Flags);
     });
   }
 
   void visitMul(BinaryOperator &I) {
-    visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
-      return mulNoWrap(LHS, RHS, I.hasNoSignedWrap(), I.hasNoUnsignedWrap());
+    IntBinOpFlags Flags{I.hasNoSignedWrap(), I.hasNoUnsignedWrap()};
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) {
+      return evaluateIntBinOp(Instruction::Mul, LHS, RHS, Flags);
     });
   }
 
   void visitSDiv(BinaryOperator &I) {
-    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
-      // Priority: Immediate UB > poison > normal value
-      if (RHS.isPoison()) {
-        reportImmediateUB() << "Division by zero (refine RHS to 0).";
-        return AnyValue::poison();
-      }
-      const APInt &RHSVal = RHS.asInteger();
-      if (RHSVal.isZero()) {
-        reportImmediateUB() << "Division by zero.";
-        return AnyValue::poison();
-      }
-      if (LHS.isPoison()) {
-        if (RHSVal.isAllOnes())
-          reportImmediateUB()
-              << "Signed division overflow (refine LHS to INT_MIN).";
-        return AnyValue::poison();
-      }
-      const APInt &LHSVal = LHS.asInteger();
-      if (LHSVal.isMinSignedValue() && RHSVal.isAllOnes()) {
-        reportImmediateUB() << "Signed division overflow.";
-        return AnyValue::poison();
-      }
-
-      if (I.isExact()) {
-        APInt Q, R;
-        APInt::sdivrem(LHSVal, RHSVal, Q, R);
-        if (!R.isZero())
-          return AnyValue::poison();
-        return Q;
-      } else {
-        return LHSVal.sdiv(RHSVal);
-      }
+    IntBinOpFlags Flags;
+    Flags.IsExact = I.isExact();
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) {
+      return evaluateIntBinOp(Instruction::SDiv, LHS, RHS, Flags);
     });
   }
 
   void visitSRem(BinaryOperator &I) {
-    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
-      // Priority: Immediate UB > poison > normal value
-      if (RHS.isPoison()) {
-        reportImmediateUB() << "Division by zero (refine RHS to 0).";
-        return AnyValue::poison();
-      }
-      const APInt &RHSVal = RHS.asInteger();
-      if (RHSVal.isZero()) {
-        reportImmediateUB() << "Division by zero.";
-        return AnyValue::poison();
-      }
-      if (LHS.isPoison()) {
-        if (RHSVal.isAllOnes())
-          reportImmediateUB()
-              << "Signed division overflow (refine LHS to INT_MIN).";
-        return AnyValue::poison();
-      }
-      const APInt &LHSVal = LHS.asInteger();
-      if (LHSVal.isMinSignedValue() && RHSVal.isAllOnes()) {
-        reportImmediateUB() << "Signed division overflow. LHS: " << LHSVal
-                            << ", RHS: " << RHSVal;
-        return AnyValue::poison();
-      }
-
-      return LHSVal.srem(RHSVal);
+    IntBinOpFlags Flags;
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) {
+      return evaluateIntBinOp(Instruction::SRem, LHS, RHS, Flags);
     });
   }
 
   void visitUDiv(BinaryOperator &I) {
-    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
-      // Priority: Immediate UB > poison > normal value
-      if (RHS.isPoison()) {
-        reportImmediateUB() << "Division by zero (refine RHS to 0).";
-        return AnyValue::poison();
-      }
-      const APInt &RHSVal = RHS.asInteger();
-      if (RHSVal.isZero()) {
-        reportImmediateUB() << "Division by zero.";
-        return AnyValue::poison();
-      }
-      if (LHS.isPoison())
-        return AnyValue::poison();
-      const APInt &LHSVal = LHS.asInteger();
-
-      if (I.isExact()) {
-        APInt Q, R;
-        APInt::udivrem(LHSVal, RHSVal, Q, R);
-        if (!R.isZero())
-          return AnyValue::poison();
-        return Q;
-      } else {
-        return LHSVal.udiv(RHSVal);
-      }
+    IntBinOpFlags Flags;
+    Flags.IsExact = I.isExact();
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) {
+      return evaluateIntBinOp(Instruction::UDiv, LHS, RHS, Flags);
     });
   }
 
   void visitURem(BinaryOperator &I) {
-    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
-      // Priority: Immediate UB > poison > normal value
-      if (RHS.isPoison()) {
-        reportImmediateUB() << "Division by zero (refine RHS to 0).";
-        return AnyValue::poison();
-      }
-      const APInt &RHSVal = RHS.asInteger();
-      if (RHSVal.isZero()) {
-        reportImmediateUB() << "Division by zero.";
-        return AnyValue::poison();
-      }
-      if (LHS.isPoison())
-        return AnyValue::poison();
-      const APInt &LHSVal = LHS.asInteger();
-      return LHSVal.urem(RHSVal);
+    IntBinOpFlags Flags;
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) {
+      return evaluateIntBinOp(Instruction::URem, LHS, RHS, Flags);
     });
   }
 
@@ -2476,54 +2677,51 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   }
 
   void visitAnd(BinaryOperator &I) {
-    visitIntBinOp(I, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
-      return LHS & RHS;
-    });
+    IntBinOpFlags Flags;
+    visitBinOp(
+        I,
+        [this, &Flags](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+          return evaluateIntBinOp(Instruction::And, LHS, RHS, Flags);
+        });
   }
 
   void visitXor(BinaryOperator &I) {
-    visitIntBinOp(I, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
-      return LHS ^ RHS;
-    });
+    IntBinOpFlags Flags;
+    visitBinOp(
+        I,
+        [this, &Flags](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+          return evaluateIntBinOp(Instruction::Xor, LHS, RHS, Flags);
+        });
   }
 
   void visitOr(BinaryOperator &I) {
-    visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
-      if (cast<PossiblyDisjointInst>(I).isDisjoint() && LHS.intersects(RHS))
-        return AnyValue::poison();
-      return LHS | RHS;
+    IntBinOpFlags Flags;
+    Flags.IsDisjoint = cast<PossiblyDisjointInst>(I).isDisjoint();
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+      return evaluateIntBinOp(Instruction::Or, LHS, RHS, Flags);
     });
   }
 
   void visitShl(BinaryOperator &I) {
-    visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
-      if (RHS.uge(LHS.getBitWidth()))
-        return AnyValue::poison();
-      if (I.hasNoSignedWrap() && RHS.uge(LHS.getNumSignBits()))
-        return AnyValue::poison();
-      if (I.hasNoUnsignedWrap() && RHS.ugt(LHS.countl_zero()))
-        return AnyValue::poison();
-      return LHS.shl(RHS);
+    IntBinOpFlags Flags{I.hasNoSignedWrap(), I.hasNoUnsignedWrap()};
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+      return evaluateIntBinOp(Instruction::Shl, LHS, RHS, Flags);
     });
   }
 
   void visitLShr(BinaryOperator &I) {
-    visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
-      if (RHS.uge(LHS.getBitWidth()) ||
-          (cast<PossiblyExactOperator>(I).isExact() &&
-           RHS.ugt(LHS.countr_zero())))
-        return AnyValue::poison();
-      return LHS.lshr(RHS);
+    IntBinOpFlags Flags;
+    Flags.IsExact = cast<PossiblyExactOperator>(I).isExact();
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+      return evaluateIntBinOp(Instruction::LShr, LHS, RHS, Flags);
     });
   }
 
   void visitAShr(BinaryOperator &I) {
-    visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
-      if (RHS.uge(LHS.getBitWidth()) ||
-          (cast<PossiblyExactOperator>(I).isExact() &&
-           RHS.ugt(LHS.countr_zero())))
-        return AnyValue::poison();
-      return LHS.ashr(RHS);
+    IntBinOpFlags Flags;
+    Flags.IsExact = cast<PossiblyExactOperator>(I).isExact();
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+      return evaluateIntBinOp(Instruction::AShr, LHS, RHS, Flags);
     });
   }
 



More information about the llvm-commits mailing list