[llvm] [llubi] Implement intrinsics for integer arithmetic/bit manipulation (PR #193702)

Zhige Chen via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 23 04:18:28 PDT 2026


https://github.com/nofe1248 updated https://github.com/llvm/llvm-project/pull/193702

>From e7443c51889a9ab12655b19177f7393fb322f4ce Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 23 Apr 2026 17:22:10 +0800
Subject: [PATCH 1/4] [llubi] Implement integer arithmetic, bit manipulation,
 overflow/saturation arithmetic intrinsics

---
 llvm/test/tools/llubi/intr_arith_overflow.ll |  36 +++
 llvm/test/tools/llubi/intr_arith_sat.ll      |  36 +++
 llvm/test/tools/llubi/intr_bit_manip.ll      |  51 ++++
 llvm/test/tools/llubi/intr_int_arith.ll      |  36 +++
 llvm/test/tools/llubi/intr_passthrough.ll    |  15 ++
 llvm/test/tools/llubi/intr_vscale.ll         |  13 +
 llvm/tools/llubi/lib/Interpreter.cpp         | 256 +++++++++++++++++++
 7 files changed, 443 insertions(+)
 create mode 100644 llvm/test/tools/llubi/intr_arith_overflow.ll
 create mode 100644 llvm/test/tools/llubi/intr_arith_sat.ll
 create mode 100644 llvm/test/tools/llubi/intr_bit_manip.ll
 create mode 100644 llvm/test/tools/llubi/intr_int_arith.ll
 create mode 100644 llvm/test/tools/llubi/intr_passthrough.ll
 create mode 100644 llvm/test/tools/llubi/intr_vscale.ll

diff --git a/llvm/test/tools/llubi/intr_arith_overflow.ll b/llvm/test/tools/llubi/intr_arith_overflow.ll
new file mode 100644
index 0000000000000..936d3aedd67ec
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_arith_overflow.ll
@@ -0,0 +1,36 @@
+; 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() {
+    call {i32, i1} @llvm.sadd.with.overflow.i32(i32 10, i32 20)
+    call {i32, i1} @llvm.sadd.with.overflow.i32(i32 -2147483647, i32 -20)
+    call {i32, i1} @llvm.uadd.with.overflow.i32(i32 10, i32 20)
+    call {i32, i1} @llvm.uadd.with.overflow.i32(i32 4294967295, i32 20)
+
+    call {i32, i1} @llvm.ssub.with.overflow.i32(i32 10, i32 20)
+    call {i32, i1} @llvm.ssub.with.overflow.i32(i32 -2147483647, i32 20)
+    call {i32, i1} @llvm.usub.with.overflow.i32(i32 20, i32 10)
+    call {i32, i1} @llvm.usub.with.overflow.i32(i32 0, i32 20)
+
+    call {i32, i1} @llvm.smul.with.overflow.i32(i32 10, i32 20)
+    call {i32, i1} @llvm.smul.with.overflow.i32(i32 -4000000, i32 4000000)
+    call {i32, i1} @llvm.umul.with.overflow.i32(i32 20, i32 10)
+    call {i32, i1} @llvm.umul.with.overflow.i32(i32 4000000, i32 4000000)
+
+    ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %1 = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 10, i32 20) => { i32 30, F }
+; CHECK-NEXT:   %2 = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 -2147483647, i32 -20) => { i32 2147483629, T }
+; CHECK-NEXT:   %3 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 10, i32 20) => { i32 30, F }
+; CHECK-NEXT:   %4 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 -1, i32 20) => { i32 19, T }
+; CHECK-NEXT:   %5 = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 10, i32 20) => { i32 -10, F }
+; CHECK-NEXT:   %6 = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 -2147483647, i32 20) => { i32 2147483629, T }
+; CHECK-NEXT:   %7 = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 20, i32 10) => { i32 10, F }
+; CHECK-NEXT:   %8 = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 0, i32 20) => { i32 -20, T }
+; CHECK-NEXT:   %9 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 10, i32 20) => { i32 200, F }
+; CHECK-NEXT:   %10 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 -4000000, i32 4000000) => { i32 -1246822400, T }
+; CHECK-NEXT:   %11 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 20, i32 10) => { i32 200, F }
+; CHECK-NEXT:   %12 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 4000000, i32 4000000) => { i32 1246822400, T }
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_arith_sat.ll b/llvm/test/tools/llubi/intr_arith_sat.ll
new file mode 100644
index 0000000000000..ccc1c44a488ee
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_arith_sat.ll
@@ -0,0 +1,36 @@
+; 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() {
+    call i32 @llvm.sadd.sat.i32(i32 10, i32 20)
+    call i32 @llvm.sadd.sat.i32(i32 -2147483647, i32 -20)
+    call i32 @llvm.uadd.sat.i32(i32 10, i32 20)
+    call i32 @llvm.uadd.sat.i32(i32 4294967295, i32 20)
+
+    call i32 @llvm.ssub.sat.i32(i32 10, i32 20)
+    call i32 @llvm.ssub.sat.i32(i32 -2147483647, i32 20)
+    call i32 @llvm.usub.sat.i32(i32 20, i32 10)
+    call i32 @llvm.usub.sat.i32(i32 0, i32 20)
+
+    call i32 @llvm.sshl.sat.i32(i32 10, i32 2)
+    call i32 @llvm.sshl.sat.i32(i32 10, i32 40)
+    call i32 @llvm.ushl.sat.i32(i32 10, i32 2)
+    call i32 @llvm.ushl.sat.i32(i32 10, i32 40)
+
+    ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %1 = call i32 @llvm.sadd.sat.i32(i32 10, i32 20) => i32 30
+; CHECK-NEXT:   %2 = call i32 @llvm.sadd.sat.i32(i32 -2147483647, i32 -20) => i32 -2147483648
+; CHECK-NEXT:   %3 = call i32 @llvm.uadd.sat.i32(i32 10, i32 20) => i32 30
+; CHECK-NEXT:   %4 = call i32 @llvm.uadd.sat.i32(i32 -1, i32 20) => i32 -1
+; CHECK-NEXT:   %5 = call i32 @llvm.ssub.sat.i32(i32 10, i32 20) => i32 -10
+; CHECK-NEXT:   %6 = call i32 @llvm.ssub.sat.i32(i32 -2147483647, i32 20) => i32 -2147483648
+; CHECK-NEXT:   %7 = call i32 @llvm.usub.sat.i32(i32 20, i32 10) => i32 10
+; CHECK-NEXT:   %8 = call i32 @llvm.usub.sat.i32(i32 0, i32 20) => i32 0
+; CHECK-NEXT:   %9 = call i32 @llvm.sshl.sat.i32(i32 10, i32 2) => i32 40
+; CHECK-NEXT:   %10 = call i32 @llvm.sshl.sat.i32(i32 10, i32 40) => i32 2147483647
+; CHECK-NEXT:   %11 = call i32 @llvm.ushl.sat.i32(i32 10, i32 2) => i32 40
+; CHECK-NEXT:   %12 = call i32 @llvm.ushl.sat.i32(i32 10, i32 40) => i32 -1
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_bit_manip.ll b/llvm/test/tools/llubi/intr_bit_manip.ll
new file mode 100644
index 0000000000000..4e8afff00d88d
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_bit_manip.ll
@@ -0,0 +1,51 @@
+; 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() {
+    call i32 @llvm.bitreverse.i32(i32 42)
+
+    call i32 @llvm.bswap.i32(i32 12345678)
+
+    call i32 @llvm.ctpop.i32(i32 42)
+
+    call i32 @llvm.ctlz.i32(i32 4200, i1 false)
+    call i32 @llvm.ctlz.i32(i32 0, i1 true)
+
+    call i32 @llvm.cttz.i32(i32 4200, i1 false)
+    call i32 @llvm.cttz.i32(i32 0, i1 true)
+
+    call i8 @llvm.fshl.i8(i8 255, i8 0, i8 15)
+    call i8 @llvm.fshl.i8(i8 15, i8 15, i8 11)
+    call i8 @llvm.fshl.i8(i8 0, i8 255, i8 8)
+
+    call i8 @llvm.fshr.i8(i8 255, i8 0, i8 15)
+    call i8 @llvm.fshr.i8(i8 15, i8 15, i8 11)
+    call i8 @llvm.fshr.i8(i8 0, i8 255, i8 8)
+
+    call i8 @llvm.clmul.i8(i8 1, i8 2)
+    call i8 @llvm.clmul.i8(i8 5, i8 6)
+    call i8 @llvm.clmul.i8(i8 -4, i8 2)
+    call i8 @llvm.clmul.i8(i8 -4, i8 -5)
+
+    ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %1 = call i32 @llvm.bitreverse.i32(i32 42) => i32 1409286144
+; CHECK-NEXT:   %2 = call i32 @llvm.bswap.i32(i32 12345678) => i32 1315027968
+; CHECK-NEXT:   %3 = call i32 @llvm.ctpop.i32(i32 42) => i32 3
+; CHECK-NEXT:   %4 = call i32 @llvm.ctlz.i32(i32 4200, i1 false) => i32 19
+; CHECK-NEXT:   %5 = call i32 @llvm.ctlz.i32(i32 0, i1 true) => poison
+; CHECK-NEXT:   %6 = call i32 @llvm.cttz.i32(i32 4200, i1 false) => i32 3
+; CHECK-NEXT:   %7 = call i32 @llvm.cttz.i32(i32 0, i1 true) => poison
+; CHECK-NEXT:   %8 = call i8 @llvm.fshl.i8(i8 -1, i8 0, i8 15) => i8 -128
+; CHECK-NEXT:   %9 = call i8 @llvm.fshl.i8(i8 15, i8 15, i8 11) => i8 120
+; CHECK-NEXT:   %10 = call i8 @llvm.fshl.i8(i8 0, i8 -1, i8 8) => i8 0
+; CHECK-NEXT:   %11 = call i8 @llvm.fshr.i8(i8 -1, i8 0, i8 15) => i8 -2
+; CHECK-NEXT:   %12 = call i8 @llvm.fshr.i8(i8 15, i8 15, i8 11) => i8 -31
+; CHECK-NEXT:   %13 = call i8 @llvm.fshr.i8(i8 0, i8 -1, i8 8) => i8 -1
+; CHECK-NEXT:   %14 = call i8 @llvm.clmul.i8(i8 1, i8 2) => i8 2
+; CHECK-NEXT:   %15 = call i8 @llvm.clmul.i8(i8 5, i8 6) => i8 30
+; CHECK-NEXT:   %16 = call i8 @llvm.clmul.i8(i8 -4, i8 2) => i8 -8
+; CHECK-NEXT:   %17 = call i8 @llvm.clmul.i8(i8 -4, i8 -5) => i8 -92
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_int_arith.ll b/llvm/test/tools/llubi/intr_int_arith.ll
new file mode 100644
index 0000000000000..f00c051791a61
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_int_arith.ll
@@ -0,0 +1,36 @@
+; 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() {
+    call i32 @llvm.abs.i32(i32 -42, i1 false)
+    call i32 @llvm.abs.i32(i32 -2147483648, i1 true)
+
+    call i32 @llvm.smin.i32(i32 -10, i32 -20)
+    call i32 @llvm.umin.i32(i32 10, i32 20)
+    call i32 @llvm.smax.i32(i32 -10, i32 -20)
+    call i32 @llvm.umax.i32(i32 10, i32 20)
+
+    call i2 @llvm.scmp.i32(i32 10, i32 20)
+    call i2 @llvm.scmp.i32(i32 -10, i32 -20)
+    call i2 @llvm.scmp.i32(i32 10, i32 10)
+    call i2 @llvm.ucmp.i32(i32 10, i32 20)
+    call i2 @llvm.ucmp.i32(i32 20, i32 10)
+    call i2 @llvm.ucmp.i32(i32 10, i32 10)
+
+    ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %1 = call i32 @llvm.abs.i32(i32 -42, i1 false) => i32 42
+; CHECK-NEXT:   %2 = call i32 @llvm.abs.i32(i32 -2147483648, i1 true) => poison
+; CHECK-NEXT:   %3 = call i32 @llvm.smin.i32(i32 -10, i32 -20) => i32 -20
+; CHECK-NEXT:   %4 = call i32 @llvm.umin.i32(i32 10, i32 20) => i32 10
+; CHECK-NEXT:   %5 = call i32 @llvm.smax.i32(i32 -10, i32 -20) => i32 -10
+; CHECK-NEXT:   %6 = call i32 @llvm.umax.i32(i32 10, i32 20) => i32 20
+; CHECK-NEXT:   %7 = call i2 @llvm.scmp.i2.i32(i32 10, i32 20) => i2 -1
+; CHECK-NEXT:   %8 = call i2 @llvm.scmp.i2.i32(i32 -10, i32 -20) => i2 1
+; CHECK-NEXT:   %9 = call i2 @llvm.scmp.i2.i32(i32 10, i32 10) => i2 0
+; CHECK-NEXT:   %10 = call i2 @llvm.ucmp.i2.i32(i32 10, i32 20) => i2 -1
+; CHECK-NEXT:   %11 = call i2 @llvm.ucmp.i2.i32(i32 20, i32 10) => i2 1
+; CHECK-NEXT:   %12 = call i2 @llvm.ucmp.i2.i32(i32 10, i32 10) => i2 0
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_passthrough.ll b/llvm/test/tools/llubi/intr_passthrough.ll
new file mode 100644
index 0000000000000..ad69f80395875
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_passthrough.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() {
+  call i32 @llvm.ssa.copy(i32 42)
+  call i64 @llvm.expect(i64 -42, i64 -42)
+  call i1 @llvm.expect.with.probability(i1 true, i1 true, double 0.5)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %1 = call i32 @llvm.ssa.copy.i32(i32 42) => i32 42
+; CHECK-NEXT:   %2 = call i64 @llvm.expect.i64(i64 -42, i64 -42) => i64 -42
+; CHECK-NEXT:   %3 = call i1 @llvm.expect.with.probability.i1(i1 true, i1 true, double 5.000000e-01) => T
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_vscale.ll b/llvm/test/tools/llubi/intr_vscale.ll
new file mode 100644
index 0000000000000..d48ccf6114132
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_vscale.ll
@@ -0,0 +1,13 @@
+; 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() {
+  call i32 @llvm.vscale.i32()
+  call i64 @llvm.vscale.i64()
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %1 = call i32 @llvm.vscale.i32() => i32 4
+; CHECK-NEXT:   %2 = call i64 @llvm.vscale.i64() => i64 4
+; 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 1118320c7f254..94ac8a1cc0b70 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -110,6 +110,17 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     });
   }
 
+  AnyValue
+  visitIntUnOpWithResult(Instruction &I,
+                         function_ref<AnyValue(const APInt &)> ScalarFn) {
+    return computeUnOp(I.getType(), getValue(I.getOperand(0)),
+                       [&](const AnyValue &Operand) -> AnyValue {
+                         if (Operand.isPoison())
+                           return AnyValue::poison();
+                         return ScalarFn(Operand.asInteger());
+                       });
+  }
+
   AnyValue computeBinOp(
       Type *Ty, const AnyValue &LHS, const AnyValue &RHS,
       function_ref<AnyValue(const AnyValue &, const AnyValue &)> ScalarFn) {
@@ -142,6 +153,76 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     });
   }
 
+  AnyValue visitIntBinOpWithResult(
+      Instruction &I,
+      function_ref<AnyValue(const APInt &, const APInt &)> ScalarFn) {
+    return computeBinOp(
+        I.getType(), getValue(I.getOperand(0)), getValue(I.getOperand(1)),
+        [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+          if (LHS.isPoison() || RHS.isPoison())
+            return AnyValue::poison();
+          return ScalarFn(LHS.asInteger(), RHS.asInteger());
+        });
+  }
+
+  AnyValue
+  computeTriOp(Type *Ty, const AnyValue &Op1, const AnyValue &Op2,
+               const AnyValue &Op3,
+               function_ref<AnyValue(const AnyValue &, const AnyValue &,
+                                     const AnyValue &)>
+                   ScalarFn) {
+    if (Ty->isVectorTy()) {
+      auto &Op1Vec = Op1.asAggregate();
+      auto &Op2Vec = Op2.asAggregate();
+      auto &Op3Vec = Op3.asAggregate();
+      std::vector<AnyValue> ResVec;
+      ResVec.reserve(Op1Vec.size());
+      for (const auto &[ScalarOp1, ScalarOp2, ScalarOp3] :
+           zip(Op1Vec, Op2Vec, Op3Vec))
+        ResVec.push_back(ScalarFn(ScalarOp1, ScalarOp2, ScalarOp3));
+      return std::move(ResVec);
+    }
+    return ScalarFn(Op1, Op2, Op3);
+  }
+
+  void visitTriOp(Instruction &I,
+                  function_ref<AnyValue(const AnyValue &, const AnyValue &,
+                                        const AnyValue &)>
+                      ScalarFn) {
+    setResult(I, computeTriOp(I.getType(), getValue(I.getOperand(0)),
+                              getValue(I.getOperand(1)),
+                              getValue(I.getOperand(2)), ScalarFn));
+  }
+
+  void visitIntTriOp(
+      Instruction &I,
+      function_ref<AnyValue(const APInt &, const APInt &, const APInt &)>
+          ScalarFn) {
+    visitTriOp(I,
+               [&](const AnyValue &Op1, const AnyValue &Op2,
+                   const AnyValue &Op3) -> AnyValue {
+                 if (Op1.isPoison() || Op2.isPoison() || Op3.isPoison())
+                   return AnyValue::poison();
+                 return ScalarFn(Op1.asInteger(), Op2.asInteger(),
+                                 Op3.asInteger());
+               });
+  }
+
+  AnyValue visitIntTriOpWithResult(
+      Instruction &I,
+      function_ref<AnyValue(const APInt &, const APInt &, const APInt &)>
+          ScalarFn) {
+    return computeTriOp(
+        I.getType(), getValue(I.getOperand(0)), getValue(I.getOperand(1)),
+        getValue(I.getOperand(2)),
+        [&](const AnyValue &Op1, const AnyValue &Op2,
+            const AnyValue &Op3) -> AnyValue {
+          if (Op1.isPoison() || Op2.isPoison() || Op3.isPoison())
+            return AnyValue::poison();
+          return ScalarFn(Op1.asInteger(), Op2.asInteger(), Op3.asInteger());
+        });
+  }
+
   void jumpTo(Instruction &Terminator, BasicBlock *DestBB) {
     if (!Handler.onBBJump(Terminator, *DestBB)) {
       setFailed();
@@ -256,6 +337,14 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     return IdxInt.sext(IndexBitWidth);
   }
 
+  // Helper function to convert BooleanKind to bool. Report an immediate UB if
+  // a poison is found.
+  bool getBooleanNonPoison(BooleanKind Boolean) {
+    if (Boolean == BooleanKind::Poison)
+      reportImmediateUB("Unexpected poison boolean value");
+    return Boolean == BooleanKind::True;
+  }
+
 public:
   InstExecutor(Context &C, EventHandler &H, Function &F,
                ArrayRef<AnyValue> Args, AnyValue &RetVal)
@@ -353,6 +442,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
 
   AnyValue callIntrinsic(CallBase &CB) {
     Intrinsic::ID IID = CB.getIntrinsicID();
+    Type *RetTy = CB.getType();
+
     switch (IID) {
     case Intrinsic::assume:
       switch (getValue(CB.getArgOperand(0)).asBoolean()) {
@@ -381,6 +472,171 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       }
       return AnyValue();
     }
+    case Intrinsic::ssa_copy:
+    case Intrinsic::expect:
+    case Intrinsic::expect_with_probability:
+      return getValue(CB.getArgOperand(0));
+    case Intrinsic::donothing:
+      return AnyValue();
+    case Intrinsic::vscale:
+      return APInt(RetTy->getScalarSizeInBits(), Ctx.getVScale());
+    case Intrinsic::abs: {
+      const bool IsIntMinPoison =
+          getBooleanNonPoison(getValue(CB.getArgOperand(1)).asBoolean());
+      return visitIntUnOpWithResult(CB, [&](const APInt &Operand) -> AnyValue {
+        if (IsIntMinPoison && Operand.isMinSignedValue())
+          return AnyValue::poison();
+        return Operand.abs();
+      });
+    }
+    case Intrinsic::smax: {
+      return visitIntBinOpWithResult(
+          CB, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
+            return APIntOps::smax(LHS, RHS);
+          });
+    }
+    case Intrinsic::smin: {
+      return visitIntBinOpWithResult(
+          CB, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
+            return APIntOps::smin(LHS, RHS);
+          });
+    }
+    case Intrinsic::umax: {
+      return visitIntBinOpWithResult(
+          CB, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
+            return APIntOps::umax(LHS, RHS);
+          });
+    }
+    case Intrinsic::umin: {
+      return visitIntBinOpWithResult(
+          CB, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
+            return APIntOps::umin(LHS, RHS);
+          });
+    }
+    case Intrinsic::scmp:
+    case Intrinsic::ucmp: {
+      std::uint32_t BitWidth = RetTy->getScalarSizeInBits();
+      return visitIntBinOpWithResult(
+          CB, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
+            if (LHS == RHS)
+              return APInt::getZero(BitWidth);
+            if (IID == Intrinsic::scmp)
+              return LHS.slt(RHS) ? APInt::getAllOnes(BitWidth)
+                                  : APInt(BitWidth, 1);
+            return LHS.ult(RHS) ? APInt::getAllOnes(BitWidth)
+                                : APInt(BitWidth, 1);
+          });
+    }
+    case Intrinsic::bitreverse: {
+      return visitIntUnOpWithResult(CB, [](const APInt &Operand) -> AnyValue {
+        return Operand.reverseBits();
+      });
+    }
+    case Intrinsic::bswap: {
+      return visitIntUnOpWithResult(CB, [](const APInt &Operand) -> AnyValue {
+        return Operand.byteSwap();
+      });
+    }
+    case Intrinsic::ctpop: {
+      return visitIntUnOpWithResult(CB, [](const APInt &Operand) -> AnyValue {
+        return APInt(Operand.getBitWidth(), Operand.popcount());
+      });
+    }
+    case Intrinsic::ctlz:
+    case Intrinsic::cttz: {
+      const bool IsZeroPoison =
+          getBooleanNonPoison(getValue(CB.getArgOperand(1)).asBoolean());
+      return visitIntUnOpWithResult(CB, [&](const APInt &Operand) -> AnyValue {
+        if (IsZeroPoison && Operand.isZero())
+          return AnyValue::poison();
+        if (IID == Intrinsic::ctlz)
+          return APInt(Operand.getBitWidth(), Operand.countl_zero());
+        return APInt(Operand.getBitWidth(), Operand.countr_zero());
+      });
+    }
+    case Intrinsic::fshl:
+    case Intrinsic::fshr: {
+      return visitIntTriOpWithResult(
+          CB,
+          [IID](const APInt &Op1, const APInt &Op2,
+                const APInt &Op3) -> AnyValue {
+            const std::uint32_t BitWidth = Op1.getBitWidth();
+            const std::uint32_t ShiftAmount = Op3.urem(BitWidth);
+            const bool IsFShr = IID == Intrinsic::fshr;
+            const std::uint32_t LShrAmount =
+                IsFShr ? ShiftAmount : BitWidth - ShiftAmount;
+            const std::uint32_t ShlAmount =
+                !IsFShr ? ShiftAmount : BitWidth - ShiftAmount;
+            return Op1.shl(ShlAmount) | Op2.lshr(LShrAmount);
+          });
+    }
+    case Intrinsic::clmul: {
+      return visitIntBinOpWithResult(
+          CB, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
+            return APIntOps::clmul(LHS, RHS);
+          });
+    }
+    case Intrinsic::sadd_with_overflow:
+    case Intrinsic::uadd_with_overflow:
+    case Intrinsic::ssub_with_overflow:
+    case Intrinsic::usub_with_overflow:
+    case Intrinsic::smul_with_overflow:
+    case Intrinsic::umul_with_overflow: {
+      return visitIntBinOpWithResult(
+          CB, [IID](const APInt &LHS, const APInt &RHS) -> AnyValue {
+            APInt Res;
+            bool Overflow = false;
+            switch (IID) {
+            case Intrinsic::sadd_with_overflow:
+              Res = LHS.sadd_ov(RHS, Overflow);
+              break;
+            case Intrinsic::uadd_with_overflow:
+              Res = LHS.uadd_ov(RHS, Overflow);
+              break;
+            case Intrinsic::ssub_with_overflow:
+              Res = LHS.ssub_ov(RHS, Overflow);
+              break;
+            case Intrinsic::usub_with_overflow:
+              Res = LHS.usub_ov(RHS, Overflow);
+              break;
+            case Intrinsic::smul_with_overflow:
+              Res = LHS.smul_ov(RHS, Overflow);
+              break;
+            case Intrinsic::umul_with_overflow:
+              Res = LHS.umul_ov(RHS, Overflow);
+              break;
+            default:
+              llvm_unreachable("Unexpected intrinsic ID");
+            }
+            return std::vector{AnyValue(Res), AnyValue::boolean(Overflow)};
+          });
+    }
+    case Intrinsic::sadd_sat:
+    case Intrinsic::uadd_sat:
+    case Intrinsic::ssub_sat:
+    case Intrinsic::usub_sat:
+    case Intrinsic::sshl_sat:
+    case Intrinsic::ushl_sat: {
+      return visitIntBinOpWithResult(
+          CB, [IID](const APInt &LHS, const APInt &RHS) -> AnyValue {
+            switch (IID) {
+            case Intrinsic::sadd_sat:
+              return LHS.sadd_sat(RHS);
+            case Intrinsic::uadd_sat:
+              return LHS.uadd_sat(RHS);
+            case Intrinsic::ssub_sat:
+              return LHS.ssub_sat(RHS);
+            case Intrinsic::usub_sat:
+              return LHS.usub_sat(RHS);
+            case Intrinsic::sshl_sat:
+              return LHS.sshl_sat(RHS);
+            case Intrinsic::ushl_sat:
+              return LHS.ushl_sat(RHS);
+            default:
+              llvm_unreachable("Unexpected intrinsic ID");
+            }
+          });
+    }
     default:
       Handler.onUnrecognizedInstruction(CB);
       setFailed();

>From 4ab4679cbbd4ebf8872ef3a0c837a9e995560ee2 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 23 Apr 2026 18:43:46 +0800
Subject: [PATCH 2/4] [llubi] Small fixes and test cases update

---
 llvm/test/tools/llubi/intr_arith_overflow.ll | 36 ++++++++-----
 llvm/test/tools/llubi/intr_arith_sat.ll      | 44 +++++++++++-----
 llvm/test/tools/llubi/intr_bit_manip.ll      | 54 ++++++++++++++------
 llvm/test/tools/llubi/intr_int_arith.ll      | 38 +++++++++-----
 llvm/tools/llubi/lib/Interpreter.cpp         | 51 ++++++++++--------
 5 files changed, 148 insertions(+), 75 deletions(-)

diff --git a/llvm/test/tools/llubi/intr_arith_overflow.ll b/llvm/test/tools/llubi/intr_arith_overflow.ll
index 936d3aedd67ec..6e5ff40b4b93e 100644
--- a/llvm/test/tools/llubi/intr_arith_overflow.ll
+++ b/llvm/test/tools/llubi/intr_arith_overflow.ll
@@ -2,35 +2,47 @@
 ; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
 
 define void @main() {
+    call {i32, i1} @llvm.sadd.with.overflow.i32(i32 poison, i32 20)
     call {i32, i1} @llvm.sadd.with.overflow.i32(i32 10, i32 20)
     call {i32, i1} @llvm.sadd.with.overflow.i32(i32 -2147483647, i32 -20)
+    call {i32, i1} @llvm.uadd.with.overflow.i32(i32 10, i32 poison)
     call {i32, i1} @llvm.uadd.with.overflow.i32(i32 10, i32 20)
     call {i32, i1} @llvm.uadd.with.overflow.i32(i32 4294967295, i32 20)
 
+    call {i32, i1} @llvm.ssub.with.overflow.i32(i32 poison, i32 20)
     call {i32, i1} @llvm.ssub.with.overflow.i32(i32 10, i32 20)
     call {i32, i1} @llvm.ssub.with.overflow.i32(i32 -2147483647, i32 20)
+    call {i32, i1} @llvm.usub.with.overflow.i32(i32 20, i32 poison)
     call {i32, i1} @llvm.usub.with.overflow.i32(i32 20, i32 10)
     call {i32, i1} @llvm.usub.with.overflow.i32(i32 0, i32 20)
 
+    call {i32, i1} @llvm.smul.with.overflow.i32(i32 poison, i32 20)
     call {i32, i1} @llvm.smul.with.overflow.i32(i32 10, i32 20)
     call {i32, i1} @llvm.smul.with.overflow.i32(i32 -4000000, i32 4000000)
+    call {i32, i1} @llvm.umul.with.overflow.i32(i32 20, i32 poison)
     call {i32, i1} @llvm.umul.with.overflow.i32(i32 20, i32 10)
     call {i32, i1} @llvm.umul.with.overflow.i32(i32 4000000, i32 4000000)
 
     ret void
 }
 ; CHECK: Entering function: main
-; CHECK-NEXT:   %1 = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 10, i32 20) => { i32 30, F }
-; CHECK-NEXT:   %2 = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 -2147483647, i32 -20) => { i32 2147483629, T }
-; CHECK-NEXT:   %3 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 10, i32 20) => { i32 30, F }
-; CHECK-NEXT:   %4 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 -1, i32 20) => { i32 19, T }
-; CHECK-NEXT:   %5 = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 10, i32 20) => { i32 -10, F }
-; CHECK-NEXT:   %6 = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 -2147483647, i32 20) => { i32 2147483629, T }
-; CHECK-NEXT:   %7 = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 20, i32 10) => { i32 10, F }
-; CHECK-NEXT:   %8 = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 0, i32 20) => { i32 -20, T }
-; CHECK-NEXT:   %9 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 10, i32 20) => { i32 200, F }
-; CHECK-NEXT:   %10 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 -4000000, i32 4000000) => { i32 -1246822400, T }
-; CHECK-NEXT:   %11 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 20, i32 10) => { i32 200, F }
-; CHECK-NEXT:   %12 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 4000000, i32 4000000) => { i32 1246822400, T }
+; CHECK-NEXT:   %1 = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 poison, i32 20) => poison
+; CHECK-NEXT:   %2 = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 10, i32 20) => { i32 30, F }
+; CHECK-NEXT:   %3 = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 -2147483647, i32 -20) => { i32 2147483629, T }
+; CHECK-NEXT:   %4 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 10, i32 poison) => poison
+; CHECK-NEXT:   %5 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 10, i32 20) => { i32 30, F }
+; CHECK-NEXT:   %6 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 -1, i32 20) => { i32 19, T }
+; CHECK-NEXT:   %7 = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 poison, i32 20) => poison
+; CHECK-NEXT:   %8 = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 10, i32 20) => { i32 -10, F }
+; CHECK-NEXT:   %9 = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 -2147483647, i32 20) => { i32 2147483629, T }
+; CHECK-NEXT:   %10 = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 20, i32 poison) => poison
+; CHECK-NEXT:   %11 = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 20, i32 10) => { i32 10, F }
+; CHECK-NEXT:   %12 = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 0, i32 20) => { i32 -20, T }
+; CHECK-NEXT:   %13 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 poison, i32 20) => poison
+; CHECK-NEXT:   %14 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 10, i32 20) => { i32 200, F }
+; CHECK-NEXT:   %15 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 -4000000, i32 4000000) => { i32 -1246822400, T }
+; CHECK-NEXT:   %16 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 20, i32 poison) => poison
+; CHECK-NEXT:   %17 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 20, i32 10) => { i32 200, F }
+; CHECK-NEXT:   %18 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 4000000, i32 4000000) => { i32 1246822400, T }
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_arith_sat.ll b/llvm/test/tools/llubi/intr_arith_sat.ll
index ccc1c44a488ee..e327b2741689e 100644
--- a/llvm/test/tools/llubi/intr_arith_sat.ll
+++ b/llvm/test/tools/llubi/intr_arith_sat.ll
@@ -2,35 +2,51 @@
 ; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
 
 define void @main() {
+    call i32 @llvm.sadd.sat.i32(i32 poison, i32 20)
     call i32 @llvm.sadd.sat.i32(i32 10, i32 20)
     call i32 @llvm.sadd.sat.i32(i32 -2147483647, i32 -20)
+    call i32 @llvm.uadd.sat.i32(i32 10, i32 poison)
     call i32 @llvm.uadd.sat.i32(i32 10, i32 20)
     call i32 @llvm.uadd.sat.i32(i32 4294967295, i32 20)
 
+    call i32 @llvm.ssub.sat.i32(i32 poison, i32 20)
     call i32 @llvm.ssub.sat.i32(i32 10, i32 20)
     call i32 @llvm.ssub.sat.i32(i32 -2147483647, i32 20)
+    call i32 @llvm.usub.sat.i32(i32 20, i32 poison)
     call i32 @llvm.usub.sat.i32(i32 20, i32 10)
     call i32 @llvm.usub.sat.i32(i32 0, i32 20)
 
+    call i32 @llvm.sshl.sat.i32(i32 poison, i32 2)
     call i32 @llvm.sshl.sat.i32(i32 10, i32 2)
-    call i32 @llvm.sshl.sat.i32(i32 10, i32 40)
+    call i32 @llvm.sshl.sat.i32(i32 10, i32 31)
+    call i32 @llvm.sshl.sat.i32(i32 10, i32 32)
+    call i32 @llvm.ushl.sat.i32(i32 10, i32 poison)
     call i32 @llvm.ushl.sat.i32(i32 10, i32 2)
-    call i32 @llvm.ushl.sat.i32(i32 10, i32 40)
+    call i32 @llvm.ushl.sat.i32(i32 10, i32 31)
+    call i32 @llvm.ushl.sat.i32(i32 10, i32 32)
 
     ret void
 }
 ; CHECK: Entering function: main
-; CHECK-NEXT:   %1 = call i32 @llvm.sadd.sat.i32(i32 10, i32 20) => i32 30
-; CHECK-NEXT:   %2 = call i32 @llvm.sadd.sat.i32(i32 -2147483647, i32 -20) => i32 -2147483648
-; CHECK-NEXT:   %3 = call i32 @llvm.uadd.sat.i32(i32 10, i32 20) => i32 30
-; CHECK-NEXT:   %4 = call i32 @llvm.uadd.sat.i32(i32 -1, i32 20) => i32 -1
-; CHECK-NEXT:   %5 = call i32 @llvm.ssub.sat.i32(i32 10, i32 20) => i32 -10
-; CHECK-NEXT:   %6 = call i32 @llvm.ssub.sat.i32(i32 -2147483647, i32 20) => i32 -2147483648
-; CHECK-NEXT:   %7 = call i32 @llvm.usub.sat.i32(i32 20, i32 10) => i32 10
-; CHECK-NEXT:   %8 = call i32 @llvm.usub.sat.i32(i32 0, i32 20) => i32 0
-; CHECK-NEXT:   %9 = call i32 @llvm.sshl.sat.i32(i32 10, i32 2) => i32 40
-; CHECK-NEXT:   %10 = call i32 @llvm.sshl.sat.i32(i32 10, i32 40) => i32 2147483647
-; CHECK-NEXT:   %11 = call i32 @llvm.ushl.sat.i32(i32 10, i32 2) => i32 40
-; CHECK-NEXT:   %12 = call i32 @llvm.ushl.sat.i32(i32 10, i32 40) => i32 -1
+; CHECK-NEXT:   %1 = call i32 @llvm.sadd.sat.i32(i32 poison, i32 20) => poison
+; CHECK-NEXT:   %2 = call i32 @llvm.sadd.sat.i32(i32 10, i32 20) => i32 30
+; CHECK-NEXT:   %3 = call i32 @llvm.sadd.sat.i32(i32 -2147483647, i32 -20) => i32 -2147483648
+; CHECK-NEXT:   %4 = call i32 @llvm.uadd.sat.i32(i32 10, i32 poison) => poison
+; CHECK-NEXT:   %5 = call i32 @llvm.uadd.sat.i32(i32 10, i32 20) => i32 30
+; CHECK-NEXT:   %6 = call i32 @llvm.uadd.sat.i32(i32 -1, i32 20) => i32 -1
+; CHECK-NEXT:   %7 = call i32 @llvm.ssub.sat.i32(i32 poison, i32 20) => poison
+; CHECK-NEXT:   %8 = call i32 @llvm.ssub.sat.i32(i32 10, i32 20) => i32 -10
+; CHECK-NEXT:   %9 = call i32 @llvm.ssub.sat.i32(i32 -2147483647, i32 20) => i32 -2147483648
+; CHECK-NEXT:   %10 = call i32 @llvm.usub.sat.i32(i32 20, i32 poison) => poison
+; CHECK-NEXT:   %11 = call i32 @llvm.usub.sat.i32(i32 20, i32 10) => i32 10
+; CHECK-NEXT:   %12 = call i32 @llvm.usub.sat.i32(i32 0, i32 20) => i32 0
+; CHECK-NEXT:   %13 = call i32 @llvm.sshl.sat.i32(i32 poison, i32 2) => poison
+; CHECK-NEXT:   %14 = call i32 @llvm.sshl.sat.i32(i32 10, i32 2) => i32 40
+; CHECK-NEXT:   %15 = call i32 @llvm.sshl.sat.i32(i32 10, i32 31) => i32 2147483647
+; CHECK-NEXT:   %16 = call i32 @llvm.sshl.sat.i32(i32 10, i32 32) => poison
+; CHECK-NEXT:   %17 = call i32 @llvm.ushl.sat.i32(i32 10, i32 poison) => poison
+; CHECK-NEXT:   %18 = call i32 @llvm.ushl.sat.i32(i32 10, i32 2) => i32 40
+; CHECK-NEXT:   %19 = call i32 @llvm.ushl.sat.i32(i32 10, i32 31) => i32 -1
+; CHECK-NEXT:   %20 = call i32 @llvm.ushl.sat.i32(i32 10, i32 32) => poison
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_bit_manip.ll b/llvm/test/tools/llubi/intr_bit_manip.ll
index 4e8afff00d88d..e9459433317d4 100644
--- a/llvm/test/tools/llubi/intr_bit_manip.ll
+++ b/llvm/test/tools/llubi/intr_bit_manip.ll
@@ -2,26 +2,36 @@
 ; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
 
 define void @main() {
+    call i32 @llvm.bitreverse.i32(i32 poison)
     call i32 @llvm.bitreverse.i32(i32 42)
 
+    call i32 @llvm.bswap.i32(i32 poison)
     call i32 @llvm.bswap.i32(i32 12345678)
 
+    call i32 @llvm.ctpop.i32(i32 poison)
     call i32 @llvm.ctpop.i32(i32 42)
 
+    call i32 @llvm.ctlz.i32(i32 poison, i1 false)
     call i32 @llvm.ctlz.i32(i32 4200, i1 false)
     call i32 @llvm.ctlz.i32(i32 0, i1 true)
 
+    call i32 @llvm.cttz.i32(i32 poison, i1 false)
     call i32 @llvm.cttz.i32(i32 4200, i1 false)
     call i32 @llvm.cttz.i32(i32 0, i1 true)
 
+    call i8 @llvm.fshl.i8(i8 poison, i8 0, i8 15)
+    call i8 @llvm.fshl.i8(i8 255, i8 0, i8 15)
     call i8 @llvm.fshl.i8(i8 255, i8 0, i8 15)
     call i8 @llvm.fshl.i8(i8 15, i8 15, i8 11)
     call i8 @llvm.fshl.i8(i8 0, i8 255, i8 8)
 
+    call i8 @llvm.fshr.i8(i8 255, i8 0, i8 poison)
     call i8 @llvm.fshr.i8(i8 255, i8 0, i8 15)
+    call i8 @llvm.fshr.i8(i8 255, i8 0, i8 0)
     call i8 @llvm.fshr.i8(i8 15, i8 15, i8 11)
     call i8 @llvm.fshr.i8(i8 0, i8 255, i8 8)
 
+    call i8 @llvm.clmul.i8(i8 poison, i8 2)
     call i8 @llvm.clmul.i8(i8 1, i8 2)
     call i8 @llvm.clmul.i8(i8 5, i8 6)
     call i8 @llvm.clmul.i8(i8 -4, i8 2)
@@ -30,22 +40,32 @@ define void @main() {
     ret void
 }
 ; CHECK: Entering function: main
-; CHECK-NEXT:   %1 = call i32 @llvm.bitreverse.i32(i32 42) => i32 1409286144
-; CHECK-NEXT:   %2 = call i32 @llvm.bswap.i32(i32 12345678) => i32 1315027968
-; CHECK-NEXT:   %3 = call i32 @llvm.ctpop.i32(i32 42) => i32 3
-; CHECK-NEXT:   %4 = call i32 @llvm.ctlz.i32(i32 4200, i1 false) => i32 19
-; CHECK-NEXT:   %5 = call i32 @llvm.ctlz.i32(i32 0, i1 true) => poison
-; CHECK-NEXT:   %6 = call i32 @llvm.cttz.i32(i32 4200, i1 false) => i32 3
-; CHECK-NEXT:   %7 = call i32 @llvm.cttz.i32(i32 0, i1 true) => poison
-; CHECK-NEXT:   %8 = call i8 @llvm.fshl.i8(i8 -1, i8 0, i8 15) => i8 -128
-; CHECK-NEXT:   %9 = call i8 @llvm.fshl.i8(i8 15, i8 15, i8 11) => i8 120
-; CHECK-NEXT:   %10 = call i8 @llvm.fshl.i8(i8 0, i8 -1, i8 8) => i8 0
-; CHECK-NEXT:   %11 = call i8 @llvm.fshr.i8(i8 -1, i8 0, i8 15) => i8 -2
-; CHECK-NEXT:   %12 = call i8 @llvm.fshr.i8(i8 15, i8 15, i8 11) => i8 -31
-; CHECK-NEXT:   %13 = call i8 @llvm.fshr.i8(i8 0, i8 -1, i8 8) => i8 -1
-; CHECK-NEXT:   %14 = call i8 @llvm.clmul.i8(i8 1, i8 2) => i8 2
-; CHECK-NEXT:   %15 = call i8 @llvm.clmul.i8(i8 5, i8 6) => i8 30
-; CHECK-NEXT:   %16 = call i8 @llvm.clmul.i8(i8 -4, i8 2) => i8 -8
-; CHECK-NEXT:   %17 = call i8 @llvm.clmul.i8(i8 -4, i8 -5) => i8 -92
+; CHECK-NEXT:   %1 = call i32 @llvm.bitreverse.i32(i32 poison) => poison
+; CHECK-NEXT:   %2 = call i32 @llvm.bitreverse.i32(i32 42) => i32 1409286144
+; CHECK-NEXT:   %3 = call i32 @llvm.bswap.i32(i32 poison) => poison
+; CHECK-NEXT:   %4 = call i32 @llvm.bswap.i32(i32 12345678) => i32 1315027968
+; CHECK-NEXT:   %5 = call i32 @llvm.ctpop.i32(i32 poison) => poison
+; CHECK-NEXT:   %6 = call i32 @llvm.ctpop.i32(i32 42) => i32 3
+; CHECK-NEXT:   %7 = call i32 @llvm.ctlz.i32(i32 poison, i1 false) => poison
+; CHECK-NEXT:   %8 = call i32 @llvm.ctlz.i32(i32 4200, i1 false) => i32 19
+; CHECK-NEXT:   %9 = call i32 @llvm.ctlz.i32(i32 0, i1 true) => poison
+; CHECK-NEXT:   %10 = call i32 @llvm.cttz.i32(i32 poison, i1 false) => poison
+; CHECK-NEXT:   %11 = call i32 @llvm.cttz.i32(i32 4200, i1 false) => i32 3
+; CHECK-NEXT:   %12 = call i32 @llvm.cttz.i32(i32 0, i1 true) => poison
+; CHECK-NEXT:   %13 = call i8 @llvm.fshl.i8(i8 poison, i8 0, i8 15) => poison
+; CHECK-NEXT:   %14 = call i8 @llvm.fshl.i8(i8 -1, i8 0, i8 15) => i8 -128
+; CHECK-NEXT:   %15 = call i8 @llvm.fshl.i8(i8 -1, i8 0, i8 15) => i8 -128
+; CHECK-NEXT:   %16 = call i8 @llvm.fshl.i8(i8 15, i8 15, i8 11) => i8 120
+; CHECK-NEXT:   %17 = call i8 @llvm.fshl.i8(i8 0, i8 -1, i8 8) => i8 0
+; CHECK-NEXT:   %18 = call i8 @llvm.fshr.i8(i8 -1, i8 0, i8 poison) => poison
+; CHECK-NEXT:   %19 = call i8 @llvm.fshr.i8(i8 -1, i8 0, i8 15) => i8 -2
+; CHECK-NEXT:   %20 = call i8 @llvm.fshr.i8(i8 -1, i8 0, i8 0) => i8 -1
+; CHECK-NEXT:   %21 = call i8 @llvm.fshr.i8(i8 15, i8 15, i8 11) => i8 -31
+; CHECK-NEXT:   %22 = call i8 @llvm.fshr.i8(i8 0, i8 -1, i8 8) => i8 0
+; CHECK-NEXT:   %23 = call i8 @llvm.clmul.i8(i8 poison, i8 2) => poison
+; CHECK-NEXT:   %24 = call i8 @llvm.clmul.i8(i8 1, i8 2) => i8 2
+; CHECK-NEXT:   %25 = call i8 @llvm.clmul.i8(i8 5, i8 6) => i8 30
+; CHECK-NEXT:   %26 = call i8 @llvm.clmul.i8(i8 -4, i8 2) => i8 -8
+; CHECK-NEXT:   %27 = call i8 @llvm.clmul.i8(i8 -4, i8 -5) => i8 -92
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_int_arith.ll b/llvm/test/tools/llubi/intr_int_arith.ll
index f00c051791a61..bc996e6e4b49a 100644
--- a/llvm/test/tools/llubi/intr_int_arith.ll
+++ b/llvm/test/tools/llubi/intr_int_arith.ll
@@ -2,35 +2,49 @@
 ; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
 
 define void @main() {
+    call i32 @llvm.abs.i32(i32 poison, i1 false)
     call i32 @llvm.abs.i32(i32 -42, i1 false)
     call i32 @llvm.abs.i32(i32 -2147483648, i1 true)
 
     call i32 @llvm.smin.i32(i32 -10, i32 -20)
+    call i32 @llvm.smin.i32(i32 poison, i32 -20)
     call i32 @llvm.umin.i32(i32 10, i32 20)
+    call i32 @llvm.umin.i32(i32 10, i32 poison)
     call i32 @llvm.smax.i32(i32 -10, i32 -20)
+    call i32 @llvm.smax.i32(i32 poison, i32 -20)
     call i32 @llvm.umax.i32(i32 10, i32 20)
+    call i32 @llvm.umax.i32(i32 10, i32 poison)
 
     call i2 @llvm.scmp.i32(i32 10, i32 20)
     call i2 @llvm.scmp.i32(i32 -10, i32 -20)
     call i2 @llvm.scmp.i32(i32 10, i32 10)
+    call i2 @llvm.scmp.i32(i32 poison, i32 10)
     call i2 @llvm.ucmp.i32(i32 10, i32 20)
     call i2 @llvm.ucmp.i32(i32 20, i32 10)
     call i2 @llvm.ucmp.i32(i32 10, i32 10)
+    call i2 @llvm.ucmp.i32(i32 10, i32 poison)
 
     ret void
 }
 ; CHECK: Entering function: main
-; CHECK-NEXT:   %1 = call i32 @llvm.abs.i32(i32 -42, i1 false) => i32 42
-; CHECK-NEXT:   %2 = call i32 @llvm.abs.i32(i32 -2147483648, i1 true) => poison
-; CHECK-NEXT:   %3 = call i32 @llvm.smin.i32(i32 -10, i32 -20) => i32 -20
-; CHECK-NEXT:   %4 = call i32 @llvm.umin.i32(i32 10, i32 20) => i32 10
-; CHECK-NEXT:   %5 = call i32 @llvm.smax.i32(i32 -10, i32 -20) => i32 -10
-; CHECK-NEXT:   %6 = call i32 @llvm.umax.i32(i32 10, i32 20) => i32 20
-; CHECK-NEXT:   %7 = call i2 @llvm.scmp.i2.i32(i32 10, i32 20) => i2 -1
-; CHECK-NEXT:   %8 = call i2 @llvm.scmp.i2.i32(i32 -10, i32 -20) => i2 1
-; CHECK-NEXT:   %9 = call i2 @llvm.scmp.i2.i32(i32 10, i32 10) => i2 0
-; CHECK-NEXT:   %10 = call i2 @llvm.ucmp.i2.i32(i32 10, i32 20) => i2 -1
-; CHECK-NEXT:   %11 = call i2 @llvm.ucmp.i2.i32(i32 20, i32 10) => i2 1
-; CHECK-NEXT:   %12 = call i2 @llvm.ucmp.i2.i32(i32 10, i32 10) => i2 0
+; CHECK-NEXT:   %1 = call i32 @llvm.abs.i32(i32 poison, i1 false) => poison
+; CHECK-NEXT:   %2 = call i32 @llvm.abs.i32(i32 -42, i1 false) => i32 42
+; CHECK-NEXT:   %3 = call i32 @llvm.abs.i32(i32 -2147483648, i1 true) => poison
+; CHECK-NEXT:   %4 = call i32 @llvm.smin.i32(i32 -10, i32 -20) => i32 -20
+; CHECK-NEXT:   %5 = call i32 @llvm.smin.i32(i32 poison, i32 -20) => poison
+; CHECK-NEXT:   %6 = call i32 @llvm.umin.i32(i32 10, i32 20) => i32 10
+; CHECK-NEXT:   %7 = call i32 @llvm.umin.i32(i32 10, i32 poison) => poison
+; CHECK-NEXT:   %8 = call i32 @llvm.smax.i32(i32 -10, i32 -20) => i32 -10
+; CHECK-NEXT:   %9 = call i32 @llvm.smax.i32(i32 poison, i32 -20) => poison
+; CHECK-NEXT:   %10 = call i32 @llvm.umax.i32(i32 10, i32 20) => i32 20
+; CHECK-NEXT:   %11 = call i32 @llvm.umax.i32(i32 10, i32 poison) => poison
+; CHECK-NEXT:   %12 = call i2 @llvm.scmp.i2.i32(i32 10, i32 20) => i2 -1
+; CHECK-NEXT:   %13 = call i2 @llvm.scmp.i2.i32(i32 -10, i32 -20) => i2 1
+; CHECK-NEXT:   %14 = call i2 @llvm.scmp.i2.i32(i32 10, i32 10) => i2 0
+; CHECK-NEXT:   %15 = call i2 @llvm.scmp.i2.i32(i32 poison, i32 10) => poison
+; CHECK-NEXT:   %16 = call i2 @llvm.ucmp.i2.i32(i32 10, i32 20) => i2 -1
+; CHECK-NEXT:   %17 = call i2 @llvm.ucmp.i2.i32(i32 20, i32 10) => i2 1
+; CHECK-NEXT:   %18 = call i2 @llvm.ucmp.i2.i32(i32 10, i32 10) => i2 0
+; CHECK-NEXT:   %19 = call i2 @llvm.ucmp.i2.i32(i32 10, i32 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 94ac8a1cc0b70..013e299fef01d 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -440,13 +440,13 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       ++CurrentFrame->PC;
   }
 
-  AnyValue callIntrinsic(CallBase &CB) {
+  AnyValue callIntrinsic(CallBase &CB, ArrayRef<AnyValue> Args) {
     Intrinsic::ID IID = CB.getIntrinsicID();
     Type *RetTy = CB.getType();
 
     switch (IID) {
     case Intrinsic::assume:
-      switch (getValue(CB.getArgOperand(0)).asBoolean()) {
+      switch (Args[0].asBoolean()) {
       case BooleanKind::True:
         break;
       case BooleanKind::False:
@@ -458,10 +458,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       return AnyValue();
     case Intrinsic::lifetime_start:
     case Intrinsic::lifetime_end: {
-      auto *Ptr = CB.getArgOperand(0);
-      if (isa<PoisonValue>(Ptr))
+      auto Ptr = Args[0];
+      if (Ptr.isPoison())
         return AnyValue();
-      auto *MO = getValue(Ptr).asPointer().getMemoryObject();
+      auto *MO = Ptr.asPointer().getMemoryObject();
       assert(MO && "Memory object accessed by lifetime intrinsic should be "
                    "always valid.");
       if (IID == Intrinsic::lifetime_start) {
@@ -475,14 +475,18 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     case Intrinsic::ssa_copy:
     case Intrinsic::expect:
     case Intrinsic::expect_with_probability:
-      return getValue(CB.getArgOperand(0));
+      return Args[0];
     case Intrinsic::donothing:
       return AnyValue();
-    case Intrinsic::vscale:
-      return APInt(RetTy->getScalarSizeInBits(), Ctx.getVScale());
+    case Intrinsic::vscale: {
+      const unsigned BitWidth = RetTy->getScalarSizeInBits();
+      const APInt VScale(64, Ctx.getVScale());
+      if (!VScale.isIntN(BitWidth))
+        return AnyValue::poison();
+      return VScale.zextOrTrunc(BitWidth);
+    }
     case Intrinsic::abs: {
-      const bool IsIntMinPoison =
-          getBooleanNonPoison(getValue(CB.getArgOperand(1)).asBoolean());
+      const bool IsIntMinPoison = getBooleanNonPoison(Args[1].asBoolean());
       return visitIntUnOpWithResult(CB, [&](const APInt &Operand) -> AnyValue {
         if (IsIntMinPoison && Operand.isMinSignedValue())
           return AnyValue::poison();
@@ -515,7 +519,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     }
     case Intrinsic::scmp:
     case Intrinsic::ucmp: {
-      std::uint32_t BitWidth = RetTy->getScalarSizeInBits();
+      const unsigned BitWidth = RetTy->getScalarSizeInBits();
       return visitIntBinOpWithResult(
           CB, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
             if (LHS == RHS)
@@ -544,8 +548,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     }
     case Intrinsic::ctlz:
     case Intrinsic::cttz: {
-      const bool IsZeroPoison =
-          getBooleanNonPoison(getValue(CB.getArgOperand(1)).asBoolean());
+      const bool IsZeroPoison = getBooleanNonPoison(Args[1].asBoolean());
       return visitIntUnOpWithResult(CB, [&](const APInt &Operand) -> AnyValue {
         if (IsZeroPoison && Operand.isZero())
           return AnyValue::poison();
@@ -560,12 +563,14 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
           CB,
           [IID](const APInt &Op1, const APInt &Op2,
                 const APInt &Op3) -> AnyValue {
-            const std::uint32_t BitWidth = Op1.getBitWidth();
-            const std::uint32_t ShiftAmount = Op3.urem(BitWidth);
+            const unsigned BitWidth = Op1.getBitWidth();
+            const std::uint64_t ShiftAmount = Op3.urem(BitWidth);
             const bool IsFShr = IID == Intrinsic::fshr;
-            const std::uint32_t LShrAmount =
+            if (ShiftAmount == 0)
+              return IsFShr ? Op2 : Op1;
+            const std::uint64_t LShrAmount =
                 IsFShr ? ShiftAmount : BitWidth - ShiftAmount;
-            const std::uint32_t ShlAmount =
+            const std::uint64_t ShlAmount =
                 !IsFShr ? ShiftAmount : BitWidth - ShiftAmount;
             return Op1.shl(ShlAmount) | Op2.lshr(LShrAmount);
           });
@@ -628,10 +633,16 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
               return LHS.ssub_sat(RHS);
             case Intrinsic::usub_sat:
               return LHS.usub_sat(RHS);
-            case Intrinsic::sshl_sat:
+            case Intrinsic::sshl_sat: {
+              if (RHS.uge(LHS.getBitWidth()))
+                return AnyValue::poison();
               return LHS.sshl_sat(RHS);
-            case Intrinsic::ushl_sat:
+            }
+            case Intrinsic::ushl_sat: {
+              if (RHS.uge(LHS.getBitWidth()))
+                return AnyValue::poison();
               return LHS.ushl_sat(RHS);
+            }
             default:
               llvm_unreachable("Unexpected intrinsic ID");
             }
@@ -715,7 +726,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
         "Expected the callee function type to match the call site signature.");
     CurrentFrame->ResolvedCallee = Callee;
     if (Callee->isIntrinsic()) {
-      CurrentFrame->CalleeRetVal = callIntrinsic(CB);
+      CurrentFrame->CalleeRetVal = callIntrinsic(CB, CalleeArgs);
       returnFromCallee();
       return;
     } else if (Callee->isDeclaration()) {

>From b7288f0322869116c6917e07bc279a926cdc0581 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 23 Apr 2026 19:06:00 +0800
Subject: [PATCH 3/4] [llubi] Tests for vectors, also fix the implementation of
 overflow arithmetic intrinsics

---
 llvm/test/tools/llubi/intr_arith_overflow.ll | 42 +++++++++------
 llvm/test/tools/llubi/intr_arith_sat.ll      | 46 ++++++++++------
 llvm/test/tools/llubi/intr_bit_manip.ll      | 56 +++++++++++---------
 llvm/test/tools/llubi/intr_int_arith.ll      | 46 ++++++++++------
 llvm/tools/llubi/lib/Interpreter.cpp         | 41 ++++++++++++--
 5 files changed, 156 insertions(+), 75 deletions(-)

diff --git a/llvm/test/tools/llubi/intr_arith_overflow.ll b/llvm/test/tools/llubi/intr_arith_overflow.ll
index 6e5ff40b4b93e..dff9b05125bde 100644
--- a/llvm/test/tools/llubi/intr_arith_overflow.ll
+++ b/llvm/test/tools/llubi/intr_arith_overflow.ll
@@ -5,23 +5,29 @@ define void @main() {
     call {i32, i1} @llvm.sadd.with.overflow.i32(i32 poison, i32 20)
     call {i32, i1} @llvm.sadd.with.overflow.i32(i32 10, i32 20)
     call {i32, i1} @llvm.sadd.with.overflow.i32(i32 -2147483647, i32 -20)
+    call {<2 x i32>, <2 x i1>} @llvm.sadd.with.overflow.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> <i32 20, i32 1>)
     call {i32, i1} @llvm.uadd.with.overflow.i32(i32 10, i32 poison)
     call {i32, i1} @llvm.uadd.with.overflow.i32(i32 10, i32 20)
     call {i32, i1} @llvm.uadd.with.overflow.i32(i32 4294967295, i32 20)
+    call {<2 x i32>, <2 x i1>} @llvm.uadd.with.overflow.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> <i32 20, i32 1>)
 
     call {i32, i1} @llvm.ssub.with.overflow.i32(i32 poison, i32 20)
     call {i32, i1} @llvm.ssub.with.overflow.i32(i32 10, i32 20)
     call {i32, i1} @llvm.ssub.with.overflow.i32(i32 -2147483647, i32 20)
+    call {<2 x i32>, <2 x i1>} @llvm.ssub.with.overflow.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> <i32 20, i32 1>)
     call {i32, i1} @llvm.usub.with.overflow.i32(i32 20, i32 poison)
     call {i32, i1} @llvm.usub.with.overflow.i32(i32 20, i32 10)
     call {i32, i1} @llvm.usub.with.overflow.i32(i32 0, i32 20)
+    call {<2 x i32>, <2 x i1>} @llvm.usub.with.overflow.v2i32(<2 x i32> <i32 20, i32 poison>, <2 x i32> <i32 10, i32 1>)
 
     call {i32, i1} @llvm.smul.with.overflow.i32(i32 poison, i32 20)
     call {i32, i1} @llvm.smul.with.overflow.i32(i32 10, i32 20)
     call {i32, i1} @llvm.smul.with.overflow.i32(i32 -4000000, i32 4000000)
+    call {<2 x i32>, <2 x i1>} @llvm.smul.with.overflow.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> <i32 20, i32 1>)
     call {i32, i1} @llvm.umul.with.overflow.i32(i32 20, i32 poison)
     call {i32, i1} @llvm.umul.with.overflow.i32(i32 20, i32 10)
     call {i32, i1} @llvm.umul.with.overflow.i32(i32 4000000, i32 4000000)
+    call {<2 x i32>, <2 x i1>} @llvm.umul.with.overflow.v2i32(<2 x i32> <i32 20, i32 poison>, <2 x i32> <i32 10, i32 1>)
 
     ret void
 }
@@ -29,20 +35,26 @@ define void @main() {
 ; CHECK-NEXT:   %1 = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 poison, i32 20) => poison
 ; CHECK-NEXT:   %2 = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 10, i32 20) => { i32 30, F }
 ; CHECK-NEXT:   %3 = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 -2147483647, i32 -20) => { i32 2147483629, T }
-; CHECK-NEXT:   %4 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 10, i32 poison) => poison
-; CHECK-NEXT:   %5 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 10, i32 20) => { i32 30, F }
-; CHECK-NEXT:   %6 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 -1, i32 20) => { i32 19, T }
-; CHECK-NEXT:   %7 = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 poison, i32 20) => poison
-; CHECK-NEXT:   %8 = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 10, i32 20) => { i32 -10, F }
-; CHECK-NEXT:   %9 = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 -2147483647, i32 20) => { i32 2147483629, T }
-; CHECK-NEXT:   %10 = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 20, i32 poison) => poison
-; CHECK-NEXT:   %11 = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 20, i32 10) => { i32 10, F }
-; CHECK-NEXT:   %12 = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 0, i32 20) => { i32 -20, T }
-; CHECK-NEXT:   %13 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 poison, i32 20) => poison
-; CHECK-NEXT:   %14 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 10, i32 20) => { i32 200, F }
-; CHECK-NEXT:   %15 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 -4000000, i32 4000000) => { i32 -1246822400, T }
-; CHECK-NEXT:   %16 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 20, i32 poison) => poison
-; CHECK-NEXT:   %17 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 20, i32 10) => { i32 200, F }
-; CHECK-NEXT:   %18 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 4000000, i32 4000000) => { i32 1246822400, T }
+; CHECK-NEXT:   %4 = call { <2 x i32>, <2 x i1> } @llvm.sadd.with.overflow.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> <i32 20, i32 1>) => { { i32 30, poison }, { F, poison } }
+; CHECK-NEXT:   %5 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 10, i32 poison) => poison
+; CHECK-NEXT:   %6 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 10, i32 20) => { i32 30, F }
+; CHECK-NEXT:   %7 = call { i32, i1 } @llvm.uadd.with.overflow.i32(i32 -1, i32 20) => { i32 19, T }
+; CHECK-NEXT:   %8 = call { <2 x i32>, <2 x i1> } @llvm.uadd.with.overflow.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> <i32 20, i32 1>) => { { i32 30, poison }, { F, poison } }
+; CHECK-NEXT:   %9 = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 poison, i32 20) => poison
+; CHECK-NEXT:   %10 = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 10, i32 20) => { i32 -10, F }
+; CHECK-NEXT:   %11 = call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 -2147483647, i32 20) => { i32 2147483629, T }
+; CHECK-NEXT:   %12 = call { <2 x i32>, <2 x i1> } @llvm.ssub.with.overflow.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> <i32 20, i32 1>) => { { i32 -10, poison }, { F, poison } }
+; CHECK-NEXT:   %13 = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 20, i32 poison) => poison
+; CHECK-NEXT:   %14 = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 20, i32 10) => { i32 10, F }
+; CHECK-NEXT:   %15 = call { i32, i1 } @llvm.usub.with.overflow.i32(i32 0, i32 20) => { i32 -20, T }
+; CHECK-NEXT:   %16 = call { <2 x i32>, <2 x i1> } @llvm.usub.with.overflow.v2i32(<2 x i32> <i32 20, i32 poison>, <2 x i32> <i32 10, i32 1>) => { { i32 10, poison }, { F, poison } }
+; CHECK-NEXT:   %17 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 poison, i32 20) => poison
+; CHECK-NEXT:   %18 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 10, i32 20) => { i32 200, F }
+; CHECK-NEXT:   %19 = call { i32, i1 } @llvm.smul.with.overflow.i32(i32 -4000000, i32 4000000) => { i32 -1246822400, T }
+; CHECK-NEXT:   %20 = call { <2 x i32>, <2 x i1> } @llvm.smul.with.overflow.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> <i32 20, i32 1>) => { { i32 200, poison }, { F, poison } }
+; CHECK-NEXT:   %21 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 20, i32 poison) => poison
+; CHECK-NEXT:   %22 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 20, i32 10) => { i32 200, F }
+; CHECK-NEXT:   %23 = call { i32, i1 } @llvm.umul.with.overflow.i32(i32 4000000, i32 4000000) => { i32 1246822400, T }
+; CHECK-NEXT:   %24 = call { <2 x i32>, <2 x i1> } @llvm.umul.with.overflow.v2i32(<2 x i32> <i32 20, i32 poison>, <2 x i32> <i32 10, i32 1>) => { { i32 200, poison }, { F, poison } }
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_arith_sat.ll b/llvm/test/tools/llubi/intr_arith_sat.ll
index e327b2741689e..a1b22929f671b 100644
--- a/llvm/test/tools/llubi/intr_arith_sat.ll
+++ b/llvm/test/tools/llubi/intr_arith_sat.ll
@@ -5,25 +5,31 @@ define void @main() {
     call i32 @llvm.sadd.sat.i32(i32 poison, i32 20)
     call i32 @llvm.sadd.sat.i32(i32 10, i32 20)
     call i32 @llvm.sadd.sat.i32(i32 -2147483647, i32 -20)
+    call <2 x i32> @llvm.sadd.sat.v2i32(<2 x i32> <i32 -1, i32 poison>, <2 x i32> <i32 2, i32 3>)
     call i32 @llvm.uadd.sat.i32(i32 10, i32 poison)
     call i32 @llvm.uadd.sat.i32(i32 10, i32 20)
     call i32 @llvm.uadd.sat.i32(i32 4294967295, i32 20)
+    call <2 x i32> @llvm.uadd.sat.v2i32(<2 x i32> <i32 1, i32 poison>, <2 x i32> <i32 2, i32 3>)
 
     call i32 @llvm.ssub.sat.i32(i32 poison, i32 20)
     call i32 @llvm.ssub.sat.i32(i32 10, i32 20)
     call i32 @llvm.ssub.sat.i32(i32 -2147483647, i32 20)
+    call <2 x i32> @llvm.ssub.sat.v2i32(<2 x i32> <i32 -1, i32 poison>, <2 x i32> <i32 2, i32 3>)
     call i32 @llvm.usub.sat.i32(i32 20, i32 poison)
     call i32 @llvm.usub.sat.i32(i32 20, i32 10)
     call i32 @llvm.usub.sat.i32(i32 0, i32 20)
+    call <2 x i32> @llvm.usub.sat.v2i32(<2 x i32> <i32 1, i32 poison>, <2 x i32> <i32 2, i32 3>)
 
     call i32 @llvm.sshl.sat.i32(i32 poison, i32 2)
     call i32 @llvm.sshl.sat.i32(i32 10, i32 2)
     call i32 @llvm.sshl.sat.i32(i32 10, i32 31)
     call i32 @llvm.sshl.sat.i32(i32 10, i32 32)
+    call <2 x i32> @llvm.sshl.sat.v2i32(<2 x i32> <i32 10, i32 10>, <2 x i32> <i32 2, i32 poison>)
     call i32 @llvm.ushl.sat.i32(i32 10, i32 poison)
     call i32 @llvm.ushl.sat.i32(i32 10, i32 2)
     call i32 @llvm.ushl.sat.i32(i32 10, i32 31)
     call i32 @llvm.ushl.sat.i32(i32 10, i32 32)
+    call <2 x i32> @llvm.ushl.sat.v2i32(<2 x i32> <i32 10, i32 10>, <2 x i32> <i32 2, i32 poison>)
 
     ret void
 }
@@ -31,22 +37,28 @@ define void @main() {
 ; CHECK-NEXT:   %1 = call i32 @llvm.sadd.sat.i32(i32 poison, i32 20) => poison
 ; CHECK-NEXT:   %2 = call i32 @llvm.sadd.sat.i32(i32 10, i32 20) => i32 30
 ; CHECK-NEXT:   %3 = call i32 @llvm.sadd.sat.i32(i32 -2147483647, i32 -20) => i32 -2147483648
-; CHECK-NEXT:   %4 = call i32 @llvm.uadd.sat.i32(i32 10, i32 poison) => poison
-; CHECK-NEXT:   %5 = call i32 @llvm.uadd.sat.i32(i32 10, i32 20) => i32 30
-; CHECK-NEXT:   %6 = call i32 @llvm.uadd.sat.i32(i32 -1, i32 20) => i32 -1
-; CHECK-NEXT:   %7 = call i32 @llvm.ssub.sat.i32(i32 poison, i32 20) => poison
-; CHECK-NEXT:   %8 = call i32 @llvm.ssub.sat.i32(i32 10, i32 20) => i32 -10
-; CHECK-NEXT:   %9 = call i32 @llvm.ssub.sat.i32(i32 -2147483647, i32 20) => i32 -2147483648
-; CHECK-NEXT:   %10 = call i32 @llvm.usub.sat.i32(i32 20, i32 poison) => poison
-; CHECK-NEXT:   %11 = call i32 @llvm.usub.sat.i32(i32 20, i32 10) => i32 10
-; CHECK-NEXT:   %12 = call i32 @llvm.usub.sat.i32(i32 0, i32 20) => i32 0
-; CHECK-NEXT:   %13 = call i32 @llvm.sshl.sat.i32(i32 poison, i32 2) => poison
-; CHECK-NEXT:   %14 = call i32 @llvm.sshl.sat.i32(i32 10, i32 2) => i32 40
-; CHECK-NEXT:   %15 = call i32 @llvm.sshl.sat.i32(i32 10, i32 31) => i32 2147483647
-; CHECK-NEXT:   %16 = call i32 @llvm.sshl.sat.i32(i32 10, i32 32) => poison
-; CHECK-NEXT:   %17 = call i32 @llvm.ushl.sat.i32(i32 10, i32 poison) => poison
-; CHECK-NEXT:   %18 = call i32 @llvm.ushl.sat.i32(i32 10, i32 2) => i32 40
-; CHECK-NEXT:   %19 = call i32 @llvm.ushl.sat.i32(i32 10, i32 31) => i32 -1
-; CHECK-NEXT:   %20 = call i32 @llvm.ushl.sat.i32(i32 10, i32 32) => poison
+; CHECK-NEXT:   %4 = call <2 x i32> @llvm.sadd.sat.v2i32(<2 x i32> <i32 -1, i32 poison>, <2 x i32> <i32 2, i32 3>) => { i32 1, poison }
+; CHECK-NEXT:   %5 = call i32 @llvm.uadd.sat.i32(i32 10, i32 poison) => poison
+; CHECK-NEXT:   %6 = call i32 @llvm.uadd.sat.i32(i32 10, i32 20) => i32 30
+; CHECK-NEXT:   %7 = call i32 @llvm.uadd.sat.i32(i32 -1, i32 20) => i32 -1
+; CHECK-NEXT:   %8 = call <2 x i32> @llvm.uadd.sat.v2i32(<2 x i32> <i32 1, i32 poison>, <2 x i32> <i32 2, i32 3>) => { i32 3, poison }
+; CHECK-NEXT:   %9 = call i32 @llvm.ssub.sat.i32(i32 poison, i32 20) => poison
+; CHECK-NEXT:   %10 = call i32 @llvm.ssub.sat.i32(i32 10, i32 20) => i32 -10
+; CHECK-NEXT:   %11 = call i32 @llvm.ssub.sat.i32(i32 -2147483647, i32 20) => i32 -2147483648
+; CHECK-NEXT:   %12 = call <2 x i32> @llvm.ssub.sat.v2i32(<2 x i32> <i32 -1, i32 poison>, <2 x i32> <i32 2, i32 3>) => { i32 -3, poison }
+; CHECK-NEXT:   %13 = call i32 @llvm.usub.sat.i32(i32 20, i32 poison) => poison
+; CHECK-NEXT:   %14 = call i32 @llvm.usub.sat.i32(i32 20, i32 10) => i32 10
+; CHECK-NEXT:   %15 = call i32 @llvm.usub.sat.i32(i32 0, i32 20) => i32 0
+; CHECK-NEXT:   %16 = call <2 x i32> @llvm.usub.sat.v2i32(<2 x i32> <i32 1, i32 poison>, <2 x i32> <i32 2, i32 3>) => { i32 0, poison }
+; CHECK-NEXT:   %17 = call i32 @llvm.sshl.sat.i32(i32 poison, i32 2) => poison
+; CHECK-NEXT:   %18 = call i32 @llvm.sshl.sat.i32(i32 10, i32 2) => i32 40
+; CHECK-NEXT:   %19 = call i32 @llvm.sshl.sat.i32(i32 10, i32 31) => i32 2147483647
+; CHECK-NEXT:   %20 = call i32 @llvm.sshl.sat.i32(i32 10, i32 32) => poison
+; CHECK-NEXT:   %21 = call <2 x i32> @llvm.sshl.sat.v2i32(<2 x i32> splat (i32 10), <2 x i32> <i32 2, i32 poison>) => { i32 40, poison }
+; CHECK-NEXT:   %22 = call i32 @llvm.ushl.sat.i32(i32 10, i32 poison) => poison
+; CHECK-NEXT:   %23 = call i32 @llvm.ushl.sat.i32(i32 10, i32 2) => i32 40
+; CHECK-NEXT:   %24 = call i32 @llvm.ushl.sat.i32(i32 10, i32 31) => i32 -1
+; CHECK-NEXT:   %25 = call i32 @llvm.ushl.sat.i32(i32 10, i32 32) => poison
+; CHECK-NEXT:   %26 = call <2 x i32> @llvm.ushl.sat.v2i32(<2 x i32> splat (i32 10), <2 x i32> <i32 2, i32 poison>) => { i32 40, poison }
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_bit_manip.ll b/llvm/test/tools/llubi/intr_bit_manip.ll
index e9459433317d4..c63e1987567f5 100644
--- a/llvm/test/tools/llubi/intr_bit_manip.ll
+++ b/llvm/test/tools/llubi/intr_bit_manip.ll
@@ -4,6 +4,7 @@
 define void @main() {
     call i32 @llvm.bitreverse.i32(i32 poison)
     call i32 @llvm.bitreverse.i32(i32 42)
+    call <2 x i32> @llvm.bitreverse.v2i32(<2 x i32> <i32 42, i32 poison>)
 
     call i32 @llvm.bswap.i32(i32 poison)
     call i32 @llvm.bswap.i32(i32 12345678)
@@ -24,48 +25,55 @@ define void @main() {
     call i8 @llvm.fshl.i8(i8 255, i8 0, i8 15)
     call i8 @llvm.fshl.i8(i8 15, i8 15, i8 11)
     call i8 @llvm.fshl.i8(i8 0, i8 255, i8 8)
+    call <2 x i8> @llvm.fshl.v2i8(<2 x i8> <i8 poison, i8 15>, <2 x i8> <i8 0, i8 15>, <2 x i8> <i8 15, i8 11>)
 
     call i8 @llvm.fshr.i8(i8 255, i8 0, i8 poison)
     call i8 @llvm.fshr.i8(i8 255, i8 0, i8 15)
     call i8 @llvm.fshr.i8(i8 255, i8 0, i8 0)
     call i8 @llvm.fshr.i8(i8 15, i8 15, i8 11)
     call i8 @llvm.fshr.i8(i8 0, i8 255, i8 8)
+    call <2 x i8> @llvm.fshr.v2i8(<2 x i8> <i8 255, i8 0>, <2 x i8> <i8 0, i8 15>, <2 x i8> <i8 0, i8 0>)
 
     call i8 @llvm.clmul.i8(i8 poison, i8 2)
     call i8 @llvm.clmul.i8(i8 1, i8 2)
     call i8 @llvm.clmul.i8(i8 5, i8 6)
     call i8 @llvm.clmul.i8(i8 -4, i8 2)
     call i8 @llvm.clmul.i8(i8 -4, i8 -5)
+    call <2 x i8> @llvm.clmul.v2i8(<2 x i8> <i8 1, i8 poison>, <2 x i8> <i8 2, i8 3>)
 
     ret void
 }
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %1 = call i32 @llvm.bitreverse.i32(i32 poison) => poison
 ; CHECK-NEXT:   %2 = call i32 @llvm.bitreverse.i32(i32 42) => i32 1409286144
-; CHECK-NEXT:   %3 = call i32 @llvm.bswap.i32(i32 poison) => poison
-; CHECK-NEXT:   %4 = call i32 @llvm.bswap.i32(i32 12345678) => i32 1315027968
-; CHECK-NEXT:   %5 = call i32 @llvm.ctpop.i32(i32 poison) => poison
-; CHECK-NEXT:   %6 = call i32 @llvm.ctpop.i32(i32 42) => i32 3
-; CHECK-NEXT:   %7 = call i32 @llvm.ctlz.i32(i32 poison, i1 false) => poison
-; CHECK-NEXT:   %8 = call i32 @llvm.ctlz.i32(i32 4200, i1 false) => i32 19
-; CHECK-NEXT:   %9 = call i32 @llvm.ctlz.i32(i32 0, i1 true) => poison
-; CHECK-NEXT:   %10 = call i32 @llvm.cttz.i32(i32 poison, i1 false) => poison
-; CHECK-NEXT:   %11 = call i32 @llvm.cttz.i32(i32 4200, i1 false) => i32 3
-; CHECK-NEXT:   %12 = call i32 @llvm.cttz.i32(i32 0, i1 true) => poison
-; CHECK-NEXT:   %13 = call i8 @llvm.fshl.i8(i8 poison, i8 0, i8 15) => poison
-; CHECK-NEXT:   %14 = call i8 @llvm.fshl.i8(i8 -1, i8 0, i8 15) => i8 -128
+; CHECK-NEXT:   %3 = call <2 x i32> @llvm.bitreverse.v2i32(<2 x i32> <i32 42, i32 poison>) => { i32 1409286144, poison }
+; CHECK-NEXT:   %4 = call i32 @llvm.bswap.i32(i32 poison) => poison
+; CHECK-NEXT:   %5 = call i32 @llvm.bswap.i32(i32 12345678) => i32 1315027968
+; CHECK-NEXT:   %6 = call i32 @llvm.ctpop.i32(i32 poison) => poison
+; CHECK-NEXT:   %7 = call i32 @llvm.ctpop.i32(i32 42) => i32 3
+; CHECK-NEXT:   %8 = call i32 @llvm.ctlz.i32(i32 poison, i1 false) => poison
+; CHECK-NEXT:   %9 = call i32 @llvm.ctlz.i32(i32 4200, i1 false) => i32 19
+; CHECK-NEXT:   %10 = call i32 @llvm.ctlz.i32(i32 0, i1 true) => poison
+; CHECK-NEXT:   %11 = call i32 @llvm.cttz.i32(i32 poison, i1 false) => poison
+; CHECK-NEXT:   %12 = call i32 @llvm.cttz.i32(i32 4200, i1 false) => i32 3
+; CHECK-NEXT:   %13 = call i32 @llvm.cttz.i32(i32 0, i1 true) => poison
+; CHECK-NEXT:   %14 = call i8 @llvm.fshl.i8(i8 poison, i8 0, i8 15) => poison
 ; CHECK-NEXT:   %15 = call i8 @llvm.fshl.i8(i8 -1, i8 0, i8 15) => i8 -128
-; CHECK-NEXT:   %16 = call i8 @llvm.fshl.i8(i8 15, i8 15, i8 11) => i8 120
-; CHECK-NEXT:   %17 = call i8 @llvm.fshl.i8(i8 0, i8 -1, i8 8) => i8 0
-; CHECK-NEXT:   %18 = call i8 @llvm.fshr.i8(i8 -1, i8 0, i8 poison) => poison
-; CHECK-NEXT:   %19 = call i8 @llvm.fshr.i8(i8 -1, i8 0, i8 15) => i8 -2
-; CHECK-NEXT:   %20 = call i8 @llvm.fshr.i8(i8 -1, i8 0, i8 0) => i8 -1
-; CHECK-NEXT:   %21 = call i8 @llvm.fshr.i8(i8 15, i8 15, i8 11) => i8 -31
-; CHECK-NEXT:   %22 = call i8 @llvm.fshr.i8(i8 0, i8 -1, i8 8) => i8 0
-; CHECK-NEXT:   %23 = call i8 @llvm.clmul.i8(i8 poison, i8 2) => poison
-; CHECK-NEXT:   %24 = call i8 @llvm.clmul.i8(i8 1, i8 2) => i8 2
-; CHECK-NEXT:   %25 = call i8 @llvm.clmul.i8(i8 5, i8 6) => i8 30
-; CHECK-NEXT:   %26 = call i8 @llvm.clmul.i8(i8 -4, i8 2) => i8 -8
-; CHECK-NEXT:   %27 = call i8 @llvm.clmul.i8(i8 -4, i8 -5) => i8 -92
+; CHECK-NEXT:   %16 = call i8 @llvm.fshl.i8(i8 -1, i8 0, i8 15) => i8 -128
+; CHECK-NEXT:   %17 = call i8 @llvm.fshl.i8(i8 15, i8 15, i8 11) => i8 120
+; CHECK-NEXT:   %18 = call i8 @llvm.fshl.i8(i8 0, i8 -1, i8 8) => i8 0
+; CHECK-NEXT:   %19 = call <2 x i8> @llvm.fshl.v2i8(<2 x i8> <i8 poison, i8 15>, <2 x i8> <i8 0, i8 15>, <2 x i8> <i8 15, i8 11>) => { poison, i8 120 }
+; CHECK-NEXT:   %20 = call i8 @llvm.fshr.i8(i8 -1, i8 0, i8 poison) => poison
+; CHECK-NEXT:   %21 = call i8 @llvm.fshr.i8(i8 -1, i8 0, i8 15) => i8 -2
+; CHECK-NEXT:   %22 = call i8 @llvm.fshr.i8(i8 -1, i8 0, i8 0) => i8 0
+; CHECK-NEXT:   %23 = call i8 @llvm.fshr.i8(i8 15, i8 15, i8 11) => i8 -31
+; CHECK-NEXT:   %24 = call i8 @llvm.fshr.i8(i8 0, i8 -1, i8 8) => i8 -1
+; CHECK-NEXT:   %25 = call <2 x i8> @llvm.fshr.v2i8(<2 x i8> <i8 -1, i8 0>, <2 x i8> <i8 0, i8 15>, <2 x i8> zeroinitializer) => { i8 0, i8 15 }
+; CHECK-NEXT:   %26 = call i8 @llvm.clmul.i8(i8 poison, i8 2) => poison
+; CHECK-NEXT:   %27 = call i8 @llvm.clmul.i8(i8 1, i8 2) => i8 2
+; CHECK-NEXT:   %28 = call i8 @llvm.clmul.i8(i8 5, i8 6) => i8 30
+; CHECK-NEXT:   %29 = call i8 @llvm.clmul.i8(i8 -4, i8 2) => i8 -8
+; CHECK-NEXT:   %30 = call i8 @llvm.clmul.i8(i8 -4, i8 -5) => i8 -92
+; CHECK-NEXT:   %31 = call <2 x i8> @llvm.clmul.v2i8(<2 x i8> <i8 1, i8 poison>, <2 x i8> <i8 2, i8 3>) => { i8 2, poison }
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_int_arith.ll b/llvm/test/tools/llubi/intr_int_arith.ll
index bc996e6e4b49a..a601200a65d82 100644
--- a/llvm/test/tools/llubi/intr_int_arith.ll
+++ b/llvm/test/tools/llubi/intr_int_arith.ll
@@ -5,24 +5,31 @@ define void @main() {
     call i32 @llvm.abs.i32(i32 poison, i1 false)
     call i32 @llvm.abs.i32(i32 -42, i1 false)
     call i32 @llvm.abs.i32(i32 -2147483648, i1 true)
+    call <2 x i32> @llvm.abs.v2i32(<2 x i32> <i32 poison, i32 -42>, i1 false)
 
     call i32 @llvm.smin.i32(i32 -10, i32 -20)
     call i32 @llvm.smin.i32(i32 poison, i32 -20)
+    call <2 x i32> @llvm.smin.v2i32(<2 x i32> <i32 poison, i32 -10>, <2 x i32> <i32 -20, i32 -20>)
     call i32 @llvm.umin.i32(i32 10, i32 20)
     call i32 @llvm.umin.i32(i32 10, i32 poison)
+    call <2 x i32> @llvm.umin.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> <i32 20, i32 20>)
     call i32 @llvm.smax.i32(i32 -10, i32 -20)
     call i32 @llvm.smax.i32(i32 poison, i32 -20)
+    call <2 x i32> @llvm.smax.v2i32(<2 x i32> <i32 poison, i32 -10>, <2 x i32> <i32 -20, i32 -20>)
     call i32 @llvm.umax.i32(i32 10, i32 20)
     call i32 @llvm.umax.i32(i32 10, i32 poison)
+    call <2 x i32> @llvm.umax.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> <i32 20, i32 20>)
 
     call i2 @llvm.scmp.i32(i32 10, i32 20)
     call i2 @llvm.scmp.i32(i32 -10, i32 -20)
     call i2 @llvm.scmp.i32(i32 10, i32 10)
     call i2 @llvm.scmp.i32(i32 poison, i32 10)
+    call <2 x i2> @llvm.scmp.v2i2.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> <i32 20, i32 10>)
     call i2 @llvm.ucmp.i32(i32 10, i32 20)
     call i2 @llvm.ucmp.i32(i32 20, i32 10)
     call i2 @llvm.ucmp.i32(i32 10, i32 10)
     call i2 @llvm.ucmp.i32(i32 10, i32 poison)
+    call <2 x i2> @llvm.ucmp.v2i2.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> <i32 20, i32 10>)
 
     ret void
 }
@@ -30,21 +37,28 @@ define void @main() {
 ; CHECK-NEXT:   %1 = call i32 @llvm.abs.i32(i32 poison, i1 false) => poison
 ; CHECK-NEXT:   %2 = call i32 @llvm.abs.i32(i32 -42, i1 false) => i32 42
 ; CHECK-NEXT:   %3 = call i32 @llvm.abs.i32(i32 -2147483648, i1 true) => poison
-; CHECK-NEXT:   %4 = call i32 @llvm.smin.i32(i32 -10, i32 -20) => i32 -20
-; CHECK-NEXT:   %5 = call i32 @llvm.smin.i32(i32 poison, i32 -20) => poison
-; CHECK-NEXT:   %6 = call i32 @llvm.umin.i32(i32 10, i32 20) => i32 10
-; CHECK-NEXT:   %7 = call i32 @llvm.umin.i32(i32 10, i32 poison) => poison
-; CHECK-NEXT:   %8 = call i32 @llvm.smax.i32(i32 -10, i32 -20) => i32 -10
-; CHECK-NEXT:   %9 = call i32 @llvm.smax.i32(i32 poison, i32 -20) => poison
-; CHECK-NEXT:   %10 = call i32 @llvm.umax.i32(i32 10, i32 20) => i32 20
-; CHECK-NEXT:   %11 = call i32 @llvm.umax.i32(i32 10, i32 poison) => poison
-; CHECK-NEXT:   %12 = call i2 @llvm.scmp.i2.i32(i32 10, i32 20) => i2 -1
-; CHECK-NEXT:   %13 = call i2 @llvm.scmp.i2.i32(i32 -10, i32 -20) => i2 1
-; CHECK-NEXT:   %14 = call i2 @llvm.scmp.i2.i32(i32 10, i32 10) => i2 0
-; CHECK-NEXT:   %15 = call i2 @llvm.scmp.i2.i32(i32 poison, i32 10) => poison
-; CHECK-NEXT:   %16 = call i2 @llvm.ucmp.i2.i32(i32 10, i32 20) => i2 -1
-; CHECK-NEXT:   %17 = call i2 @llvm.ucmp.i2.i32(i32 20, i32 10) => i2 1
-; CHECK-NEXT:   %18 = call i2 @llvm.ucmp.i2.i32(i32 10, i32 10) => i2 0
-; CHECK-NEXT:   %19 = call i2 @llvm.ucmp.i2.i32(i32 10, i32 poison) => poison
+; CHECK-NEXT:   %4 = call <2 x i32> @llvm.abs.v2i32(<2 x i32> <i32 poison, i32 -42>, i1 false) => { poison, i32 42 }
+; CHECK-NEXT:   %5 = call i32 @llvm.smin.i32(i32 -10, i32 -20) => i32 -20
+; CHECK-NEXT:   %6 = call i32 @llvm.smin.i32(i32 poison, i32 -20) => poison
+; CHECK-NEXT:   %7 = call <2 x i32> @llvm.smin.v2i32(<2 x i32> <i32 poison, i32 -10>, <2 x i32> splat (i32 -20)) => { poison, i32 -20 }
+; CHECK-NEXT:   %8 = call i32 @llvm.umin.i32(i32 10, i32 20) => i32 10
+; CHECK-NEXT:   %9 = call i32 @llvm.umin.i32(i32 10, i32 poison) => poison
+; CHECK-NEXT:   %10 = call <2 x i32> @llvm.umin.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> splat (i32 20)) => { i32 10, poison }
+; CHECK-NEXT:   %11 = call i32 @llvm.smax.i32(i32 -10, i32 -20) => i32 -10
+; CHECK-NEXT:   %12 = call i32 @llvm.smax.i32(i32 poison, i32 -20) => poison
+; CHECK-NEXT:   %13 = call <2 x i32> @llvm.smax.v2i32(<2 x i32> <i32 poison, i32 -10>, <2 x i32> splat (i32 -20)) => { poison, i32 -10 }
+; CHECK-NEXT:   %14 = call i32 @llvm.umax.i32(i32 10, i32 20) => i32 20
+; CHECK-NEXT:   %15 = call i32 @llvm.umax.i32(i32 10, i32 poison) => poison
+; CHECK-NEXT:   %16 = call <2 x i32> @llvm.umax.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> splat (i32 20)) => { i32 20, poison }
+; CHECK-NEXT:   %17 = call i2 @llvm.scmp.i2.i32(i32 10, i32 20) => i2 -1
+; CHECK-NEXT:   %18 = call i2 @llvm.scmp.i2.i32(i32 -10, i32 -20) => i2 1
+; CHECK-NEXT:   %19 = call i2 @llvm.scmp.i2.i32(i32 10, i32 10) => i2 0
+; CHECK-NEXT:   %20 = call i2 @llvm.scmp.i2.i32(i32 poison, i32 10) => poison
+; CHECK-NEXT:   %21 = call <2 x i2> @llvm.scmp.v2i2.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> <i32 20, i32 10>) => { i2 -1, poison }
+; CHECK-NEXT:   %22 = call i2 @llvm.ucmp.i2.i32(i32 10, i32 20) => i2 -1
+; CHECK-NEXT:   %23 = call i2 @llvm.ucmp.i2.i32(i32 20, i32 10) => i2 1
+; CHECK-NEXT:   %24 = call i2 @llvm.ucmp.i2.i32(i32 10, i32 10) => i2 0
+; CHECK-NEXT:   %25 = call i2 @llvm.ucmp.i2.i32(i32 10, i32 poison) => poison
+; CHECK-NEXT:   %26 = call <2 x i2> @llvm.ucmp.v2i2.v2i32(<2 x i32> <i32 10, i32 poison>, <2 x i32> <i32 20, i32 10>) => { i2 -1, 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 013e299fef01d..3bec36f353ce2 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -165,6 +165,40 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
         });
   }
 
+  AnyValue visitOverflowIntBinOpWithResult(
+      CallBase &CB,
+      function_ref<std::pair<APInt, bool>(const APInt &, const APInt &)>
+          ScalarFn) {
+    const AnyValue &LHS = getValue(CB.getOperand(0));
+    const AnyValue &RHS = getValue(CB.getOperand(1));
+    if (!LHS.isAggregate())
+      return visitIntBinOpWithResult(
+          CB, [&](const APInt &ScalarLHS, const APInt &ScalarRHS) -> AnyValue {
+            auto [Res, Overflow] = ScalarFn(ScalarLHS, ScalarRHS);
+            return std::vector{AnyValue(Res), AnyValue::boolean(Overflow)};
+          });
+
+    auto &LHSVec = LHS.asAggregate();
+    auto &RHSVec = RHS.asAggregate();
+    std::vector<AnyValue> ResVec;
+    std::vector<AnyValue> OverflowVec;
+    ResVec.reserve(LHSVec.size());
+    OverflowVec.reserve(LHSVec.size());
+    for (const auto &[ScalarLHS, ScalarRHS] : zip(LHSVec, RHSVec)) {
+      if (ScalarLHS.isPoison() || ScalarRHS.isPoison()) {
+        ResVec.push_back(AnyValue::poison());
+        OverflowVec.push_back(AnyValue::poison());
+        continue;
+      }
+      auto [Res, Overflow] =
+          ScalarFn(ScalarLHS.asInteger(), ScalarRHS.asInteger());
+      ResVec.push_back(AnyValue(Res));
+      OverflowVec.push_back(AnyValue::boolean(Overflow));
+    }
+    return std::vector{AnyValue(std::move(ResVec)),
+                       AnyValue(std::move(OverflowVec))};
+  }
+
   AnyValue
   computeTriOp(Type *Ty, const AnyValue &Op1, const AnyValue &Op2,
                const AnyValue &Op3,
@@ -587,8 +621,9 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     case Intrinsic::usub_with_overflow:
     case Intrinsic::smul_with_overflow:
     case Intrinsic::umul_with_overflow: {
-      return visitIntBinOpWithResult(
-          CB, [IID](const APInt &LHS, const APInt &RHS) -> AnyValue {
+      return visitOverflowIntBinOpWithResult(
+          CB,
+          [IID](const APInt &LHS, const APInt &RHS) -> std::pair<APInt, bool> {
             APInt Res;
             bool Overflow = false;
             switch (IID) {
@@ -613,7 +648,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
             default:
               llvm_unreachable("Unexpected intrinsic ID");
             }
-            return std::vector{AnyValue(Res), AnyValue::boolean(Overflow)};
+            return {Res, Overflow};
           });
     }
     case Intrinsic::sadd_sat:

>From 3e815257ccf2f3714fb7db42408809ae39139466 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 23 Apr 2026 19:18:10 +0800
Subject: [PATCH 4/4] [llubi] Add test for vscale overflow

---
 llvm/test/tools/llubi/intr_vscale_poison.ll | 13 +++++++++++++
 1 file changed, 13 insertions(+)
 create mode 100644 llvm/test/tools/llubi/intr_vscale_poison.ll

diff --git a/llvm/test/tools/llubi/intr_vscale_poison.ll b/llvm/test/tools/llubi/intr_vscale_poison.ll
new file mode 100644
index 0000000000000..2fe406d532254
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_vscale_poison.ll
@@ -0,0 +1,13 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --vscale=257 --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  call i8 @llvm.vscale.i8()
+  call i16 @llvm.vscale.i16()
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %1 = call i8 @llvm.vscale.i8() => poison
+; CHECK-NEXT:   %2 = call i16 @llvm.vscale.i16() => i16 257
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main



More information about the llvm-commits mailing list