[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