[llvm] [llubi] Implement experimental vector intrinsics (PR #206899)

Zhige Chen via llvm-commits llvm-commits at lists.llvm.org
Sat Jul 4 22:43:27 PDT 2026


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

>From 54f4be7b3f016de6e7d589a9c9417f531c1b43eb Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Wed, 1 Jul 2026 15:27:37 +0800
Subject: [PATCH 1/5] [llubi] Implement experimental vector intrinsics

---
 .../tools/llubi/intr_experimental_vector.ll   | 120 +++++++++
 llvm/tools/llubi/lib/Interpreter.cpp          | 240 ++++++++++++++++++
 2 files changed, 360 insertions(+)
 create mode 100644 llvm/test/tools/llubi/intr_experimental_vector.ll

diff --git a/llvm/test/tools/llubi/intr_experimental_vector.ll b/llvm/test/tools/llubi/intr_experimental_vector.ll
new file mode 100644
index 0000000000000..d23d126357cad
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_experimental_vector.ll
@@ -0,0 +1,120 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %cttz_some = call i8 @llvm.experimental.cttz.elts.i8.v4i1(<4 x i1> <i1 false, i1 false, i1 true, i1 false>, i1 true)
+  %cttz_all = call i8 @llvm.experimental.cttz.elts.i8.v4i1(<4 x i1> zeroinitializer, i1 false)
+  %cttz_poison = call i8 @llvm.experimental.cttz.elts.i8.v4i1(<4 x i1> zeroinitializer, i1 true)
+
+  %evl_zero = call i32 @llvm.experimental.get.vector.length.i32(i32 0, i32 4, i1 false)
+  %evl_short = call i32 @llvm.experimental.get.vector.length.i32(i32 3, i32 4, i1 false)
+  %evl_full = call i32 @llvm.experimental.get.vector.length.i32(i32 9, i32 4, i1 false)
+  %evl_poison = call i32 @llvm.experimental.get.vector.length.i64(i64 poison, i32 4, i1 false)
+
+  %last = call i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> <i1 true, i1 false, i1 true, i1 false>, i32 99)
+  %last_passthru = call i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> zeroinitializer, i32 99)
+  %last_poison = call i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> <i1 false, i1 poison, i1 false, i1 false>, i32 99)
+
+  %compress = call <4 x i32> @llvm.experimental.vector.compress.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> <i1 false, i1 true, i1 false, i1 true>, <4 x i32> <i32 101, i32 102, i32 103, i32 104>)
+  %compress_all_false = call <4 x i32> @llvm.experimental.vector.compress.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> zeroinitializer, <4 x i32> <i32 101, i32 102, i32 103, i32 104>)
+  %compress_poison = call <4 x i32> @llvm.experimental.vector.compress.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> <i1 false, i1 poison, i1 false, i1 false>, <4 x i32> zeroinitializer)
+
+  %match = call <4 x i1> @llvm.experimental.vector.match.v4i1.v4i8.v3i8(<4 x i8> <i8 1, i8 2, i8 3, i8 4>, <3 x i8> <i8 4, i8 2, i8 9>, <4 x i1> <i1 true, i1 true, i1 false, i1 true>)
+  %match_poison_needles = call <4 x i1> @llvm.experimental.vector.match.v4i1.v4i8.v3i8(<4 x i8> <i8 1, i8 2, i8 3, i8 4>, <3 x i8> <i8 poison, i8 3, i8 9>, <4 x i1> <i1 true, i1 true, i1 true, i1 false>)
+
+  %buckets = alloca [8 x i32], align 4
+  %b0 = getelementptr inbounds [8 x i32], ptr %buckets, i64 0, i64 0
+  %b1 = getelementptr inbounds [8 x i32], ptr %buckets, i64 0, i64 1
+  %b2 = getelementptr inbounds [8 x i32], ptr %buckets, i64 0, i64 2
+  %b3 = getelementptr inbounds [8 x i32], ptr %buckets, i64 0, i64 3
+  store i32 10, ptr %b0, align 4
+  store i32 20, ptr %b1, align 4
+  store i32 30, ptr %b2, align 4
+  store i32 40, ptr %b3, align 4
+  %ptrs_add0 = insertelement <4 x ptr> poison, ptr %b0, i32 0
+  %ptrs_add1 = insertelement <4 x ptr> %ptrs_add0, ptr %b1, i32 1
+  %ptrs_add2 = insertelement <4 x ptr> %ptrs_add1, ptr %b0, i32 2
+  %ptrs_add3 = insertelement <4 x ptr> %ptrs_add2, ptr %b2, i32 3
+  call void @llvm.experimental.vector.histogram.add.v4p0.i32(<4 x ptr> %ptrs_add3, i32 5, <4 x i1> <i1 true, i1 true, i1 true, i1 false>)
+  %add0 = load i32, ptr %b0, align 4
+  %add1 = load i32, ptr %b1, align 4
+  %add2 = load i32, ptr %b2, align 4
+
+  store i32 4294967290, ptr %b0, align 4
+  store i32 1, ptr %b1, align 4
+  %ptrs_sat0 = insertelement <4 x ptr> poison, ptr %b0, i32 0
+  %ptrs_sat1 = insertelement <4 x ptr> %ptrs_sat0, ptr %b0, i32 1
+  %ptrs_sat2 = insertelement <4 x ptr> %ptrs_sat1, ptr %b1, i32 2
+  %ptrs_sat3 = insertelement <4 x ptr> %ptrs_sat2, ptr %b1, i32 3
+  call void @llvm.experimental.vector.histogram.uadd.sat.v4p0.i32(<4 x ptr> %ptrs_sat3, i32 4, <4 x i1> <i1 true, i1 true, i1 false, i1 true>)
+  %sat0 = load i32, ptr %b0, align 4
+  %sat1 = load i32, ptr %b1, align 4
+
+  store i32 7, ptr %b0, align 4
+  store i32 42, ptr %b1, align 4
+  call void @llvm.experimental.vector.histogram.umax.v4p0.i32(<4 x ptr> %ptrs_add3, i32 20, <4 x i1> <i1 true, i1 true, i1 true, i1 false>)
+  %max0 = load i32, ptr %b0, align 4
+  %max1 = load i32, ptr %b1, align 4
+
+  store i32 7, ptr %b0, align 4
+  store i32 42, ptr %b1, align 4
+  call void @llvm.experimental.vector.histogram.umin.v4p0.i32(<4 x ptr> %ptrs_add3, i32 20, <4 x i1> <i1 true, i1 true, i1 true, i1 false>)
+  %min0 = load i32, ptr %b0, align 4
+  %min1 = load i32, ptr %b1, align 4
+
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %cttz_some = call i8 @llvm.experimental.cttz.elts.i8.v4i1(<4 x i1> <i1 false, i1 false, i1 true, i1 false>, i1 true) => i8 2
+; CHECK-NEXT:   %cttz_all = call i8 @llvm.experimental.cttz.elts.i8.v4i1(<4 x i1> zeroinitializer, i1 false) => i8 4
+; CHECK-NEXT:   %cttz_poison = call i8 @llvm.experimental.cttz.elts.i8.v4i1(<4 x i1> zeroinitializer, i1 true) => poison
+; CHECK-NEXT:   %evl_zero = call i32 @llvm.experimental.get.vector.length.i32(i32 0, i32 4, i1 false) => i32 0
+; CHECK-NEXT:   %evl_short = call i32 @llvm.experimental.get.vector.length.i32(i32 3, i32 4, i1 false) => i32 3
+; CHECK-NEXT:   %evl_full = call i32 @llvm.experimental.get.vector.length.i32(i32 9, i32 4, i1 false) => i32 4
+; CHECK-NEXT:   %evl_poison = call i32 @llvm.experimental.get.vector.length.i64(i64 poison, i32 4, i1 false) => poison
+; CHECK-NEXT:   %last = call i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> <i1 true, i1 false, i1 true, i1 false>, i32 99) => i32 30
+; CHECK-NEXT:   %last_passthru = call i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> zeroinitializer, i32 99) => i32 99
+; CHECK-NEXT:   %last_poison = call i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> <i1 false, i1 poison, i1 false, i1 false>, i32 99) => poison
+; CHECK-NEXT:   %compress = call <4 x i32> @llvm.experimental.vector.compress.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> <i1 false, i1 true, i1 false, i1 true>, <4 x i32> <i32 101, i32 102, i32 103, i32 104>) => { i32 20, i32 40, i32 103, i32 104 }
+; CHECK-NEXT:   %compress_all_false = call <4 x i32> @llvm.experimental.vector.compress.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> zeroinitializer, <4 x i32> <i32 101, i32 102, i32 103, i32 104>) => { i32 101, i32 102, i32 103, i32 104 }
+; CHECK-NEXT:   %compress_poison = call <4 x i32> @llvm.experimental.vector.compress.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> <i1 false, i1 poison, i1 false, i1 false>, <4 x i32> zeroinitializer) => { poison, poison, poison, poison }
+; CHECK-NEXT:   %match = call <4 x i1> @llvm.experimental.vector.match.v4i8.v3i8(<4 x i8> <i8 1, i8 2, i8 3, i8 4>, <3 x i8> <i8 4, i8 2, i8 9>, <4 x i1> <i1 true, i1 true, i1 false, i1 true>) => { F, T, F, T }
+; CHECK-NEXT:   %match_poison_needles = call <4 x i1> @llvm.experimental.vector.match.v4i8.v3i8(<4 x i8> <i8 1, i8 2, i8 3, i8 4>, <3 x i8> <i8 poison, i8 3, i8 9>, <4 x i1> <i1 true, i1 true, i1 true, i1 false>) => { poison, poison, T, F }
+; CHECK-NEXT:   %buckets = alloca [8 x i32], align 4 => ptr 0x8 [buckets]
+; CHECK-NEXT:   %b0 = getelementptr inbounds [8 x i32], ptr %buckets, i64 0, i64 0 => ptr 0x8 [buckets]
+; CHECK-NEXT:   %b1 = getelementptr inbounds [8 x i32], ptr %buckets, i64 0, i64 1 => ptr 0xC [buckets + 4]
+; CHECK-NEXT:   %b2 = getelementptr inbounds [8 x i32], ptr %buckets, i64 0, i64 2 => ptr 0x10 [buckets + 8]
+; CHECK-NEXT:   %b3 = getelementptr inbounds [8 x i32], ptr %buckets, i64 0, i64 3 => ptr 0x14 [buckets + 12]
+; CHECK-NEXT:   store i32 10, ptr %b0, align 4
+; CHECK-NEXT:   store i32 20, ptr %b1, align 4
+; CHECK-NEXT:   store i32 30, ptr %b2, align 4
+; CHECK-NEXT:   store i32 40, ptr %b3, align 4
+; CHECK-NEXT:   %ptrs_add0 = insertelement <4 x ptr> poison, ptr %b0, i32 0 => { ptr 0x8 [buckets], poison, poison, poison }
+; CHECK-NEXT:   %ptrs_add1 = insertelement <4 x ptr> %ptrs_add0, ptr %b1, i32 1 => { ptr 0x8 [buckets], ptr 0xC [buckets + 4], poison, poison }
+; CHECK-NEXT:   %ptrs_add2 = insertelement <4 x ptr> %ptrs_add1, ptr %b0, i32 2 => { ptr 0x8 [buckets], ptr 0xC [buckets + 4], ptr 0x8 [buckets], poison }
+; CHECK-NEXT:   %ptrs_add3 = insertelement <4 x ptr> %ptrs_add2, ptr %b2, i32 3 => { ptr 0x8 [buckets], ptr 0xC [buckets + 4], ptr 0x8 [buckets], ptr 0x10 [buckets + 8] }
+; CHECK-NEXT:   call void @llvm.experimental.vector.histogram.add.v4p0.i32(<4 x ptr> %ptrs_add3, i32 5, <4 x i1> <i1 true, i1 true, i1 true, i1 false>)
+; CHECK-NEXT:   %add0 = load i32, ptr %b0, align 4 => i32 20
+; CHECK-NEXT:   %add1 = load i32, ptr %b1, align 4 => i32 25
+; CHECK-NEXT:   %add2 = load i32, ptr %b2, align 4 => i32 30
+; CHECK-NEXT:   store i32 -6, ptr %b0, align 4
+; CHECK-NEXT:   store i32 1, ptr %b1, align 4
+; CHECK-NEXT:   %ptrs_sat0 = insertelement <4 x ptr> poison, ptr %b0, i32 0 => { ptr 0x8 [buckets], poison, poison, poison }
+; CHECK-NEXT:   %ptrs_sat1 = insertelement <4 x ptr> %ptrs_sat0, ptr %b0, i32 1 => { ptr 0x8 [buckets], ptr 0x8 [buckets], poison, poison }
+; CHECK-NEXT:   %ptrs_sat2 = insertelement <4 x ptr> %ptrs_sat1, ptr %b1, i32 2 => { ptr 0x8 [buckets], ptr 0x8 [buckets], ptr 0xC [buckets + 4], poison }
+; CHECK-NEXT:   %ptrs_sat3 = insertelement <4 x ptr> %ptrs_sat2, ptr %b1, i32 3 => { ptr 0x8 [buckets], ptr 0x8 [buckets], ptr 0xC [buckets + 4], ptr 0xC [buckets + 4] }
+; CHECK-NEXT:   call void @llvm.experimental.vector.histogram.uadd.sat.v4p0.i32(<4 x ptr> %ptrs_sat3, i32 4, <4 x i1> <i1 true, i1 true, i1 false, i1 true>)
+; CHECK-NEXT:   %sat0 = load i32, ptr %b0, align 4 => i32 -1
+; CHECK-NEXT:   %sat1 = load i32, ptr %b1, align 4 => i32 5
+; CHECK-NEXT:   store i32 7, ptr %b0, align 4
+; CHECK-NEXT:   store i32 42, ptr %b1, align 4
+; CHECK-NEXT:   call void @llvm.experimental.vector.histogram.umax.v4p0.i32(<4 x ptr> %ptrs_add3, i32 20, <4 x i1> <i1 true, i1 true, i1 true, i1 false>)
+; CHECK-NEXT:   %max0 = load i32, ptr %b0, align 4 => i32 20
+; CHECK-NEXT:   %max1 = load i32, ptr %b1, align 4 => i32 42
+; CHECK-NEXT:   store i32 7, ptr %b0, align 4
+; CHECK-NEXT:   store i32 42, ptr %b1, align 4
+; CHECK-NEXT:   call void @llvm.experimental.vector.histogram.umin.v4p0.i32(<4 x ptr> %ptrs_add3, i32 20, <4 x i1> <i1 true, i1 true, i1 true, i1 false>)
+; CHECK-NEXT:   %min0 = load i32, ptr %b0, align 4 => i32 7
+; CHECK-NEXT:   %min1 = load i32, ptr %b1, align 4 => i32 20
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index d734a9962e6d9..bf1e72c7e2a95 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -810,6 +810,102 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     return AnyValue();
   }
 
+  static BooleanKind getMaskLane(const AnyValue &Mask, size_t I) {
+    if (Mask.isAggregate())
+      return Mask.asAggregate()[I].asBoolean();
+    return Mask.asBoolean();
+  }
+
+  AnyValue callExperimentalVectorHistogramIntrinsic(CallBase &CB,
+                                                    ArrayRef<AnyValue> Args,
+                                                    Intrinsic::ID IID) {
+    struct Bucket {
+      MemoryObject *MO;
+      uint64_t Offset;
+      uint64_t Count;
+    };
+
+    const auto &Ptrs = Args[0].asAggregate();
+    const AnyValue &Update = Args[1];
+    const AnyValue &Mask = Args[2];
+    Type *ElemTy = CB.getArgOperand(1)->getType();
+    const uint64_t AccessSize = Ctx.getEffectiveTypeStoreSize(ElemTy);
+
+    SmallVector<Bucket, 8> Buckets;
+    for (size_t I = 0, E = Ptrs.size(); I != E; ++I) {
+      switch (getMaskLane(Mask, I)) {
+      case BooleanKind::False:
+        continue;
+      case BooleanKind::Poison:
+        reportImmediateUB()
+            << "Poison mask lane in experimental vector histogram intrinsic.";
+        return AnyValue();
+      case BooleanKind::True:
+        break;
+      }
+
+      if (Ptrs[I].isPoison()) {
+        reportImmediateUB() << "Poison pointer lane in experimental vector "
+                               "histogram intrinsic.";
+        return AnyValue();
+      }
+
+      auto [MO, Offset] =
+          verifyMemAccess(Ptrs[I].asPointer(), AccessSize, Align(1),
+                          /*IsStore=*/true);
+      if (!MO)
+        return AnyValue();
+ 
+      auto *It =
+          find_if(Buckets, [&, MO = MO, Offset = Offset](const Bucket &B) {
+            return B.MO == MO && B.Offset == Offset;
+          });
+      if (It == Buckets.end())
+        Buckets.push_back({MO, Offset, 1});
+      else
+        ++It->Count;
+    }
+
+    for (const auto &[MO, Offset, Count] : Buckets) {
+      AnyValue Old = Ctx.load(*MO, Offset, ElemTy);
+      AnyValue New;
+
+      if (Old.isPoison() || Update.isPoison()) {
+        New = AnyValue::poison();
+      } else {
+        const APInt &OldInt = Old.asInteger();
+        const APInt &UpdateInt = Update.asInteger();
+
+        switch (IID) {
+        case Intrinsic::experimental_vector_histogram_add:
+          New = OldInt + UpdateInt * APInt(UpdateInt.getBitWidth(), Count,
+                                           /*isSigned=*/false,
+                                           /*implicitTrunc=*/true);
+          break;
+        case Intrinsic::experimental_vector_histogram_uadd_sat: {
+          APInt Acc = OldInt;
+          for (uint64_t I = 0; I != Count; ++I)
+            Acc = Acc.uadd_sat(UpdateInt);
+          New = Acc;
+          break;
+        }
+        case Intrinsic::experimental_vector_histogram_umax:
+          New = APIntOps::umax(OldInt, UpdateInt);
+          break;
+        case Intrinsic::experimental_vector_histogram_umin:
+          New = APIntOps::umin(OldInt, UpdateInt);
+          break;
+        default:
+          llvm_unreachable("Unexpected histogram intrinsic ID");
+        }
+      }
+
+      Ctx.store(*MO, Offset, New, ElemTy);
+    }
+
+    return AnyValue();
+  }
+
 public:
   InstExecutor(Context &C, EventHandler &H, Function &F,
                ArrayRef<AnyValue> Args, AnyValue &RetVal)
@@ -1629,6 +1725,150 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     case Intrinsic::experimental_noalias_scope_decl:
       // FIXME: Not implemented yet. Currently it acts as a noop.
       return AnyValue();
+    case Intrinsic::experimental_cttz_elts: {
+      auto *IsZeroPoisonC = cast<ConstantInt>(CB.getArgOperand(1));
+      const bool IsZeroPoison = IsZeroPoisonC->isOne();
+
+      const auto &Vec = Args[0].asAggregate();
+      const unsigned RetBW = RetTy->getIntegerBitWidth();
+
+      if (APInt::getMaxValue(RetBW).ult(Vec.size())) {
+        reportImmediateUB() << "experimental.cttz.elts return type too narrow.";
+        return AnyValue::poison();
+      }
+
+      uint64_t Count = 0;
+      for (const AnyValue &V : Vec) {
+        if (V.isPoison())
+          return AnyValue::poison();
+        if (!V.asInteger().isZero())
+          break;
+        ++Count;
+      }
+
+      if (Count == Vec.size() && IsZeroPoison)
+        return AnyValue::poison();
+      return APInt(RetBW, Count);
+    }
+    case Intrinsic::experimental_get_vector_length: {
+      auto *VFC = cast<ConstantInt>(CB.getArgOperand(1));
+      auto *ScalableC = cast<ConstantInt>(CB.getArgOperand(2));
+
+      if (Args[0].isPoison())
+        return AnyValue::poison();
+
+      const APInt &Cnt = Args[0].asInteger();
+      const uint64_t VF = VFC->getZExtValue();
+      const bool Scalable = ScalableC->isOne();
+
+      const uint64_t MaxLanes =
+          Scalable ? Ctx.getEVL(ElementCount::getScalable(VF)) : VF;
+
+      uint64_t Res = 0;
+      if (!Cnt.isZero()) {
+        APInt Max(Cnt.getBitWidth(), MaxLanes);
+        Res = Cnt.ugt(Max) ? MaxLanes : Cnt.getZExtValue();
+      }
+
+      return APInt(32, Res);
+    }
+
+    case Intrinsic::experimental_vector_extract_last_active: {
+      const auto &Data = Args[0].asAggregate();
+      const AnyValue &Mask = Args[1];
+
+      for (size_t I = Data.size(); I != 0; --I) {
+        switch (getMaskLane(Mask, I - 1)) {
+        case BooleanKind::True:
+          return Data[I - 1];
+        case BooleanKind::False:
+          break;
+        case BooleanKind::Poison:
+          return AnyValue::poison();
+        }
+      }
+
+      return Args[2];
+    }
+
+    case Intrinsic::experimental_vector_compress: {
+      const auto &Val = Args[0].asAggregate();
+      const AnyValue &Mask = Args[1];
+      const auto &Passthru = Args[2].asAggregate();
+
+      std::vector<AnyValue> Res;
+      Res.reserve(Val.size());
+
+      for (size_t I = 0, E = Val.size(); I != E; ++I) {
+        switch (getMaskLane(Mask, I)) {
+        case BooleanKind::True:
+          Res.push_back(Val[I]);
+          break;
+        case BooleanKind::False:
+          break;
+        case BooleanKind::Poison:
+          return AnyValue::getPoisonValue(Ctx, RetTy);
+        }
+      }
+
+      for (size_t I = Res.size(), E = Val.size(); I != E; ++I)
+        Res.push_back(Passthru[I]);
+      return std::move(Res);
+    }
+
+    case Intrinsic::experimental_vector_match: {
+      const auto &Search = Args[0].asAggregate();
+      const auto &Needles = Args[1].asAggregate();
+      const auto &Mask = Args[2].asAggregate();
+
+      std::vector<AnyValue> Res;
+      Res.reserve(Search.size());
+
+      for (size_t I = 0, E = Search.size(); I != E; ++I) {
+        switch (Mask[I].asBoolean()) {
+        case BooleanKind::False:
+          Res.push_back(AnyValue::boolean(false));
+          continue;
+        case BooleanKind::Poison:
+          Res.push_back(AnyValue::poison());
+          continue;
+        case BooleanKind::True:
+          break;
+        }
+
+        if (Search[I].isPoison()) {
+          Res.push_back(AnyValue::poison());
+          continue;
+        }
+
+        bool Found = false;
+        bool SawPoison = false;
+        for (const AnyValue &Needle : Needles) {
+          if (Needle.isPoison()) {
+            SawPoison = true;
+            continue;
+          }
+          if (Search[I].asInteger() == Needle.asInteger()) {
+            Found = true;
+            break;
+          }
+        }
+
+        if (Found)
+          Res.push_back(AnyValue::boolean(true));
+        else if (SawPoison)
+          Res.push_back(AnyValue::poison());
+        else
+          Res.push_back(AnyValue::boolean(false));
+      }
+
+      return std::move(Res);
+    }
+    case Intrinsic::experimental_vector_histogram_add:
+    case Intrinsic::experimental_vector_histogram_uadd_sat:
+    case Intrinsic::experimental_vector_histogram_umax:
+    case Intrinsic::experimental_vector_histogram_umin:
+      return callExperimentalVectorHistogramIntrinsic(CB, Args, IID);
     default:
       Handler.onUnrecognizedInstruction(CB);
       setFailed();

>From 51a108e3c010cf0352f5aae29c3fa8473eda29d1 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Wed, 1 Jul 2026 15:33:35 +0800
Subject: [PATCH 2/5] [llubi] Format code

---
 llvm/tools/llubi/lib/Interpreter.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index bf1e72c7e2a95..87c9f80d41d14 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -855,7 +855,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
                           /*IsStore=*/true);
       if (!MO)
         return AnyValue();
- 
+
       auto *It =
           find_if(Buckets, [&, MO = MO, Offset = Offset](const Bucket &B) {
             return B.MO == MO && B.Offset == Offset;

>From 2b6d46c2fca07adcc5de3f014cc7ee48cf9a6155 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Wed, 1 Jul 2026 19:53:01 +0800
Subject: [PATCH 3/5] [llubi] Address comments

---
 .../tools/llubi/intr_experimental_vector.ll     |  4 +++-
 llvm/tools/llubi/lib/Interpreter.cpp            | 17 ++++++-----------
 2 files changed, 9 insertions(+), 12 deletions(-)

diff --git a/llvm/test/tools/llubi/intr_experimental_vector.ll b/llvm/test/tools/llubi/intr_experimental_vector.ll
index d23d126357cad..37e06d47c0ae5 100644
--- a/llvm/test/tools/llubi/intr_experimental_vector.ll
+++ b/llvm/test/tools/llubi/intr_experimental_vector.ll
@@ -9,6 +9,7 @@ define void @main() {
   %evl_zero = call i32 @llvm.experimental.get.vector.length.i32(i32 0, i32 4, i1 false)
   %evl_short = call i32 @llvm.experimental.get.vector.length.i32(i32 3, i32 4, i1 false)
   %evl_full = call i32 @llvm.experimental.get.vector.length.i32(i32 9, i32 4, i1 false)
+  %evl_scalable = call i32 @llvm.experimental.get.vector.length.i32(i32 20, i32 4, i1 true)
   %evl_poison = call i32 @llvm.experimental.get.vector.length.i64(i64 poison, i32 4, i1 false)
 
   %last = call i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> <i1 true, i1 false, i1 true, i1 false>, i32 99)
@@ -71,6 +72,7 @@ define void @main() {
 ; CHECK-NEXT:   %evl_zero = call i32 @llvm.experimental.get.vector.length.i32(i32 0, i32 4, i1 false) => i32 0
 ; CHECK-NEXT:   %evl_short = call i32 @llvm.experimental.get.vector.length.i32(i32 3, i32 4, i1 false) => i32 3
 ; CHECK-NEXT:   %evl_full = call i32 @llvm.experimental.get.vector.length.i32(i32 9, i32 4, i1 false) => i32 4
+; CHECK-NEXT:   %evl_scalable = call i32 @llvm.experimental.get.vector.length.i32(i32 20, i32 4, i1 true) => i32 16
 ; CHECK-NEXT:   %evl_poison = call i32 @llvm.experimental.get.vector.length.i64(i64 poison, i32 4, i1 false) => poison
 ; CHECK-NEXT:   %last = call i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> <i1 true, i1 false, i1 true, i1 false>, i32 99) => i32 30
 ; CHECK-NEXT:   %last_passthru = call i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> zeroinitializer, i32 99) => i32 99
@@ -79,7 +81,7 @@ define void @main() {
 ; CHECK-NEXT:   %compress_all_false = call <4 x i32> @llvm.experimental.vector.compress.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> zeroinitializer, <4 x i32> <i32 101, i32 102, i32 103, i32 104>) => { i32 101, i32 102, i32 103, i32 104 }
 ; CHECK-NEXT:   %compress_poison = call <4 x i32> @llvm.experimental.vector.compress.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> <i1 false, i1 poison, i1 false, i1 false>, <4 x i32> zeroinitializer) => { poison, poison, poison, poison }
 ; CHECK-NEXT:   %match = call <4 x i1> @llvm.experimental.vector.match.v4i8.v3i8(<4 x i8> <i8 1, i8 2, i8 3, i8 4>, <3 x i8> <i8 4, i8 2, i8 9>, <4 x i1> <i1 true, i1 true, i1 false, i1 true>) => { F, T, F, T }
-; CHECK-NEXT:   %match_poison_needles = call <4 x i1> @llvm.experimental.vector.match.v4i8.v3i8(<4 x i8> <i8 1, i8 2, i8 3, i8 4>, <3 x i8> <i8 poison, i8 3, i8 9>, <4 x i1> <i1 true, i1 true, i1 true, i1 false>) => { poison, poison, T, F }
+; CHECK-NEXT:   %match_poison_needles = call <4 x i1> @llvm.experimental.vector.match.v4i8.v3i8(<4 x i8> <i8 1, i8 2, i8 3, i8 4>, <3 x i8> <i8 poison, i8 3, i8 9>, <4 x i1> <i1 true, i1 true, i1 true, i1 false>) => { poison, poison, poison, F }
 ; CHECK-NEXT:   %buckets = alloca [8 x i32], align 4 => ptr 0x8 [buckets]
 ; CHECK-NEXT:   %b0 = getelementptr inbounds [8 x i32], ptr %buckets, i64 0, i64 0 => ptr 0x8 [buckets]
 ; CHECK-NEXT:   %b1 = getelementptr inbounds [8 x i32], ptr %buckets, i64 0, i64 1 => ptr 0xC [buckets + 4]
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 87c9f80d41d14..ce1f9c44015b8 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -1732,10 +1732,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       const auto &Vec = Args[0].asAggregate();
       const unsigned RetBW = RetTy->getIntegerBitWidth();
 
-      if (APInt::getMaxValue(RetBW).ult(Vec.size())) {
-        reportImmediateUB() << "experimental.cttz.elts return type too narrow.";
+      if (APInt::getMaxValue(RetBW).ult(Vec.size()))
         return AnyValue::poison();
-      }
 
       uint64_t Count = 0;
       for (const AnyValue &V : Vec) {
@@ -1846,20 +1844,17 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
         for (const AnyValue &Needle : Needles) {
           if (Needle.isPoison()) {
             SawPoison = true;
-            continue;
-          }
-          if (Search[I].asInteger() == Needle.asInteger()) {
-            Found = true;
             break;
           }
+          if (Search[I].asInteger() == Needle.asInteger())
+            Found = true;
         }
 
-        if (Found)
-          Res.push_back(AnyValue::boolean(true));
-        else if (SawPoison)
+        if (SawPoison)
           Res.push_back(AnyValue::poison());
         else
-          Res.push_back(AnyValue::boolean(false));
+          Res.push_back(Found ? AnyValue::boolean(true)
+                              : AnyValue::boolean(false));
       }
 
       return std::move(Res);

>From 120cc020fec75383979358f280311d093406fb42 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Fri, 3 Jul 2026 17:25:13 +0800
Subject: [PATCH 4/5] [llubi] Address comments

---
 .../tools/llubi/intr_experimental_vector.ll   | 23 +++++-
 llvm/tools/llubi/lib/Interpreter.cpp          | 72 +++++++++++--------
 2 files changed, 65 insertions(+), 30 deletions(-)

diff --git a/llvm/test/tools/llubi/intr_experimental_vector.ll b/llvm/test/tools/llubi/intr_experimental_vector.ll
index 37e06d47c0ae5..100e77c189c60 100644
--- a/llvm/test/tools/llubi/intr_experimental_vector.ll
+++ b/llvm/test/tools/llubi/intr_experimental_vector.ll
@@ -63,6 +63,16 @@ define void @main() {
   %min0 = load i32, ptr %b0, align 4
   %min1 = load i32, ptr %b1, align 4
 
+  %overlap = alloca [8 x i8], align 1
+  store i64 0, ptr %overlap, align 1
+  %overlap_p0 = getelementptr inbounds [8 x i8], ptr %overlap, i64 0, i64 0
+  %overlap_p2 = getelementptr inbounds i8, ptr %overlap_p0, i64 2
+  %overlap_ptrs0 = insertelement <2 x ptr> poison, ptr %overlap_p0, i32 0
+  %overlap_ptrs1 = insertelement <2 x ptr> %overlap_ptrs0, ptr %overlap_p2, i32 1
+  call void @llvm.experimental.vector.histogram.add.v2p0.i32(<2 x ptr> %overlap_ptrs1, i32 65536, <2 x i1> <i1 true, i1 true>)
+  %overlap0 = load i32, ptr %overlap_p0, align 1
+  %overlap2 = load i32, ptr %overlap_p2, align 1
+
   ret void
 }
 ; CHECK: Entering function: main
@@ -71,8 +81,8 @@ define void @main() {
 ; CHECK-NEXT:   %cttz_poison = call i8 @llvm.experimental.cttz.elts.i8.v4i1(<4 x i1> zeroinitializer, i1 true) => poison
 ; CHECK-NEXT:   %evl_zero = call i32 @llvm.experimental.get.vector.length.i32(i32 0, i32 4, i1 false) => i32 0
 ; CHECK-NEXT:   %evl_short = call i32 @llvm.experimental.get.vector.length.i32(i32 3, i32 4, i1 false) => i32 3
-; CHECK-NEXT:   %evl_full = call i32 @llvm.experimental.get.vector.length.i32(i32 9, i32 4, i1 false) => i32 4
-; CHECK-NEXT:   %evl_scalable = call i32 @llvm.experimental.get.vector.length.i32(i32 20, i32 4, i1 true) => i32 16
+; CHECK-NEXT:   %evl_full = call i32 @llvm.experimental.get.vector.length.i32(i32 9, i32 4, i1 false) => i32 3
+; CHECK-NEXT:   %evl_scalable = call i32 @llvm.experimental.get.vector.length.i32(i32 20, i32 4, i1 true) => i32 13
 ; CHECK-NEXT:   %evl_poison = call i32 @llvm.experimental.get.vector.length.i64(i64 poison, i32 4, i1 false) => poison
 ; CHECK-NEXT:   %last = call i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> <i1 true, i1 false, i1 true, i1 false>, i32 99) => i32 30
 ; CHECK-NEXT:   %last_passthru = call i32 @llvm.experimental.vector.extract.last.active.v4i32(<4 x i32> <i32 10, i32 20, i32 30, i32 40>, <4 x i1> zeroinitializer, i32 99) => i32 99
@@ -118,5 +128,14 @@ define void @main() {
 ; CHECK-NEXT:   call void @llvm.experimental.vector.histogram.umin.v4p0.i32(<4 x ptr> %ptrs_add3, i32 20, <4 x i1> <i1 true, i1 true, i1 true, i1 false>)
 ; CHECK-NEXT:   %min0 = load i32, ptr %b0, align 4 => i32 7
 ; CHECK-NEXT:   %min1 = load i32, ptr %b1, align 4 => i32 20
+; CHECK-NEXT:   %overlap = alloca [8 x i8], align 1 => ptr 0x29 [overlap]
+; CHECK-NEXT:   store i64 0, ptr %overlap, align 1
+; CHECK-NEXT:   %overlap_p0 = getelementptr inbounds [8 x i8], ptr %overlap, i64 0, i64 0 => ptr 0x29 [overlap]
+; CHECK-NEXT:   %overlap_p2 = getelementptr inbounds i8, ptr %overlap_p0, i64 2 => ptr 0x2B [overlap + 2]
+; CHECK-NEXT:   %overlap_ptrs0 = insertelement <2 x ptr> poison, ptr %overlap_p0, i32 0 => { ptr 0x29 [overlap], poison }
+; CHECK-NEXT:   %overlap_ptrs1 = insertelement <2 x ptr> %overlap_ptrs0, ptr %overlap_p2, i32 1 => { ptr 0x29 [overlap], ptr 0x2B [overlap + 2] }
+; CHECK-NEXT:   call void @llvm.experimental.vector.histogram.add.v2p0.i32(<2 x ptr> %overlap_ptrs1, i32 65536, <2 x i1> splat (i1 true))
+; CHECK-NEXT:   %overlap0 = load i32, ptr %overlap_p0, align 1 => i32 0
+; CHECK-NEXT:   %overlap2 = load i32, ptr %overlap_p2, align 1 => i32 65536
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index ce1f9c44015b8..70ac65e909a12 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -811,18 +811,19 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   }
 
   static BooleanKind getMaskLane(const AnyValue &Mask, size_t I) {
-    if (Mask.isAggregate())
-      return Mask.asAggregate()[I].asBoolean();
-    return Mask.asBoolean();
+    assert(Mask.isAggregate());
+    return Mask.asAggregate()[I].asBoolean();
   }
 
   AnyValue callExperimentalVectorHistogramIntrinsic(CallBase &CB,
                                                     ArrayRef<AnyValue> Args,
                                                     Intrinsic::ID IID) {
-    struct Bucket {
+    struct LaneUpdate {
       MemoryObject *MO;
       uint64_t Offset;
       uint64_t Count;
+      AnyValue Old;
+      AnyValue New;
     };
 
     const auto &Ptrs = Args[0].asAggregate();
@@ -831,7 +832,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     Type *ElemTy = CB.getArgOperand(1)->getType();
     const uint64_t AccessSize = Ctx.getEffectiveTypeStoreSize(ElemTy);
 
-    SmallVector<Bucket, 8> Buckets;
+    SmallVector<LaneUpdate, 8> Lanes;
     for (size_t I = 0, E = Ptrs.size(); I != E; ++I) {
       switch (getMaskLane(Mask, I)) {
       case BooleanKind::False:
@@ -856,19 +857,19 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       if (!MO)
         return AnyValue();
 
-      auto *It =
-          find_if(Buckets, [&, MO = MO, Offset = Offset](const Bucket &B) {
-            return B.MO == MO && B.Offset == Offset;
-          });
-      if (It == Buckets.end())
-        Buckets.push_back({MO, Offset, 1});
-      else
-        ++It->Count;
+      Lanes.push_back({MO, Offset, 0, AnyValue(), AnyValue()});
     }
 
-    for (const auto &[MO, Offset, Count] : Buckets) {
-      AnyValue Old = Ctx.load(*MO, Offset, ElemTy);
-      AnyValue New;
+    for (LaneUpdate &Lane : Lanes) {
+      Lane.Count = count_if(Lanes, [&](const LaneUpdate &Other) {
+        return Other.MO == Lane.MO && Other.Offset == Lane.Offset;
+      });
+      Lane.Old = Ctx.load(*Lane.MO, Lane.Offset, ElemTy);
+    }
+
+    for (LaneUpdate &Lane : Lanes) {
+      const AnyValue &Old = Lane.Old;
+      AnyValue &New = Lane.New;
 
       if (Old.isPoison() || Update.isPoison()) {
         New = AnyValue::poison();
@@ -878,13 +879,13 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
 
         switch (IID) {
         case Intrinsic::experimental_vector_histogram_add:
-          New = OldInt + UpdateInt * APInt(UpdateInt.getBitWidth(), Count,
+          New = OldInt + UpdateInt * APInt(UpdateInt.getBitWidth(), Lane.Count,
                                            /*isSigned=*/false,
                                            /*implicitTrunc=*/true);
           break;
         case Intrinsic::experimental_vector_histogram_uadd_sat: {
           APInt Acc = OldInt;
-          for (uint64_t I = 0; I != Count; ++I)
+          for (uint64_t I = 0; I != Lane.Count; ++I)
             Acc = Acc.uadd_sat(UpdateInt);
           New = Acc;
           break;
@@ -899,10 +900,11 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
           llvm_unreachable("Unexpected histogram intrinsic ID");
         }
       }
-
-      Ctx.store(*MO, Offset, New, ElemTy);
     }
 
+    for (const LaneUpdate &Lane : Lanes)
+      Ctx.store(*Lane.MO, Lane.Offset, Lane.New, ElemTy);
+
     return AnyValue();
   }
 
@@ -1732,7 +1734,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       const auto &Vec = Args[0].asAggregate();
       const unsigned RetBW = RetTy->getIntegerBitWidth();
 
-      if (APInt::getMaxValue(RetBW).ult(Vec.size()))
+      if (!isUIntN(RetBW, Vec.size()))
         return AnyValue::poison();
 
       uint64_t Count = 0;
@@ -1759,16 +1761,31 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       const uint64_t VF = VFC->getZExtValue();
       const bool Scalable = ScalableC->isOne();
 
-      const uint64_t MaxLanes =
-          Scalable ? Ctx.getEVL(ElementCount::getScalable(VF)) : VF;
+      const uint64_t MaxLanes = Ctx.getEVL(ElementCount::get(VF, Scalable));
 
       uint64_t Res = 0;
       if (!Cnt.isZero()) {
-        APInt Max(Cnt.getBitWidth(), MaxLanes);
-        Res = Cnt.ugt(Max) ? MaxLanes : Cnt.getZExtValue();
+        if (Cnt.getActiveBits() <= 64 && Cnt.getZExtValue() <= MaxLanes) {
+          Res = Cnt.getZExtValue();
+        } else {
+          auto ceilUDiv = [](const APInt &N, const APInt &D) {
+            APInt Q = N.udiv(D);
+            if (!N.urem(D).isZero())
+              ++Q;
+            return Q;
+          };
+
+          APInt Max(Cnt.getBitWidth(), MaxLanes);
+          APInt NumIters = ceilUDiv(Cnt, Max);
+          uint64_t Lower = ceilUDiv(Cnt, NumIters).getZExtValue();
+          uint64_t Range = MaxLanes - Lower + 1;
+          Res = Lower + Ctx.getRandomUInt64() % Range;
+        }
       }
 
-      return APInt(32, Res);
+      if (isIntN(32, Res))
+        return APInt(32, Res);
+      return AnyValue::poison();
     }
 
     case Intrinsic::experimental_vector_extract_last_active: {
@@ -1853,8 +1870,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
         if (SawPoison)
           Res.push_back(AnyValue::poison());
         else
-          Res.push_back(Found ? AnyValue::boolean(true)
-                              : AnyValue::boolean(false));
+          Res.push_back(AnyValue::boolean(Found));
       }
 
       return std::move(Res);

>From e02b41e59b3a4aaebdb545b4da78c6825eccea6c Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Sun, 5 Jul 2026 13:43:08 +0800
Subject: [PATCH 5/5] [llubi] Address comments

---
 llvm/tools/llubi/lib/Interpreter.cpp | 16 ++++++----------
 1 file changed, 6 insertions(+), 10 deletions(-)

diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 70ac65e909a12..425215b4aee2e 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -811,7 +811,6 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   }
 
   static BooleanKind getMaskLane(const AnyValue &Mask, size_t I) {
-    assert(Mask.isAggregate());
     return Mask.asAggregate()[I].asBoolean();
   }
 
@@ -833,6 +832,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     const uint64_t AccessSize = Ctx.getEffectiveTypeStoreSize(ElemTy);
 
     SmallVector<LaneUpdate, 8> Lanes;
+    Lanes.reserve(Ptrs.size());
     for (size_t I = 0, E = Ptrs.size(); I != E; ++I) {
       switch (getMaskLane(Mask, I)) {
       case BooleanKind::False:
@@ -1768,16 +1768,12 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
         if (Cnt.getActiveBits() <= 64 && Cnt.getZExtValue() <= MaxLanes) {
           Res = Cnt.getZExtValue();
         } else {
-          auto ceilUDiv = [](const APInt &N, const APInt &D) {
-            APInt Q = N.udiv(D);
-            if (!N.urem(D).isZero())
-              ++Q;
-            return Q;
-          };
-
           APInt Max(Cnt.getBitWidth(), MaxLanes);
-          APInt NumIters = ceilUDiv(Cnt, Max);
-          uint64_t Lower = ceilUDiv(Cnt, NumIters).getZExtValue();
+          APInt NumIters =
+              APIntOps::RoundingUDiv(Cnt, Max, APInt::Rounding::UP);
+          uint64_t Lower =
+              APIntOps::RoundingUDiv(Cnt, NumIters, APInt::Rounding::UP)
+                  .getZExtValue();
           uint64_t Range = MaxLanes - Lower + 1;
           Res = Lower + Ctx.getRandomUInt64() % Range;
         }



More information about the llvm-commits mailing list