[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