[llvm] [llubi] Add support for integer arithmetic ops (PR #181224)

Yingwei Zheng via llvm-commits llvm-commits at lists.llvm.org
Fri Feb 13 03:52:36 PST 2026


https://github.com/dtcxzyw updated https://github.com/llvm/llvm-project/pull/181224

>From aa8678a6a4467fec08c86a0123d8a103e2fd90ea Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Fri, 13 Feb 2026 04:02:33 +0800
Subject: [PATCH 1/2] [llubi] Add support for integer arithmetic ops

---
 llvm/test/tools/llubi/divrem_ub1.ll  |  12 +
 llvm/test/tools/llubi/divrem_ub2.ll  |  12 +
 llvm/test/tools/llubi/divrem_ub3.ll  |  10 +
 llvm/test/tools/llubi/divrem_ub4.ll  |  10 +
 llvm/test/tools/llubi/int_arith.ll   | 145 +++++++++++
 llvm/tools/llubi/lib/Context.cpp     |  26 +-
 llvm/tools/llubi/lib/Interpreter.cpp | 365 ++++++++++++++++++++++++++-
 llvm/tools/llubi/lib/Value.h         |   9 +
 8 files changed, 580 insertions(+), 9 deletions(-)
 create mode 100644 llvm/test/tools/llubi/divrem_ub1.ll
 create mode 100644 llvm/test/tools/llubi/divrem_ub2.ll
 create mode 100644 llvm/test/tools/llubi/divrem_ub3.ll
 create mode 100644 llvm/test/tools/llubi/divrem_ub4.ll
 create mode 100644 llvm/test/tools/llubi/int_arith.ll

diff --git a/llvm/test/tools/llubi/divrem_ub1.ll b/llvm/test/tools/llubi/divrem_ub1.ll
new file mode 100644
index 0000000000000..00b6dfeca43f5
--- /dev/null
+++ b/llvm/test/tools/llubi/divrem_ub1.ll
@@ -0,0 +1,12 @@
+; RUN: sed 's/DIVREM/sdiv/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+; RUN: sed 's/DIVREM/udiv/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+; RUN: sed 's/DIVREM/srem/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+; RUN: sed 's/DIVREM/urem/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @main() {
+  %res = DIVREM <2 x i32> splat(i32 10), zeroinitializer
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Division by zero.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/divrem_ub2.ll b/llvm/test/tools/llubi/divrem_ub2.ll
new file mode 100644
index 0000000000000..03a941da1e7a0
--- /dev/null
+++ b/llvm/test/tools/llubi/divrem_ub2.ll
@@ -0,0 +1,12 @@
+; RUN: sed 's/DIVREM/sdiv/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+; RUN: sed 's/DIVREM/udiv/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+; RUN: sed 's/DIVREM/srem/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+; RUN: sed 's/DIVREM/urem/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @main() {
+  %res = DIVREM i32 10, poison
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Division by zero (refine RHS to 0).
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/divrem_ub3.ll b/llvm/test/tools/llubi/divrem_ub3.ll
new file mode 100644
index 0000000000000..7ab668d6b9c8a
--- /dev/null
+++ b/llvm/test/tools/llubi/divrem_ub3.ll
@@ -0,0 +1,10 @@
+; RUN: sed 's/DIVREM/sdiv/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+; RUN: sed 's/DIVREM/srem/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @main() {
+  %res = DIVREM i8 -128, -1
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Signed division overflow.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/divrem_ub4.ll b/llvm/test/tools/llubi/divrem_ub4.ll
new file mode 100644
index 0000000000000..90a7d7c025f23
--- /dev/null
+++ b/llvm/test/tools/llubi/divrem_ub4.ll
@@ -0,0 +1,10 @@
+; RUN: sed 's/DIVREM/sdiv/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+; RUN: sed 's/DIVREM/srem/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @main() {
+  %res = DIVREM i8 poison, -1
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Signed division overflow (refine LHS to INT_MIN).
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/int_arith.ll b/llvm/test/tools/llubi/int_arith.ll
new file mode 100644
index 0000000000000..0dc45d9d6fa8e
--- /dev/null
+++ b/llvm/test/tools/llubi/int_arith.ll
@@ -0,0 +1,145 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: llubi --verbose --use-constant-int-for-fixed-length-splat < %s 2>&1 | FileCheck %s
+
+; Check constant and poison propagation for integer arithmetic operators.
+
+define void @main() {
+  %add = add i32 1, 2
+  %add_nuw_poison = add nuw i32 2147483648, 2147483648
+  %add_nsw_poison1 = add nsw i8 -1, -128
+  %add_nsw_poison2 = add nsw i8 127, 127
+  %add_use1 = add i32 %add, 3
+  %add_use2 = add i32 %add_nuw_poison, 2
+  %add_vec = add nuw nsw <4 x i32> <i32 2147483648, i32 -1, i32 poison, i32 0>, <i32 2147483648, i32 2147483648, i32 0, i32 poison>
+  %add_splat = add <4 x i32> splat(i32 1), zeroinitializer
+  %add_i1 = add <4 x i1> <i1 true, i1 true, i1 false, i1 false>, <i1 true, i1 false, i1 true, i1 false>
+
+  %sub = sub i32 1, 2
+  %sub_nuw_poison = sub nuw i32 0, 1
+  %sub_nsw_poison1 = sub nsw i32 -2147483648, 1
+  %sub_nsw_poison2 = sub nsw i32 0, -2147483648
+
+  %mul = mul i32 2, -3
+  %mul_nsw_poison = mul nsw i8 -16, 9
+  %mul_nuw_poison = mul nuw i8 16, 16
+
+  %sdiv = sdiv i32 90, 15
+  %sdiv_poison = sdiv i32 poison, %sdiv
+  %sdiv_exact = sdiv i32 4, 2
+  %sdiv_exact_poison = sdiv exact i32 3, 2
+  %srem = srem i32 90, 16
+  %srem_poison = srem i32 poison, 1
+
+  %udiv = udiv i32 90, 15
+  %udiv_poison = udiv i32 poison, %udiv
+  %udiv_exact = udiv i32 4, 2
+  %udiv_exact_poison = udiv exact i32 3, 2
+  %urem = urem i32 90, 16
+  %urem_poison = urem i32 poison, 1
+
+  %trunc = trunc i32 65533 to i8
+  %trunc_poison = trunc i32 poison to i8
+  %trunc_nsw = trunc nsw i32 -1 to i8
+  %trunc_nsw_poison = trunc nsw i32 511 to i8
+  %trunc_nuw = trunc nuw i32 255 to i8
+  %trunc_nuw_poison = trunc nuw i32 511 to i8
+
+  %zext = zext i8 -1 to i32
+  %zext_poison = zext i8 poison to i32
+  %zext_nneg_poison = zext nneg i8 -1 to i32
+
+  %sext = sext i8 -1 to i32
+  %sext_poison = sext i8 poison to i32
+
+  %and = and i8 15, 10
+  %xor = xor i8 13, 10
+  %or = or i8 15, 10
+  %or_disjoint = or disjoint i8 12, 3
+  %or_disjoint_poison = or disjoint i8 15, 10
+
+  %shl = shl i8 127, 3
+  %shl_oob = shl i8 127, 8
+  %shl_nsw = shl nsw i8 32, 1
+  %shl_nsw_poison = shl nsw i8 32, 2
+  %shl_nuw = shl nuw i8 10, 4
+  %shl_nuw_poison = shl nuw i8 10, 5
+
+  %lshr = lshr i8 127, 3
+  %lshr_exact = lshr exact i8 126, 1
+  %lshr_exact_poison = lshr exact i8 1, 1
+
+  %ashr = ashr i8 127, 3
+  %ashr_exact = ashr exact i8 126, 1
+  %ashr_exact_poison = ashr exact i8 1, 1
+
+  %select = select i1 true, i1 false, i1 poison
+  %select_vec1 = select <3 x i1> <i1 true, i1 false, i1 poison>, <3 x i32> splat(i32 10), <3 x i32> splat(i32 20)
+  %select_vec2 = select i1 false, <2 x i32> splat(i32 10), <2 x i32> splat(i32 20)
+  %select_struct = select i1 false, {i32, [2 x i1], { <2 x i16> }} zeroinitializer, {i32, [2 x i1], { <2 x i16> }} {i32 0, [2 x i1] [i1 true, i1 poison],{ <2 x i16> } { <2 x i16> <i16 1, i16 2> }}
+
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %add = add i32 1, 2 => i32 3
+; CHECK-NEXT:   %add_nuw_poison = add nuw i32 -2147483648, -2147483648 => poison
+; CHECK-NEXT:   %add_nsw_poison1 = add nsw i8 -1, -128 => poison
+; CHECK-NEXT:   %add_nsw_poison2 = add nsw i8 127, 127 => poison
+; CHECK-NEXT:   %add_use1 = add i32 %add, 3 => i32 6
+; CHECK-NEXT:   %add_use2 = add i32 %add_nuw_poison, 2 => poison
+; CHECK-NEXT:   %add_vec = add nuw nsw <4 x i32> <i32 -2147483648, i32 -1, i32 poison, i32 0>, <i32 -2147483648, i32 -2147483648, i32 0, i32 poison> => { poison, poison, poison, poison }
+; CHECK-NEXT:   %add_splat = add <4 x i32> splat (i32 1), zeroinitializer => { i32 1, i32 1, i32 1, i32 1 }
+; CHECK-NEXT:   %add_i1 = add <4 x i1> <i1 true, i1 true, i1 false, i1 false>, <i1 true, i1 false, i1 true, i1 false> => { F, T, T, F }
+; CHECK-NEXT:   %sub = sub i32 1, 2 => i32 -1
+; CHECK-NEXT:   %sub_nuw_poison = sub nuw i32 0, 1 => poison
+; CHECK-NEXT:   %sub_nsw_poison1 = sub nsw i32 -2147483648, 1 => poison
+; CHECK-NEXT:   %sub_nsw_poison2 = sub nsw i32 0, -2147483648 => poison
+; CHECK-NEXT:   %mul = mul i32 2, -3 => i32 -6
+; CHECK-NEXT:   %mul_nsw_poison = mul nsw i8 -16, 9 => poison
+; CHECK-NEXT:   %mul_nuw_poison = mul nuw i8 16, 16 => poison
+; CHECK-NEXT:   %sdiv = sdiv i32 90, 15 => i32 6
+; CHECK-NEXT:   %sdiv_poison = sdiv i32 poison, %sdiv => poison
+; CHECK-NEXT:   %sdiv_exact = sdiv i32 4, 2 => i32 2
+; CHECK-NEXT:   %sdiv_exact_poison = sdiv exact i32 3, 2 => poison
+; CHECK-NEXT:   %srem = srem i32 90, 16 => i32 10
+; CHECK-NEXT:   %srem_poison = srem i32 poison, 1 => poison
+; CHECK-NEXT:   %udiv = udiv i32 90, 15 => i32 6
+; CHECK-NEXT:   %udiv_poison = udiv i32 poison, %udiv => poison
+; CHECK-NEXT:   %udiv_exact = udiv i32 4, 2 => i32 2
+; CHECK-NEXT:   %udiv_exact_poison = udiv exact i32 3, 2 => poison
+; CHECK-NEXT:   %urem = urem i32 90, 16 => i32 10
+; CHECK-NEXT:   %urem_poison = urem i32 poison, 1 => poison
+; CHECK-NEXT:   %trunc = trunc i32 65533 to i8 => i8 -3
+; CHECK-NEXT:   %trunc_poison = trunc i32 poison to i8 => poison
+; CHECK-NEXT:   %trunc_nsw = trunc nsw i32 -1 to i8 => i8 -1
+; CHECK-NEXT:   %trunc_nsw_poison = trunc nsw i32 511 to i8 => poison
+; CHECK-NEXT:   %trunc_nuw = trunc nuw i32 255 to i8 => i8 -1
+; CHECK-NEXT:   %trunc_nuw_poison = trunc nuw i32 511 to i8 => poison
+; CHECK-NEXT:   %zext = zext i8 -1 to i32 => i32 255
+; CHECK-NEXT:   %zext_poison = zext i8 poison to i32 => poison
+; CHECK-NEXT:   %zext_nneg_poison = zext nneg i8 -1 to i32 => poison
+; CHECK-NEXT:   %sext = sext i8 -1 to i32 => i32 -1
+; CHECK-NEXT:   %sext_poison = sext i8 poison to i32 => poison
+; CHECK-NEXT:   %and = and i8 15, 10 => i8 10
+; CHECK-NEXT:   %xor = xor i8 13, 10 => i8 7
+; CHECK-NEXT:   %or = or i8 15, 10 => i8 15
+; CHECK-NEXT:   %or_disjoint = or disjoint i8 12, 3 => i8 15
+; CHECK-NEXT:   %or_disjoint_poison = or disjoint i8 15, 10 => poison
+; CHECK-NEXT:   %shl = shl i8 127, 3 => i8 -8
+; CHECK-NEXT:   %shl_oob = shl i8 127, 8 => poison
+; CHECK-NEXT:   %shl_nsw = shl nsw i8 32, 1 => i8 64
+; CHECK-NEXT:   %shl_nsw_poison = shl nsw i8 32, 2 => poison
+; CHECK-NEXT:   %shl_nuw = shl nuw i8 10, 4 => i8 -96
+; CHECK-NEXT:   %shl_nuw_poison = shl nuw i8 10, 5 => poison
+; CHECK-NEXT:   %lshr = lshr i8 127, 3 => i8 15
+; CHECK-NEXT:   %lshr_exact = lshr exact i8 126, 1 => i8 63
+; CHECK-NEXT:   %lshr_exact_poison = lshr exact i8 1, 1 => poison
+; CHECK-NEXT:   %ashr = ashr i8 127, 3 => i8 15
+; CHECK-NEXT:   %ashr_exact = ashr exact i8 126, 1 => i8 63
+; CHECK-NEXT:   %ashr_exact_poison = ashr exact i8 1, 1 => poison
+; CHECK-NEXT:   %select = select i1 true, i1 false, i1 poison => F
+; CHECK-NEXT:   %select_vec1 = select <3 x i1> <i1 true, i1 false, i1 poison>, <3 x i32> splat (i32 10), <3 x i32> splat (i32 20) => { i32 10, i32 20, poison }
+; CHECK-NEXT:   %select_vec2 = select i1 false, <2 x i32> splat (i32 10), <2 x i32> splat (i32 20) => { i32 20, i32 20 }
+; CHECK-NEXT:   %select_struct = select i1 false, { i32, [2 x i1], { <2 x i16> } } zeroinitializer, { i32, [2 x i1], { <2 x i16> } } { i32 0, [2 x i1] [i1 true, i1 poison], { <2 x i16> } { <2 x i16> <i16 1, i16 2> } } => { i32 0, { T, poison }, { { i16 1, i16 2 } } }
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 6b5362204cfde..8e720d85d5ebc 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -25,9 +25,31 @@ AnyValue Context::getConstantValueImpl(Constant *C) {
   if (isa<PoisonValue>(C))
     return AnyValue::getPoisonValue(*this, C->getType());
 
-  // TODO: Handle ConstantInt vector.
-  if (auto *CI = dyn_cast<ConstantInt>(C))
+  if (isa<ConstantAggregateZero>(C))
+    return AnyValue::getNullValue(*this, C->getType());
+
+  if (auto *CI = dyn_cast<ConstantInt>(C)) {
+    if (auto *VecTy = dyn_cast<VectorType>(CI->getType()))
+      return std::vector<AnyValue>(getEVL(VecTy->getElementCount()),
+                                   AnyValue(CI->getValue()));
     return CI->getValue();
+  }
+
+  if (auto *CDS = dyn_cast<ConstantDataSequential>(C)) {
+    std::vector<AnyValue> Elts;
+    Elts.reserve(CDS->getNumElements());
+    for (uint32_t I = 0, E = CDS->getNumElements(); I != E; ++I)
+      Elts.push_back(getConstantValue(CDS->getElementAsConstant(I)));
+    return std::move(Elts);
+  }
+
+  if (auto *CA = dyn_cast<ConstantAggregate>(C)) {
+    std::vector<AnyValue> Elts;
+    Elts.reserve(CA->getNumOperands());
+    for (uint32_t I = 0, E = CA->getNumOperands(); I != E; ++I)
+      Elts.push_back(getConstantValue(CA->getOperand(I)));
+    return std::move(Elts);
+  }
 
   llvm_unreachable("Unrecognized constant");
 }
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index aaad8fb15262e..ba4119d7b970a 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -13,6 +13,7 @@
 #include "Context.h"
 #include "Value.h"
 #include "llvm/IR/InstVisitor.h"
+#include "llvm/IR/Operator.h"
 #include "llvm/Support/Allocator.h"
 
 namespace llvm::ubi {
@@ -75,6 +76,42 @@ struct Frame {
   }
 };
 
+static AnyValue addNoWrap(const APInt &LHS, const APInt &RHS, bool HasNSW,
+                          bool HasNUW) {
+  APInt Res = LHS + RHS;
+  if (HasNUW && Res.ult(RHS))
+    return AnyValue::poison();
+  if (HasNSW && LHS.isNonNegative() == RHS.isNonNegative() &&
+      LHS.isNonNegative() != Res.isNonNegative())
+    return AnyValue::poison();
+  return Res;
+}
+
+static AnyValue subNoWrap(const APInt &LHS, const APInt &RHS, bool HasNSW,
+                          bool HasNUW) {
+  APInt Res = LHS - RHS;
+  if (HasNUW && Res.ugt(LHS))
+    return AnyValue::poison();
+  if (HasNSW && LHS.isNonNegative() != RHS.isNonNegative() &&
+      LHS.isNonNegative() != Res.isNonNegative())
+    return AnyValue::poison();
+  return Res;
+}
+
+static AnyValue mulNoWrap(const APInt &LHS, const APInt &RHS, bool HasNSW,
+                          bool HasNUW) {
+  bool Overflow = false;
+  APInt Res = LHS.smul_ov(RHS, Overflow);
+  if (HasNSW && Overflow)
+    return AnyValue::poison();
+  if (HasNUW) {
+    (void)LHS.umul_ov(RHS, Overflow);
+    if (Overflow)
+      return AnyValue::poison();
+  }
+  return Res;
+}
+
 /// Instruction executor using the visitor pattern.
 /// visit* methods return true on success, false on error.
 /// Unlike the Context class that manages the global state,
@@ -103,6 +140,74 @@ class InstExecutor : public InstVisitor<InstExecutor, bool> {
     return CurrentFrame->ValueMap.at(V);
   }
 
+  bool setResult(Instruction &I, AnyValue V) {
+    if (Status)
+      Handler.onInstructionExecuted(I, V);
+    CurrentFrame->ValueMap.insert_or_assign(&I, std::move(V));
+    return true;
+  }
+
+  AnyValue computeUnOp(Type *Ty, const AnyValue &Operand,
+                       function_ref<AnyValue(const AnyValue &)> ScalarFn) {
+    if (Ty->isVectorTy()) {
+      auto &OperandVec = Operand.asAggregate();
+      std::vector<AnyValue> ResVec;
+      ResVec.reserve(OperandVec.size());
+      for (const auto &Scalar : OperandVec)
+        ResVec.push_back(ScalarFn(Scalar));
+      return std::move(ResVec);
+    }
+    return ScalarFn(Operand);
+  }
+
+  bool visitUnOp(Instruction &I,
+                 function_ref<AnyValue(const AnyValue &)> ScalarFn) {
+    return setResult(
+        I, computeUnOp(I.getType(), getValue(I.getOperand(0)), ScalarFn));
+  }
+
+  bool visitIntUnOp(Instruction &I,
+                    function_ref<AnyValue(const APInt &)> ScalarFn) {
+    return visitUnOp(I, [&](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) {
+    if (Ty->isVectorTy()) {
+      auto &LHSVec = LHS.asAggregate();
+      auto &RHSVec = RHS.asAggregate();
+      std::vector<AnyValue> ResVec;
+      ResVec.reserve(LHSVec.size());
+      for (const auto &[ScalarLHS, ScalarRHS] : zip(LHSVec, RHSVec))
+        ResVec.push_back(ScalarFn(ScalarLHS, ScalarRHS));
+      return std::move(ResVec);
+    }
+    return ScalarFn(LHS, RHS);
+  }
+
+  bool visitBinOp(
+      Instruction &I,
+      function_ref<AnyValue(const AnyValue &, const AnyValue &)> ScalarFn) {
+    return setResult(I, computeBinOp(I.getType(), getValue(I.getOperand(0)),
+                                     getValue(I.getOperand(1)), ScalarFn));
+  }
+
+  bool
+  visitIntBinOp(Instruction &I,
+                function_ref<AnyValue(const APInt &, const APInt &)> ScalarFn) {
+    return visitBinOp(
+        I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+          if (LHS.isPoison() || RHS.isPoison())
+            return AnyValue::poison();
+          return ScalarFn(LHS.asInteger(), RHS.asInteger());
+        });
+  }
+
 public:
   InstExecutor(Context &C, EventHandler &H, Function &F,
                ArrayRef<AnyValue> Args, AnyValue &RetVal)
@@ -110,12 +215,265 @@ class InstExecutor : public InstVisitor<InstExecutor, bool> {
     CallStack.emplace_back(F, /*CallSite=*/nullptr, /*LastFrame=*/nullptr, Args,
                            RetVal, Ctx.getTLIImpl());
   }
+
   bool visitReturnInst(ReturnInst &RI) {
     if (auto *RV = RI.getReturnValue())
       CurrentFrame->RetVal = getValue(RV);
     CurrentFrame->State = FrameState::Exit;
     return Handler.onInstructionExecuted(RI, None);
   }
+
+  bool visitAdd(BinaryOperator &I) {
+    return visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
+      return addNoWrap(LHS, RHS, I.hasNoSignedWrap(), I.hasNoUnsignedWrap());
+    });
+  }
+
+  bool visitSub(BinaryOperator &I) {
+    return visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
+      return subNoWrap(LHS, RHS, I.hasNoSignedWrap(), I.hasNoUnsignedWrap());
+    });
+  }
+
+  bool visitMul(BinaryOperator &I) {
+    return visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
+      return mulNoWrap(LHS, RHS, I.hasNoSignedWrap(), I.hasNoUnsignedWrap());
+    });
+  }
+
+  bool visitSDiv(BinaryOperator &I) {
+    return 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);
+          }
+        });
+  }
+
+  bool visitSRem(BinaryOperator &I) {
+    return 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();
+          }
+
+          return LHSVal.srem(RHSVal);
+        });
+  }
+
+  bool visitUDiv(BinaryOperator &I) {
+    return 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);
+          }
+        });
+  }
+
+  bool visitURem(BinaryOperator &I) {
+    return 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);
+        });
+  }
+
+  bool visitTruncInst(TruncInst &Trunc) {
+    return visitIntUnOp(Trunc, [&](const APInt &Operand) -> AnyValue {
+      unsigned DestBW = Trunc.getType()->getScalarSizeInBits();
+      if (Trunc.hasNoSignedWrap() && Operand.getSignificantBits() > DestBW)
+        return AnyValue::poison();
+      if (Trunc.hasNoUnsignedWrap() && Operand.getActiveBits() > DestBW)
+        return AnyValue::poison();
+      return Operand.trunc(DestBW);
+    });
+  }
+
+  bool visitZExtInst(ZExtInst &ZExt) {
+    return visitIntUnOp(ZExt, [&](const APInt &Operand) -> AnyValue {
+      uint32_t DestBW = ZExt.getDestTy()->getScalarSizeInBits();
+      if (ZExt.hasNonNeg() && Operand.isNegative())
+        return AnyValue::poison();
+      return Operand.zext(DestBW);
+    });
+  }
+
+  bool visitSExtInst(SExtInst &SExt) {
+    return visitIntUnOp(SExt, [&](const APInt &Operand) -> AnyValue {
+      uint32_t DestBW = SExt.getDestTy()->getScalarSizeInBits();
+      return Operand.sext(DestBW);
+    });
+  }
+
+  bool visitAnd(BinaryOperator &I) {
+    return visitIntBinOp(I, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
+      return LHS & RHS;
+    });
+  }
+
+  bool visitXor(BinaryOperator &I) {
+    return visitIntBinOp(I, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
+      return LHS ^ RHS;
+    });
+  }
+
+  bool visitOr(BinaryOperator &I) {
+    return visitIntBinOp(
+        I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
+          if (cast<PossiblyDisjointInst>(I).isDisjoint() && LHS.intersects(RHS))
+            return AnyValue::poison();
+          return LHS | RHS;
+        });
+  }
+
+  bool visitShl(BinaryOperator &I) {
+    return 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);
+        });
+  }
+
+  bool visitLShr(BinaryOperator &I) {
+    return visitIntBinOp(I,
+                         [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
+                           if (RHS.uge(cast<PossiblyExactOperator>(I).isExact()
+                                           ? LHS.countr_zero() + 1
+                                           : LHS.getBitWidth()))
+                             return AnyValue::poison();
+                           return LHS.lshr(RHS);
+                         });
+  }
+
+  bool visitAShr(BinaryOperator &I) {
+    return visitIntBinOp(I,
+                         [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
+                           if (RHS.uge(cast<PossiblyExactOperator>(I).isExact()
+                                           ? LHS.countr_zero() + 1
+                                           : LHS.getBitWidth()))
+                             return AnyValue::poison();
+                           return LHS.ashr(RHS);
+                         });
+  }
+
+  bool visitSelect(SelectInst &SI) {
+    // TODO: handle fast-math flags.
+    if (SI.getCondition()->getType()->isIntegerTy(1)) {
+      switch (getValue(SI.getCondition()).asBoolean()) {
+      case BooleanKind::True:
+        return setResult(SI, getValue(SI.getTrueValue()));
+      case BooleanKind::False:
+        return setResult(SI, getValue(SI.getFalseValue()));
+      case BooleanKind::Poison:
+        return setResult(SI, AnyValue::getPoisonValue(Ctx, SI.getType()));
+      }
+    }
+
+    auto &Cond = getValue(SI.getCondition()).asAggregate();
+    auto &TV = getValue(SI.getTrueValue()).asAggregate();
+    auto &FV = getValue(SI.getFalseValue()).asAggregate();
+    std::vector<AnyValue> Res;
+    size_t Len = Cond.size();
+    Res.reserve(Len);
+    for (uint32_t I = 0; I != Len; ++I) {
+      switch (Cond[I].asBoolean()) {
+      case BooleanKind::True:
+        Res.push_back(TV[I]);
+        break;
+      case BooleanKind::False:
+        Res.push_back(FV[I]);
+        break;
+      case BooleanKind::Poison:
+        Res.push_back(AnyValue::poison());
+        break;
+      }
+    }
+    return setResult(SI, std::move(Res));
+  }
+
   bool visitInstruction(Instruction &I) {
     Handler.onUnrecognizedInstruction(I);
     return false;
@@ -157,13 +515,6 @@ class InstExecutor : public InstVisitor<InstExecutor, bool> {
         if (!Status)
           break;
 
-        if (Top.State != FrameState::Pending && !I.isTerminator()) {
-          if (I.getType()->isVoidTy())
-            Handler.onInstructionExecuted(I, None);
-          else
-            Handler.onInstructionExecuted(I, Top.ValueMap.at(&I));
-        }
-
         // A function call or return has occurred.
         // We need to exit the inner loop and switch to a different frame.
         if (Top.State != FrameState::Running)
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index 0828941538798..278a569b1edda 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -54,6 +54,9 @@ enum class StorageKind {
   Aggregate, // Struct, Array or Vector
 };
 
+/// Tri-state boolean value.
+enum class BooleanKind { False, True, Poison };
+
 class Pointer {
   // The underlying memory object. It can be null for invalid or dangling
   // pointers.
@@ -140,6 +143,12 @@ class [[nodiscard]] AnyValue {
     assert(I < AggVal.size() && "Index out of bounds");
     return AggVal[I];
   }
+
+  BooleanKind asBoolean() const {
+    if (isPoison())
+      return BooleanKind::Poison;
+    return asInteger().isZero() ? BooleanKind::False : BooleanKind::True;
+  }
 };
 
 inline raw_ostream &operator<<(raw_ostream &OS, const AnyValue &V) {

>From d95859c6e78d2daa172fed10c1ac0a9131cc8bb7 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Fri, 13 Feb 2026 19:52:12 +0800
Subject: [PATCH 2/2] [llubi] Update test. NFC.

---
 llvm/test/tools/llubi/int_arith.ll | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/llvm/test/tools/llubi/int_arith.ll b/llvm/test/tools/llubi/int_arith.ll
index 0dc45d9d6fa8e..db16d0c9cf2c5 100644
--- a/llvm/test/tools/llubi/int_arith.ll
+++ b/llvm/test/tools/llubi/int_arith.ll
@@ -47,6 +47,7 @@ define void @main() {
 
   %zext = zext i8 -1 to i32
   %zext_poison = zext i8 poison to i32
+  %zext_nneg = zext nneg i8 1 to i32
   %zext_nneg_poison = zext nneg i8 -1 to i32
 
   %sext = sext i8 -1 to i32
@@ -117,6 +118,7 @@ define void @main() {
 ; CHECK-NEXT:   %trunc_nuw_poison = trunc nuw i32 511 to i8 => poison
 ; CHECK-NEXT:   %zext = zext i8 -1 to i32 => i32 255
 ; CHECK-NEXT:   %zext_poison = zext i8 poison to i32 => poison
+; CHECK-NEXT:   %zext_nneg = zext nneg i8 1 to i32 => i32 1
 ; CHECK-NEXT:   %zext_nneg_poison = zext nneg i8 -1 to i32 => poison
 ; CHECK-NEXT:   %sext = sext i8 -1 to i32 => i32 -1
 ; CHECK-NEXT:   %sext_poison = sext i8 poison to i32 => poison



More information about the llvm-commits mailing list