[llvm] [llubi] Add support for constant expressions (PR #203746)

Yingwei Zheng via llvm-commits llvm-commits at lists.llvm.org
Sun Jun 14 03:22:32 PDT 2026


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

>From b24fa26a2709fe982bf6bc4cb706d4d7681faa96 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 8 Jun 2026 09:47:23 +0800
Subject: [PATCH 1/2] [llubi] Add support for constant expressions

---
 .../llubi/global_constexpr_initializer.ll     | 108 +++++-
 llvm/test/tools/llubi/unsupported_constant.ll |  12 +
 llvm/tools/llubi/lib/Context.cpp              | 346 +++++++++++++++++-
 llvm/tools/llubi/lib/Context.h                |  13 +
 llvm/tools/llubi/lib/Interpreter.cpp          | 204 +----------
 llvm/tools/llubi/lib/Value.h                  |  36 ++
 6 files changed, 513 insertions(+), 206 deletions(-)
 create mode 100644 llvm/test/tools/llubi/unsupported_constant.ll

diff --git a/llvm/test/tools/llubi/global_constexpr_initializer.ll b/llvm/test/tools/llubi/global_constexpr_initializer.ll
index c4b2ee2e78384..ebc7cc9997aa6 100644
--- a/llvm/test/tools/llubi/global_constexpr_initializer.ll
+++ b/llvm/test/tools/llubi/global_constexpr_initializer.ll
@@ -1,10 +1,114 @@
-; RUN: not llubi < %s 2>&1 | FileCheck %s
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+target datalayout = "p:64:64:64"
 
 @value = global i32 0
 @aggregate = global [1 x ptr] [ptr getelementptr (i32, ptr @value, i64 1)]
+ at trunc = global i8 trunc (i64 ptrtoaddr (ptr @value to i64) to i8)
+ at bitcast = global double bitcast (i64 ptrtoaddr (ptr @value to i64) to double)
+ at insert = global <2 x ptr> insertelement (<2 x ptr> zeroinitializer, ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7))
+ at extract = global ptr extractelement (<2 x ptr> insertelement (<2 x ptr> zeroinitializer, ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), i64 1)
+ at shuffle = global <2 x ptr> shufflevector (<2 x ptr> insertelement (<2 x ptr> zeroinitializer, ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), <2 x ptr> poison, <2 x i32> <i32 1, i32 1>)
+ at gep = global ptr getelementptr (i32, ptr @value, i64 1)
+ at add = global i64 add (i64 ptrtoaddr (ptr @value to i64), i64 8)
+ at sub = global i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 8)
+ at xor = global i64 xor (i64 ptrtoaddr (ptr @value to i64), i64 8)
 
 define void @main() {
+  %load_aggregate = load ptr, ptr @aggregate
+  %val_aggregage = getelementptr i8, ptr getelementptr (i32, ptr @value, i64 1), i64 0
+
+  %load_trunc = load i8, ptr @trunc
+  %val_trunc = add i8 trunc (i64 ptrtoaddr (ptr @value to i64) to i8), 0
+  %val_trunc_poison = add i8 trunc (i64 sub nuw (i64 ptrtoaddr (ptr @value to i64), i64 9) to i8), 0
+  %val_trunc_vec = add <2 x i8> trunc (<2 x i64> insertelement (<2 x i64> poison, i64 ptrtoaddr (ptr @value to i64), i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)) to <2 x i8>), zeroinitializer
+
+  %load_bitcast = load double, ptr @bitcast
+  %val_bitcast = fadd double bitcast (i64 ptrtoaddr (ptr @value to i64) to double), -0.0
+  %load_insert = load <2 x ptr>, ptr @insert
+
+  %val_insert = getelementptr i8, <2 x ptr> insertelement (<2 x ptr> zeroinitializer, ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), i64 0
+  %val_insert_poison1 = getelementptr i8, <2 x ptr> insertelement (<2 x ptr> zeroinitializer, ptr @value, i64 sub nuw (i64 ptrtoaddr (ptr @value to i64), i64 9)), i64 0
+  %val_insert_poison2 = getelementptr i8, <2 x ptr> insertelement (<2 x ptr> zeroinitializer, ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 6)), i64 0
+  %val_insert_scalable = getelementptr i8, <vscale x 2 x ptr> insertelement (<vscale x 2 x ptr> zeroinitializer, ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 8)), i64 0
+
+  %load_extract = load ptr, ptr @extract
+  %val_extract = getelementptr i8, ptr extractelement (<2 x ptr> insertelement (<2 x ptr> zeroinitializer, ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), i64 1), i64 0
+  %val_extract_poison1 = getelementptr i8, ptr extractelement (<2 x ptr> insertelement (<2 x ptr> zeroinitializer, ptr @value, i64 1), i64 sub nuw (i64 ptrtoaddr (ptr @value to i64), i64 9)), i64 0
+  %val_extract_poison2 = getelementptr i8, ptr extractelement (<2 x ptr> insertelement (<2 x ptr> zeroinitializer, ptr @value, i64 1), i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 6)), i64 0
+  %val_extract_scalable = getelementptr i8, ptr extractelement (<vscale x 2 x ptr> insertelement (<vscale x 2 x ptr> zeroinitializer, ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 8)), i64 1), i64 0
+
+  %load_shuffle = load <2 x ptr>, ptr @shuffle
+  %val_shuffle = getelementptr i8, <2 x ptr> shufflevector (<2 x ptr> insertelement (<2 x ptr> zeroinitializer, ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), <2 x ptr> poison, <2 x i32> <i32 1, i32 1>), i64 0
+  %val_shuffle_mixed = getelementptr i8, <2 x ptr> shufflevector (<2 x ptr> insertelement (<2 x ptr> zeroinitializer, ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), <2 x ptr> zeroinitializer, <2 x i32> <i32 1, i32 2>), i64 0
+
+  %load_gep = load ptr, ptr @gep
+  %val_gep = getelementptr i8, ptr getelementptr (i32, ptr @value, i64 1), i64 0
+  %val_gep_inbounds = getelementptr i8, ptr getelementptr inbounds (i32, ptr @value, i64 1), i64 0
+  %val_gep_inbounds_oob = getelementptr i8, ptr getelementptr inbounds (i32, ptr @value, i64 2), i64 0
+  %val_gep_vec1 = getelementptr i8, <2 x ptr> getelementptr (i32, ptr @value, <2 x i64> <i64 1, i64 2>), i64 0
+  %val_gep_vec2 = getelementptr i8, <2 x ptr> getelementptr (i32, <2 x ptr> insertelement (<2 x ptr> zeroinitializer, ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), i64 1), i64 0
+
+  %load_add = load i64, ptr @add
+  %val_add = add i64 add (i64 ptrtoaddr (ptr @value to i64), i64 8), 0
+  %val_add_nuw_nsw = add i64 add nuw nsw (i64 ptrtoaddr (ptr @value to i64), i64 8), 0
+  %val_add_nsw_poison = add i64 add nsw (i64 ptrtoaddr (ptr @value to i64), i64 9223372036854775800), 0
+  %val_add_nuw_poison = add i64 add nuw (i64 ptrtoaddr (ptr @value to i64), i64 -7), 0
+
+  %load_sub = load i64, ptr @sub
+  %val_sub = add i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 8), 0
+  %val_sub_nuw_nsw = add i64 sub nuw nsw (i64 ptrtoaddr (ptr @value to i64), i64 8), 0
+  %val_sub_nsw_poison = add i64 sub nsw (i64 ptrtoaddr (ptr @value to i64), i64 -9223372036854775800), 0
+  %val_sub_nuw_poison = add i64 sub nuw (i64 ptrtoaddr (ptr @value to i64), i64 9), 0
+
+  %load_xor = load i64, ptr @xor
+  %val_xor = add i64 xor (i64 ptrtoaddr (ptr @value to i64), i64 8), 0
+  %val_xor_vec_poison = add <2 x i64> xor (<2 x i64> insertelement (<2 x i64> zeroinitializer, i64 sub nuw (i64 ptrtoaddr (ptr @value to i64), i64 9), i64 1), <2 x i64> splat(i64 1)), zeroinitializer
+
   ret void
 }
 
-; CHECK: error: Failed to initialize global values
+; CHECK: Entering function: main
+; CHECK-NEXT:   %load_aggregate = load ptr, ptr @aggregate, align 8 => ptr 0xC [@value + 4]
+; CHECK-NEXT:   %val_aggregage = getelementptr i8, ptr getelementptr (i32, ptr @value, i64 1), i64 0 => ptr 0xC [@value + 4]
+; CHECK-NEXT:   %load_trunc = load i8, ptr @trunc, align 1 => i8 8
+; CHECK-NEXT:   %val_trunc = add i8 trunc (i64 ptrtoaddr (ptr @value to i64) to i8), 0 => i8 8
+; CHECK-NEXT:   %val_trunc_poison = add i8 trunc (i64 sub nuw (i64 ptrtoaddr (ptr @value to i64), i64 9) to i8), 0 => poison
+; CHECK-NEXT:   %val_trunc_vec = add <2 x i8> <i8 trunc (i64 extractelement (<2 x i64> insertelement (<2 x i64> poison, i64 ptrtoaddr (ptr @value to i64), i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), i32 0) to i8), i8 trunc (i64 extractelement (<2 x i64> insertelement (<2 x i64> poison, i64 ptrtoaddr (ptr @value to i64), i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), i32 1) to i8)>, zeroinitializer => { poison, i8 8 }
+; CHECK-NEXT:   %load_bitcast = load double, ptr @bitcast, align 8 => double 3.952530e-323
+; CHECK-NEXT:   %val_bitcast = fadd double bitcast (i64 ptrtoaddr (ptr @value to i64) to double), -0.000000e+00 => double 3.952530e-323
+; CHECK-NEXT:   %load_insert = load <2 x ptr>, ptr @insert, align 16 => { ptr 0x0 [nullary], ptr 0x8 [@value] }
+; CHECK-NEXT:   %val_insert = getelementptr i8, <2 x ptr> insertelement (<2 x ptr> splat (ptr null), ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), i64 0 => { ptr 0x0 [nullary], ptr 0x8 [@value] }
+; CHECK-NEXT:   %val_insert_poison1 = getelementptr i8, <2 x ptr> insertelement (<2 x ptr> splat (ptr null), ptr @value, i64 sub nuw (i64 ptrtoaddr (ptr @value to i64), i64 9)), i64 0 => { poison, poison }
+; CHECK-NEXT:   %val_insert_poison2 = getelementptr i8, <2 x ptr> insertelement (<2 x ptr> splat (ptr null), ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 6)), i64 0 => { poison, poison }
+; CHECK-NEXT:   %val_insert_scalable = getelementptr i8, <vscale x 2 x ptr> insertelement (<vscale x 2 x ptr> splat (ptr null), ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 8)), i64 0 => { ptr 0x8 [@value], ptr 0x0 [nullary], ptr 0x0 [nullary], ptr 0x0 [nullary], ptr 0x0 [nullary], ptr 0x0 [nullary], ptr 0x0 [nullary], ptr 0x0 [nullary] }
+; CHECK-NEXT:   %load_extract = load ptr, ptr @extract, align 8 => ptr 0x8 [@value]
+; CHECK-NEXT:   %val_extract = getelementptr i8, ptr extractelement (<2 x ptr> insertelement (<2 x ptr> splat (ptr null), ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), i64 1), i64 0 => ptr 0x8 [@value]
+; CHECK-NEXT:   %val_extract_poison1 = getelementptr i8, ptr extractelement (<2 x ptr> <ptr null, ptr @value>, i64 sub nuw (i64 ptrtoaddr (ptr @value to i64), i64 9)), i64 0 => poison
+; CHECK-NEXT:   %val_extract_poison2 = getelementptr i8, ptr extractelement (<2 x ptr> <ptr null, ptr @value>, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 6)), i64 0 => poison
+; CHECK-NEXT:   %val_extract_scalable = getelementptr i8, ptr extractelement (<vscale x 2 x ptr> insertelement (<vscale x 2 x ptr> splat (ptr null), ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 8)), i64 1), i64 0 => ptr 0x0 [nullary]
+; CHECK-NEXT:   %load_shuffle = load <2 x ptr>, ptr @shuffle, align 16 => { ptr 0x8 [@value], ptr 0x8 [@value] }
+; CHECK-NEXT:   %val_shuffle = getelementptr i8, <2 x ptr> <ptr extractelement (<2 x ptr> insertelement (<2 x ptr> splat (ptr null), ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), i32 1), ptr extractelement (<2 x ptr> insertelement (<2 x ptr> splat (ptr null), ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), i32 1)>, i64 0 => { ptr 0x8 [@value], ptr 0x8 [@value] }
+; CHECK-NEXT:   %val_shuffle_mixed = getelementptr i8, <2 x ptr> <ptr extractelement (<2 x ptr> insertelement (<2 x ptr> splat (ptr null), ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), i32 1), ptr null>, i64 0 => { ptr 0x8 [@value], ptr 0x0 [nullary] }
+; CHECK-NEXT:   %load_gep = load ptr, ptr @gep, align 8 => ptr 0xC [@value + 4]
+; CHECK-NEXT:   %val_gep = getelementptr i8, ptr getelementptr (i32, ptr @value, i64 1), i64 0 => ptr 0xC [@value + 4]
+; CHECK-NEXT:   %val_gep_inbounds = getelementptr i8, ptr getelementptr inbounds (i32, ptr @value, i64 1), i64 0 => ptr 0xC [@value + 4]
+; CHECK-NEXT:   %val_gep_inbounds_oob = getelementptr i8, ptr getelementptr inbounds (i32, ptr @value, i64 2), i64 0 => poison
+; CHECK-NEXT:   %val_gep_vec1 = getelementptr i8, <2 x ptr> getelementptr (i32, ptr @value, <2 x i64> <i64 1, i64 2>), i64 0 => { ptr 0xC [@value + 4], ptr 0x10 [@value + 8] }
+; CHECK-NEXT:   %val_gep_vec2 = getelementptr i8, <2 x ptr> getelementptr (i32, <2 x ptr> insertelement (<2 x ptr> splat (ptr null), ptr @value, i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 7)), <2 x i64> splat (i64 1)), i64 0 => { ptr 0x4 [nullary], ptr 0xC [@value + 4] }
+; CHECK-NEXT:   %load_add = load i64, ptr @add, align 4 => i64 16
+; CHECK-NEXT:   %val_add = add i64 add (i64 ptrtoaddr (ptr @value to i64), i64 8), 0 => i64 16
+; CHECK-NEXT:   %val_add_nuw_nsw = add i64 add nuw nsw (i64 ptrtoaddr (ptr @value to i64), i64 8), 0 => i64 16
+; CHECK-NEXT:   %val_add_nsw_poison = add i64 add nsw (i64 ptrtoaddr (ptr @value to i64), i64 9223372036854775800), 0 => poison
+; CHECK-NEXT:   %val_add_nuw_poison = add i64 add nuw (i64 ptrtoaddr (ptr @value to i64), i64 -7), 0 => poison
+; CHECK-NEXT:   %load_sub = load i64, ptr @sub, align 4 => i64 0
+; CHECK-NEXT:   %val_sub = add i64 sub (i64 ptrtoaddr (ptr @value to i64), i64 8), 0 => i64 0
+; CHECK-NEXT:   %val_sub_nuw_nsw = add i64 sub nuw nsw (i64 ptrtoaddr (ptr @value to i64), i64 8), 0 => i64 0
+; CHECK-NEXT:   %val_sub_nsw_poison = add i64 sub nsw (i64 ptrtoaddr (ptr @value to i64), i64 -9223372036854775800), 0 => poison
+; CHECK-NEXT:   %val_sub_nuw_poison = add i64 sub nuw (i64 ptrtoaddr (ptr @value to i64), i64 9), 0 => poison
+; CHECK-NEXT:   %load_xor = load i64, ptr @xor, align 4 => i64 0
+; CHECK-NEXT:   %val_xor = add i64 xor (i64 ptrtoaddr (ptr @value to i64), i64 8), 0 => i64 0
+; CHECK-NEXT:   %val_xor_vec_poison = add <2 x i64> <i64 1, i64 xor (i64 sub nuw (i64 ptrtoaddr (ptr @value to i64), i64 9), i64 1)>, zeroinitializer => { i64 1, poison }
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/unsupported_constant.ll b/llvm/test/tools/llubi/unsupported_constant.ll
new file mode 100644
index 0000000000000..96d23cff4904a
--- /dev/null
+++ b/llvm/test/tools/llubi/unsupported_constant.ll
@@ -0,0 +1,12 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %res = add i32 0, undef
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   %res = add i32 0, undef at @main <stdin>:5
+; CHECK-NEXT: Error: Unsupported constant: i32 undef.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 7b9d0d849e5f3..570ae980dbd5c 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -11,6 +11,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "Context.h"
+#include "llvm/IR/GetElementPtrTypeIterator.h"
+#include "llvm/IR/Instructions.h"
 #include "llvm/Support/MathExtras.h"
 
 namespace llvm::ubi {
@@ -89,12 +91,9 @@ std::optional<AnyValue> Context::getConstantValueImpl(Constant *C) {
   if (isa<PoisonValue>(C))
     return AnyValue::getPoisonValue(*this, C->getType());
 
-  if (isa<ConstantAggregateZero>(C))
+  if (isa<ConstantAggregateZero, ConstantPointerNull>(C))
     return AnyValue::getNullValue(*this, C->getType());
 
-  if (isa<ConstantPointerNull>(C))
-    return Pointer::null(C->getType()->getPointerAddressSpace(), DL);
-
   if (auto *CI = dyn_cast<ConstantInt>(C)) {
     if (auto *VecTy = dyn_cast<VectorType>(CI->getType()))
       return std::vector<AnyValue>(getEVL(VecTy->getElementCount()),
@@ -142,9 +141,182 @@ std::optional<AnyValue> Context::getConstantValueImpl(Constant *C) {
   if (auto *F = dyn_cast<Function>(C))
     return FuncAddrMap.at(F);
 
+  if (auto *CE = dyn_cast<ConstantExpr>(C))
+    return evaluateConstantExpression(CE);
+
   return std::nullopt;
 }
 
+std::optional<AnyValue> Context::evaluateConstantExpression(ConstantExpr *CE) {
+  unsigned Opc = CE->getOpcode();
+  switch (Opc) {
+  case Instruction::Trunc: {
+    const AnyValue *Src = getConstantValue(CE->getOperand(0));
+    if (!Src)
+      return std::nullopt;
+    if (Src->isPoison())
+      return AnyValue::poison();
+    unsigned BitWidth = CE->getType()->getScalarSizeInBits();
+    if (Src->isInteger())
+      return AnyValue(Src->asInteger().trunc(BitWidth));
+    std::vector<AnyValue> Vec = Src->asAggregate();
+    for (auto &V : Vec) {
+      if (V.isInteger())
+        V = V.asInteger().trunc(BitWidth);
+    }
+    return AnyValue(std::move(Vec));
+  }
+  case Instruction::BitCast: {
+    Constant *SrcOp = CE->getOperand(0);
+    const AnyValue *Src = getConstantValue(SrcOp);
+    if (!Src)
+      return std::nullopt;
+    SmallVector<Byte> Bytes;
+    Bytes.resize(getEffectiveTypeStoreSize(CE->getType()), Byte::concrete(0));
+    toBytes(*Src, SrcOp->getType(), Bytes);
+    return fromBytes(Bytes, CE->getType());
+  }
+  case Instruction::InsertElement: {
+    const AnyValue *Src = getConstantValue(CE->getOperand(0));
+    if (!Src)
+      return std::nullopt;
+    const AnyValue *Val = getConstantValue(CE->getOperand(1));
+    if (!Val)
+      return std::nullopt;
+    const AnyValue *Idx = getConstantValue(CE->getOperand(2));
+    if (!Idx)
+      return std::nullopt;
+    auto &SrcVec = Src->asAggregate();
+    if (Idx->isPoison() || Idx->asInteger().uge(SrcVec.size()))
+      return AnyValue::getPoisonValue(*this, CE->getType());
+    std::vector<AnyValue> ResVec = SrcVec;
+    ResVec[Idx->asInteger().getZExtValue()] = *Val;
+    return AnyValue(std::move(ResVec));
+  }
+  case Instruction::ExtractElement: {
+    const AnyValue *Src = getConstantValue(CE->getOperand(0));
+    if (!Src)
+      return std::nullopt;
+    const AnyValue *Idx = getConstantValue(CE->getOperand(1));
+    if (!Idx)
+      return std::nullopt;
+    auto &SrcVec = Src->asAggregate();
+    if (Idx->isPoison() || Idx->asInteger().uge(SrcVec.size()))
+      return AnyValue::getPoisonValue(*this, CE->getType());
+    return SrcVec[Idx->asInteger().getZExtValue()];
+  }
+  case Instruction::ShuffleVector: {
+    const AnyValue *LHS = getConstantValue(CE->getOperand(0));
+    if (!LHS)
+      return std::nullopt;
+    const AnyValue *RHS = getConstantValue(CE->getOperand(1));
+    if (!RHS)
+      return std::nullopt;
+    auto &LHSVec = LHS->asAggregate();
+    auto &RHSVec = RHS->asAggregate();
+    uint32_t Size = cast<VectorType>(CE->getOperand(0)->getType())
+                        ->getElementCount()
+                        .getKnownMinValue();
+    std::vector<AnyValue> Res;
+    uint32_t DstLen =
+        getEVL(cast<VectorType>(CE->getType())->getElementCount());
+    Res.reserve(DstLen);
+    uint32_t Stride = CE->getShuffleMask().size();
+    // For scalable vectors, we need to repeat the shuffle mask until we fill
+    // the destination vector.
+    for (uint32_t Off = 0; Off != DstLen; Off += Stride) {
+      for (int Idx : CE->getShuffleMask()) {
+        if (Idx == PoisonMaskElem)
+          Res.push_back(AnyValue::poison());
+        else if (Idx < static_cast<int>(Size))
+          Res.push_back(LHSVec[Idx]);
+        else
+          Res.push_back(RHSVec[Idx - Size]);
+      }
+    }
+    return AnyValue(std::move(Res));
+  }
+  case Instruction::GetElementPtr: {
+    // Temporary variable for reference to poison values when the subexpression
+    // cannot be evaluated. As the reference will be consumed immediately, we
+    // don't need to store them into a list.
+    AnyValue PoisonValue;
+    AnyValue Res =
+        computeGEP(*cast<GEPOperator>(CE), [&](Value *V) -> const AnyValue & {
+          const AnyValue *Val = getConstantValue(cast<Constant>(V));
+          if (Val)
+            return *Val;
+          PoisonValue = AnyValue::getPoisonValue(*this, V->getType());
+          return PoisonValue;
+        });
+    if (!PoisonValue.isNone())
+      return std::nullopt;
+    return std::move(Res);
+  }
+  case Instruction::PtrToAddr: {
+    const AnyValue *Src = getConstantValue(CE->getOperand(0));
+    if (!Src)
+      return std::nullopt;
+    if (Src->isPoison())
+      return AnyValue::poison();
+    unsigned BitWidth = CE->getType()->getScalarSizeInBits();
+    if (Src->isPointer())
+      return Src->asPointer().address().trunc(BitWidth);
+    std::vector<AnyValue> Vec = Src->asAggregate();
+    for (auto &V : Vec) {
+      if (V.isPointer())
+        V = V.asPointer().address().trunc(BitWidth);
+    }
+    return AnyValue(std::move(Vec));
+  }
+  default:
+    assert(Instruction::isBinaryOp(Opc) && "Must be binary operator?");
+    const AnyValue *LHS = getConstantValue(CE->getOperand(0));
+    if (!LHS)
+      return std::nullopt;
+    const AnyValue *RHS = getConstantValue(CE->getOperand(1));
+    if (!RHS)
+      return std::nullopt;
+
+    bool HasNUW = false;
+    bool HasNSW = false;
+    if (auto *OBO = dyn_cast<OverflowingBinaryOperator>(CE)) {
+      HasNUW = OBO->hasNoUnsignedWrap();
+      HasNSW = OBO->hasNoSignedWrap();
+    }
+
+    auto ScalarEval = [&](const AnyValue &LHS,
+                          const AnyValue &RHS) -> AnyValue {
+      if (LHS.isPoison() || RHS.isPoison())
+        return AnyValue::poison();
+      auto &LHSVal = LHS.asInteger();
+      auto &RHSVal = RHS.asInteger();
+      switch (Opc) {
+      case Instruction::Add:
+        return addNoWrap(LHSVal, RHSVal, HasNSW, HasNUW);
+      case Instruction::Sub:
+        return subNoWrap(LHSVal, RHSVal, HasNSW, HasNUW);
+      case Instruction::Xor:
+        return LHSVal ^ RHSVal;
+      default:
+        llvm_unreachable("Unsupported opcode in constant expression.");
+      }
+    };
+
+    if (CE->getType()->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(ScalarEval(ScalarLHS, ScalarRHS));
+      return std::move(ResVec);
+    }
+
+    return ScalarEval(*LHS, *RHS);
+  }
+}
+
 const AnyValue *Context::getConstantValue(Constant *C) {
   auto It = ConstCache.find(C);
   if (It != ConstCache.end())
@@ -547,6 +719,172 @@ void Context::freeze(AnyValue &Val, Type *Ty) {
   }
 }
 
+AnyValue Context::computePtrAdd(const Pointer &Ptr, const APInt &Offset,
+                                GEPNoWrapFlags Flags,
+                                AnyValue &AccumulatedOffset) {
+  if (Offset.isZero())
+    return Ptr;
+  APInt IndexBits = Ptr.address().trunc(Offset.getBitWidth());
+  auto NewIndex =
+      addNoWrap(IndexBits, Offset, /*HasNSW=*/false, Flags.hasNoUnsignedWrap());
+  if (NewIndex.isPoison())
+    return AnyValue::poison();
+  if (Flags.hasNoUnsignedSignedWrap()) {
+    // The successive addition of the current address, truncated to the
+    // pointer index type and interpreted as an unsigned number, and each
+    // offset, interpreted as a signed number, does not wrap the pointer index
+    // type.
+    if (Offset.isNonNegative() ? NewIndex.asInteger().ult(IndexBits)
+                               : NewIndex.asInteger().ugt(IndexBits))
+      return AnyValue::poison();
+  }
+  APInt NewAddr = Ptr.address();
+  NewAddr.insertBits(NewIndex.asInteger(), 0);
+
+  MemoryObject *MO = nullptr;
+  if (Flags.isInBounds()) {
+    MO = checkProvenance(
+        Ptr, [](const Provenance &) { return true; },
+        /*HasSideEffect=*/false);
+    if (!MO || !MO->inBounds(NewAddr))
+      return AnyValue::poison();
+  }
+
+  if (!AccumulatedOffset.isPoison()) {
+    AccumulatedOffset =
+        addNoWrap(AccumulatedOffset.asInteger(), Offset,
+                  Flags.hasNoUnsignedSignedWrap(), Flags.hasNoUnsignedWrap());
+    if (AccumulatedOffset.isPoison())
+      return AnyValue::poison();
+  }
+
+  // Should not expose provenance here even if the new address doesn't point
+  // to the original object.
+  auto Res = Ptr.getWithNewAddr(NewAddr);
+  if (MO) {
+    auto &Prov = Res.provenance();
+    if (Prov.isWildcard() && !Prov.getMemoryObject())
+      Res = Res.getWithNewProvenance(Prov.getWithKnownMemoryObject(*MO));
+  }
+  return Res;
+}
+
+AnyValue Context::computePtrAdd(const AnyValue &Ptr, const APInt &Offset,
+                                GEPNoWrapFlags Flags,
+                                AnyValue &AccumulatedOffset) {
+  if (Ptr.isPoison())
+    return AnyValue::poison();
+  return computePtrAdd(Ptr.asPointer(), Offset, Flags, AccumulatedOffset);
+}
+
+AnyValue Context::computeScaledPtrAdd(const AnyValue &Ptr,
+                                      const AnyValue &Index, const APInt &Scale,
+                                      GEPNoWrapFlags Flags,
+                                      AnyValue &AccumulatedOffset) {
+  if (Ptr.isPoison() || Index.isPoison())
+    return AnyValue::poison();
+  assert(Ptr.isPointer() && Index.isInteger() && "Unexpected type.");
+  if (Scale.isOne())
+    return computePtrAdd(Ptr, Index.asInteger(), Flags, AccumulatedOffset);
+  auto ScaledOffset =
+      mulNoWrap(Index.asInteger(), Scale, Flags.hasNoUnsignedSignedWrap(),
+                Flags.hasNoUnsignedWrap());
+  if (ScaledOffset.isPoison())
+    return AnyValue::poison();
+  return computePtrAdd(Ptr, ScaledOffset.asInteger(), Flags, AccumulatedOffset);
+}
+
+static AnyValue canonicalizeIndex(const AnyValue &Idx, unsigned IndexBitWidth,
+                                  GEPNoWrapFlags Flags) {
+  if (Idx.isPoison())
+    return AnyValue::poison();
+  auto &IdxInt = Idx.asInteger();
+  if (IdxInt.getBitWidth() == IndexBitWidth)
+    return Idx;
+  if (IdxInt.getBitWidth() > IndexBitWidth) {
+    if (Flags.hasNoUnsignedSignedWrap() && !IdxInt.isSignedIntN(IndexBitWidth))
+      return AnyValue::poison();
+
+    if (Flags.hasNoUnsignedWrap() && !IdxInt.isIntN(IndexBitWidth))
+      return AnyValue::poison();
+
+    return IdxInt.trunc(IndexBitWidth);
+  }
+  return IdxInt.sext(IndexBitWidth);
+}
+
+AnyValue
+Context::computeGEP(GEPOperator &GEP,
+                    function_ref<const AnyValue &(Value *V)> GetValue) {
+  uint32_t IndexBitWidth =
+      DL.getIndexSizeInBits(GEP.getType()->getPointerAddressSpace());
+  GEPNoWrapFlags Flags = GEP.getNoWrapFlags();
+  AnyValue Res = GetValue(GEP.getPointerOperand());
+  AnyValue AccumulatedOffset = APInt(IndexBitWidth, 0);
+  if (Res.isAggregate())
+    AccumulatedOffset =
+        AnyValue::getVectorSplat(AccumulatedOffset, Res.asAggregate().size());
+  auto ApplyScaledOffset = [&](const AnyValue &Index, const APInt &Scale) {
+    if (Index.isAggregate() && !Res.isAggregate()) {
+      Res = AnyValue::getVectorSplat(Res, Index.asAggregate().size());
+      AccumulatedOffset = AnyValue::getVectorSplat(AccumulatedOffset,
+                                                   Index.asAggregate().size());
+    }
+    if (Index.isAggregate() && Res.isAggregate()) {
+      for (auto &&[ResElem, IndexElem, OffsetElem] :
+           zip(Res.asAggregate(), Index.asAggregate(),
+               AccumulatedOffset.asAggregate()))
+        ResElem = computeScaledPtrAdd(
+            ResElem, canonicalizeIndex(IndexElem, IndexBitWidth, Flags), Scale,
+            Flags, OffsetElem);
+    } else {
+      AnyValue CanonicalIndex = canonicalizeIndex(Index, IndexBitWidth, Flags);
+      if (Res.isAggregate()) {
+        for (auto &&[ResElem, OffsetElem] :
+             zip(Res.asAggregate(), AccumulatedOffset.asAggregate()))
+          ResElem = computeScaledPtrAdd(ResElem, CanonicalIndex, Scale, Flags,
+                                        OffsetElem);
+      } else {
+        Res = computeScaledPtrAdd(Res, CanonicalIndex, Scale, Flags,
+                                  AccumulatedOffset);
+      }
+    }
+  };
+
+  for (gep_type_iterator GTI = gep_type_begin(GEP), GTE = gep_type_end(GEP);
+       GTI != GTE; ++GTI) {
+    Value *V = GTI.getOperand();
+
+    // Fast path for zero offsets.
+    if (auto *CI = dyn_cast<ConstantInt>(V)) {
+      if (CI->isZero())
+        continue;
+    }
+    if (isa<ConstantAggregateZero>(V))
+      continue;
+
+    // Handle a struct index, which adds its field offset to the pointer.
+    if (StructType *STy = GTI.getStructTypeOrNull()) {
+      unsigned ElementIdx = cast<ConstantInt>(V)->getZExtValue();
+      const StructLayout *SL = DL.getStructLayout(STy);
+      // Element offset is in bytes.
+      ApplyScaledOffset(APInt(IndexBitWidth, SL->getElementOffset(ElementIdx)),
+                        APInt(IndexBitWidth, 1));
+      continue;
+    }
+
+    // Truncate if type size exceeds index space.
+    // TODO: Should be documented in LangRef: GEPs with nowrap flags should
+    // return poison when the type size exceeds index space.
+    TypeSize Offset = GTI.getSequentialElementStride(DL);
+    APInt Scale(IndexBitWidth, getEffectiveTypeSize(Offset),
+                /*isSigned=*/false, /*implicitTrunc=*/true);
+    if (!Scale.isZero())
+      ApplyScaledOffset(GetValue(V), Scale);
+  }
+  return Res;
+}
+
 MemoryObject::~MemoryObject() = default;
 MemoryObject::MemoryObject(uint64_t Addr, uint64_t Size, StringRef Name,
                            unsigned AS, MemInitKind InitKind,
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index cc947bd6af51a..5f8b79a6b183c 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -15,6 +15,7 @@
 #include "llvm/AsmParser/AsmParserContext.h"
 #include "llvm/IR/FPEnv.h"
 #include "llvm/IR/Module.h"
+#include "llvm/IR/Operator.h"
 #include <map>
 #include <optional>
 #include <random>
@@ -279,6 +280,14 @@ class Context {
   void toBytes(const AnyValue &Val, Type *Ty, uint32_t OffsetInBits,
                MutableBytesView Bytes, bool PaddingBits);
 
+  AnyValue computePtrAdd(const Pointer &Ptr, const APInt &Offset,
+                         GEPNoWrapFlags Flags, AnyValue &AccumulatedOffset);
+  AnyValue computePtrAdd(const AnyValue &Ptr, const APInt &Offset,
+                         GEPNoWrapFlags Flags, AnyValue &AccumulatedOffset);
+  AnyValue computeScaledPtrAdd(const AnyValue &Ptr, const AnyValue &Index,
+                               const APInt &Scale, GEPNoWrapFlags Flags,
+                               AnyValue &AccumulatedOffset);
+
   // Constants
   // Use std::map to avoid iterator/reference invalidation.
   std::map<Constant *, AnyValue> ConstCache;
@@ -290,6 +299,7 @@ class Context {
       ValidBlockTargets;
   DenseMap<GlobalVariable *, Pointer> GlobalAddrMap;
   std::optional<AnyValue> getConstantValueImpl(Constant *C);
+  std::optional<AnyValue> evaluateConstantExpression(ConstantExpr *CE);
 
   // Floating-point environment
   RoundingMode CurrentRoundingMode = RoundingMode::NearestTiesToEven;
@@ -397,6 +407,9 @@ class Context {
   /// Freeze the value in-place.
   void freeze(AnyValue &Val, Type *Ty);
 
+  AnyValue computeGEP(GEPOperator &GEP,
+                      function_ref<const AnyValue &(Value *V)> GetValue);
+
   Function *getTargetFunction(const Pointer &Ptr);
   BasicBlock *getTargetBlock(const Pointer &Ptr);
 
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 341bf8fd7fca9..5769d0a4a9c4d 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -17,7 +17,6 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Analysis/VectorUtils.h"
-#include "llvm/IR/GetElementPtrTypeIterator.h"
 #include "llvm/IR/InlineAsm.h"
 #include "llvm/IR/InstVisitor.h"
 #include "llvm/IR/Operator.h"
@@ -31,42 +30,6 @@ namespace llvm::ubi {
 
 using namespace PatternMatch;
 
-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;
-}
-
 /// Visit the scalar values recursively. The callback function may modify the
 /// value in-place.
 static void forEachScalarValue(AnyValue &V,
@@ -713,99 +676,6 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     return false;
   }
 
-  AnyValue computePtrAdd(const Pointer &Ptr, const APInt &Offset,
-                         GEPNoWrapFlags Flags, AnyValue &AccumulatedOffset) {
-    if (Offset.isZero())
-      return Ptr;
-    APInt IndexBits = Ptr.address().trunc(Offset.getBitWidth());
-    auto NewIndex = addNoWrap(IndexBits, Offset, /*HasNSW=*/false,
-                              Flags.hasNoUnsignedWrap());
-    if (NewIndex.isPoison())
-      return AnyValue::poison();
-    if (Flags.hasNoUnsignedSignedWrap()) {
-      // The successive addition of the current address, truncated to the
-      // pointer index type and interpreted as an unsigned number, and each
-      // offset, interpreted as a signed number, does not wrap the pointer index
-      // type.
-      if (Offset.isNonNegative() ? NewIndex.asInteger().ult(IndexBits)
-                                 : NewIndex.asInteger().ugt(IndexBits))
-        return AnyValue::poison();
-    }
-    APInt NewAddr = Ptr.address();
-    NewAddr.insertBits(NewIndex.asInteger(), 0);
-
-    MemoryObject *MO = nullptr;
-    if (Flags.isInBounds()) {
-      MO = Ctx.checkProvenance(
-          Ptr, [](const Provenance &) { return true; },
-          /*HasSideEffect=*/false);
-      if (!MO || !MO->inBounds(NewAddr))
-        return AnyValue::poison();
-    }
-
-    if (!AccumulatedOffset.isPoison()) {
-      AccumulatedOffset =
-          addNoWrap(AccumulatedOffset.asInteger(), Offset,
-                    Flags.hasNoUnsignedSignedWrap(), Flags.hasNoUnsignedWrap());
-      if (AccumulatedOffset.isPoison())
-        return AnyValue::poison();
-    }
-
-    // Should not expose provenance here even if the new address doesn't point
-    // to the original object.
-    auto Res = Ptr.getWithNewAddr(NewAddr);
-    if (MO) {
-      auto &Prov = Res.provenance();
-      if (Prov.isWildcard() && !Prov.getMemoryObject())
-        Res = Res.getWithNewProvenance(Prov.getWithKnownMemoryObject(*MO));
-    }
-    return Res;
-  }
-
-  AnyValue computePtrAdd(const AnyValue &Ptr, const APInt &Offset,
-                         GEPNoWrapFlags Flags, AnyValue &AccumulatedOffset) {
-    if (Ptr.isPoison())
-      return AnyValue::poison();
-    return computePtrAdd(Ptr.asPointer(), Offset, Flags, AccumulatedOffset);
-  }
-
-  AnyValue computeScaledPtrAdd(const AnyValue &Ptr, const AnyValue &Index,
-                               const APInt &Scale, GEPNoWrapFlags Flags,
-                               AnyValue &AccumulatedOffset) {
-    if (Ptr.isPoison() || Index.isPoison())
-      return AnyValue::poison();
-    assert(Ptr.isPointer() && Index.isInteger() && "Unexpected type.");
-    if (Scale.isOne())
-      return computePtrAdd(Ptr, Index.asInteger(), Flags, AccumulatedOffset);
-    auto ScaledOffset =
-        mulNoWrap(Index.asInteger(), Scale, Flags.hasNoUnsignedSignedWrap(),
-                  Flags.hasNoUnsignedWrap());
-    if (ScaledOffset.isPoison())
-      return AnyValue::poison();
-    return computePtrAdd(Ptr, ScaledOffset.asInteger(), Flags,
-                         AccumulatedOffset);
-  }
-
-  AnyValue canonicalizeIndex(const AnyValue &Idx, unsigned IndexBitWidth,
-                             GEPNoWrapFlags Flags) {
-    if (Idx.isPoison())
-      return AnyValue::poison();
-    auto &IdxInt = Idx.asInteger();
-    if (IdxInt.getBitWidth() == IndexBitWidth)
-      return Idx;
-    if (IdxInt.getBitWidth() > IndexBitWidth) {
-      if (Flags.hasNoUnsignedSignedWrap() &&
-          !IdxInt.isSignedIntN(IndexBitWidth))
-        return AnyValue::poison();
-
-      if (Flags.hasNoUnsignedWrap() && !IdxInt.isIntN(IndexBitWidth))
-        return AnyValue::poison();
-
-      return IdxInt.trunc(IndexBitWidth);
-    }
-    return IdxInt.sext(IndexBitWidth);
-  }
-
   DenormalMode getCurrentDenormalMode(Type *Ty) {
     return CurrentFrame->Func.getDenormalMode(
         Ty->getScalarType()->getFltSemantics());
@@ -2361,76 +2231,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   }
 
   void visitGetElementPtrInst(GetElementPtrInst &GEP) {
-    uint32_t IndexBitWidth =
-        DL.getIndexSizeInBits(GEP.getType()->getPointerAddressSpace());
-    GEPNoWrapFlags Flags = GEP.getNoWrapFlags();
-    AnyValue Res = getValue(GEP.getPointerOperand());
-    AnyValue AccumulatedOffset = APInt(IndexBitWidth, 0);
-    if (Res.isAggregate())
-      AccumulatedOffset =
-          AnyValue::getVectorSplat(AccumulatedOffset, Res.asAggregate().size());
-    auto ApplyScaledOffset = [&](const AnyValue &Index, const APInt &Scale) {
-      if (Index.isAggregate() && !Res.isAggregate()) {
-        Res = AnyValue::getVectorSplat(Res, Index.asAggregate().size());
-        AccumulatedOffset = AnyValue::getVectorSplat(
-            AccumulatedOffset, Index.asAggregate().size());
-      }
-      if (Index.isAggregate() && Res.isAggregate()) {
-        for (auto &&[ResElem, IndexElem, OffsetElem] :
-             zip(Res.asAggregate(), Index.asAggregate(),
-                 AccumulatedOffset.asAggregate()))
-          ResElem = computeScaledPtrAdd(
-              ResElem, canonicalizeIndex(IndexElem, IndexBitWidth, Flags),
-              Scale, Flags, OffsetElem);
-      } else {
-        AnyValue CanonicalIndex =
-            canonicalizeIndex(Index, IndexBitWidth, Flags);
-        if (Res.isAggregate()) {
-          for (auto &&[ResElem, OffsetElem] :
-               zip(Res.asAggregate(), AccumulatedOffset.asAggregate()))
-            ResElem = computeScaledPtrAdd(ResElem, CanonicalIndex, Scale, Flags,
-                                          OffsetElem);
-        } else {
-          Res = computeScaledPtrAdd(Res, CanonicalIndex, Scale, Flags,
-                                    AccumulatedOffset);
-        }
-      }
-    };
-
-    for (gep_type_iterator GTI = gep_type_begin(GEP), GTE = gep_type_end(GEP);
-         GTI != GTE; ++GTI) {
-      Value *V = GTI.getOperand();
-
-      // Fast path for zero offsets.
-      if (auto *CI = dyn_cast<ConstantInt>(V)) {
-        if (CI->isZero())
-          continue;
-      }
-      if (isa<ConstantAggregateZero>(V))
-        continue;
-
-      // Handle a struct index, which adds its field offset to the pointer.
-      if (StructType *STy = GTI.getStructTypeOrNull()) {
-        unsigned ElementIdx = cast<ConstantInt>(V)->getZExtValue();
-        const StructLayout *SL = DL.getStructLayout(STy);
-        // Element offset is in bytes.
-        ApplyScaledOffset(
-            APInt(IndexBitWidth, SL->getElementOffset(ElementIdx)),
-            APInt(IndexBitWidth, 1));
-        continue;
-      }
-
-      // Truncate if type size exceeds index space.
-      // TODO: Should be documented in LangRef: GEPs with nowrap flags should
-      // return poison when the type size exceeds index space.
-      TypeSize Offset = GTI.getSequentialElementStride(DL);
-      APInt Scale(IndexBitWidth, Ctx.getEffectiveTypeSize(Offset),
-                  /*isSigned=*/false, /*implicitTrunc=*/true);
-      if (!Scale.isZero())
-        ApplyScaledOffset(getValue(V), Scale);
-    }
-
-    setResult(GEP, std::move(Res));
+    setResult(GEP, Ctx.computeGEP(cast<GEPOperator>(GEP),
+                                  [this](Value *V) -> const AnyValue & {
+                                    return getValue(V);
+                                  }));
   }
 
   void visitPtrToInt(PtrToIntInst &I) {
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index b5c5078103e18..77e428813a832 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -334,6 +334,42 @@ inline raw_ostream &operator<<(raw_ostream &OS, const Pointer &P) {
   return OS;
 }
 
+inline 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;
+}
+
+inline 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;
+}
+
+inline 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;
+}
+
 } // namespace llvm::ubi
 
 #endif

>From 9f055d55118a35cd82296e4bb7d8d46fef4a3b70 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sun, 14 Jun 2026 18:22:08 +0800
Subject: [PATCH 2/2] [llubi] Address review comments.

---
 llvm/tools/llubi/lib/Context.cpp | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 570ae980dbd5c..988faa94a59db 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -91,7 +91,10 @@ std::optional<AnyValue> Context::getConstantValueImpl(Constant *C) {
   if (isa<PoisonValue>(C))
     return AnyValue::getPoisonValue(*this, C->getType());
 
-  if (isa<ConstantAggregateZero, ConstantPointerNull>(C))
+  if (isa<ConstantAggregateZero>(C))
+    return AnyValue::getNullValue(*this, C->getType());
+
+  if (isa<ConstantPointerNull>(C))
     return AnyValue::getNullValue(*this, C->getType());
 
   if (auto *CI = dyn_cast<ConstantInt>(C)) {
@@ -269,6 +272,10 @@ std::optional<AnyValue> Context::evaluateConstantExpression(ConstantExpr *CE) {
     }
     return AnyValue(std::move(Vec));
   }
+  case Instruction::PtrToInt:
+  case Instruction::IntToPtr:
+  case Instruction::AddrSpaceCast:
+    return std::optional;
   default:
     assert(Instruction::isBinaryOp(Opc) && "Must be binary operator?");
     const AnyValue *LHS = getConstantValue(CE->getOperand(0));



More information about the llvm-commits mailing list