[llvm] 63492ca - [llubi] Add support for GEP/alloca/inttoptr (#181861)

via llvm-commits llvm-commits at lists.llvm.org
Thu Feb 19 04:12:55 PST 2026


Author: Yingwei Zheng
Date: 2026-02-19T20:12:50+08:00
New Revision: 63492ca84a8c84b5d468250b938183602a39805a

URL: https://github.com/llvm/llvm-project/commit/63492ca84a8c84b5d468250b938183602a39805a
DIFF: https://github.com/llvm/llvm-project/commit/63492ca84a8c84b5d468250b938183602a39805a.diff

LOG: [llubi] Add support for GEP/alloca/inttoptr (#181861)

This patch mainly adds support for GEP. To test GEP functionality,
alloca/inttoptr are also introduced.

Added: 
    llvm/test/tools/llubi/alloca.ll
    llvm/test/tools/llubi/alloca_large_count.ll
    llvm/test/tools/llubi/alloca_large_size.ll
    llvm/test/tools/llubi/alloca_poison_count.ll
    llvm/test/tools/llubi/alloca_size_overflow.ll
    llvm/test/tools/llubi/gep.ll
    llvm/test/tools/llubi/inttoptr.ll

Modified: 
    llvm/tools/llubi/lib/Context.cpp
    llvm/tools/llubi/lib/Context.h
    llvm/tools/llubi/lib/Interpreter.cpp
    llvm/tools/llubi/lib/Value.cpp
    llvm/tools/llubi/lib/Value.h

Removed: 
    


################################################################################
diff  --git a/llvm/test/tools/llubi/alloca.ll b/llvm/test/tools/llubi/alloca.ll
new file mode 100644
index 0000000000000..51f50dff67fc0
--- /dev/null
+++ b/llvm/test/tools/llubi/alloca.ll
@@ -0,0 +1,41 @@
+; 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 = "e-p:64:64:64:32"
+
+%struct = type { i64, [2 x i32], i64 }
+
+define i32 @count() {
+  ret i32 4
+}
+
+define ptr @stack_address() {
+  %alloc = alloca i32
+  ret ptr %alloc
+}
+
+define void @main() {
+  %alloc = alloca i32
+  %alloc_large_align = alloca i32, align 64
+  %count = call i32 @count()
+  %alloc_dyn = alloca i32, i32 %count
+  %alloc_struct = alloca %struct
+  %ptr = call ptr @stack_address()
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT:   %alloc_large_align = alloca i32, align 64 => ptr 0x40 [alloc_large_align]
+; CHECK-NEXT: Entering function: count
+; CHECK-NEXT:   ret i32 4
+; CHECK-NEXT: Exiting function: count
+; CHECK-NEXT:   %count = call i32 @count() => i32 4
+; CHECK-NEXT:   %alloc_dyn = alloca i32, i32 %count, align 4 => ptr 0x44 [alloc_dyn]
+; CHECK-NEXT:   %alloc_struct = alloca %struct, align 8 => ptr 0x58 [alloc_struct]
+; CHECK-NEXT: Entering function: stack_address
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x70 [alloc]
+; CHECK-NEXT:   ret ptr %alloc
+; CHECK-NEXT: Exiting function: stack_address
+; CHECK-NEXT:   %ptr = call ptr @stack_address() => ptr 0x70 [dangling]
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main

diff  --git a/llvm/test/tools/llubi/alloca_large_count.ll b/llvm/test/tools/llubi/alloca_large_count.ll
new file mode 100644
index 0000000000000..62772317ae561
--- /dev/null
+++ b/llvm/test/tools/llubi/alloca_large_count.ll
@@ -0,0 +1,10 @@
+; 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() {
+  %alloc_dyn = alloca i32, i128 -1
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Alloca with large array size that overflows uint64_t.
+; CHECK-NEXT: error: Execution of function 'main' failed.

diff  --git a/llvm/test/tools/llubi/alloca_large_size.ll b/llvm/test/tools/llubi/alloca_large_size.ll
new file mode 100644
index 0000000000000..a9773d3bcb184
--- /dev/null
+++ b/llvm/test/tools/llubi/alloca_large_size.ll
@@ -0,0 +1,10 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose --max-mem=100 < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %alloc_dyn = alloca i32, i32 100
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Error: Insufficient stack space.
+; CHECK-NEXT: error: Execution of function 'main' failed.

diff  --git a/llvm/test/tools/llubi/alloca_poison_count.ll b/llvm/test/tools/llubi/alloca_poison_count.ll
new file mode 100644
index 0000000000000..c8c97c4517bfc
--- /dev/null
+++ b/llvm/test/tools/llubi/alloca_poison_count.ll
@@ -0,0 +1,10 @@
+; 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() {
+  %alloc_dyn = alloca i32, i32 poison
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Alloca with poison array size.
+; CHECK-NEXT: error: Execution of function 'main' failed.

diff  --git a/llvm/test/tools/llubi/alloca_size_overflow.ll b/llvm/test/tools/llubi/alloca_size_overflow.ll
new file mode 100644
index 0000000000000..258f94d57d90f
--- /dev/null
+++ b/llvm/test/tools/llubi/alloca_size_overflow.ll
@@ -0,0 +1,10 @@
+; 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() {
+  %alloc_dyn = alloca i32, i64 -1
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Alloca with allocation size that overflows uint64_t.
+; CHECK-NEXT: error: Execution of function 'main' failed.

diff  --git a/llvm/test/tools/llubi/gep.ll b/llvm/test/tools/llubi/gep.ll
new file mode 100644
index 0000000000000..4376569fa1811
--- /dev/null
+++ b/llvm/test/tools/llubi/gep.ll
@@ -0,0 +1,146 @@
+; 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 = "e-p:64:64:64:32"
+
+%struct = type { i64, [2 x i32], i64 }
+
+define ptr @dead_stack_object() {
+  %alloc = alloca i32
+  ret ptr %alloc
+}
+
+define void @main() {
+  %alloc = alloca i32
+  %alloc_struct = alloca %struct
+
+  %gep = getelementptr i8, ptr %alloc, i64 3
+  %gep_poison_base = getelementptr i8, ptr poison, i64 1
+  %gep_poison_idx = getelementptr i8, ptr %alloc, i64 poison
+  ; The indices are first converted to offsets in the pointer’s index type.
+  ; The index itself is sign-extended or truncated, and then multiplied by the type allocation size
+  %gep_trunc_idx = getelementptr i32, ptr %alloc, i128 18446744073709551616
+  %gep_sext_idx = getelementptr i32, ptr %alloc, i8 -1
+  ; The offsets are then added to the low bits of the base address up to the index type width,
+  ; with silently-wrapping two’s complement arithmetic.
+  %large_address = inttoptr i64 -1 to ptr
+  %gep_update_idx_bits = getelementptr i32, ptr %large_address, i64 1
+  %gep_struct = getelementptr %struct, ptr %alloc_struct, i64 0, i32 1, i32 1
+  %gep_scalable_vec = getelementptr <vscale x 4 x i32>, ptr null, i64 4
+
+  %gep_vec_idx = getelementptr [2 x i32], ptr %alloc, i64 1, <2 x i64> <i64 0, i64 2>
+  %ptr_vec_insert = insertelement <2 x ptr> poison, ptr %alloc, i32 0
+  %ptr_vec_splat = shufflevector <2 x ptr> %ptr_vec_insert, <2 x ptr> poison, <2 x i32> zeroinitializer
+  %gep_vec_ptr = getelementptr i32, <2 x ptr> %ptr_vec_splat, i64 1
+  %gep_vec_ptr_vec_idx = getelementptr i32, <2 x ptr> %ptr_vec_splat, <2 x i64> <i64 0, i64 2>
+
+  ; Note that getelementptr with all-zero indices is always considered to be inbounds, even if the base
+  ; pointer does not point to an allocated object. As a corollary, the only pointer in bounds of the null
+  ; pointer in the default address space is the null pointer itself.
+  %gep_inbounds_valid_null = getelementptr inbounds i8, ptr null, i64 0
+  %gep_inbounds_invalid_null = getelementptr inbounds i8, ptr null, i64 1
+  ; The base pointer has an in bounds address of the allocated object that it is based on.
+  ; This means that it points into that allocated object, or to its end.
+  %gep_inbounds_valid1 = getelementptr inbounds i8, ptr %alloc, i64 3
+  %gep_inbounds_valid2 = getelementptr inbounds i8, ptr %alloc, i64 4
+  %gep_inbounds_invalid1 = getelementptr inbounds i8, ptr %alloc, i64 -1
+  %gep_inbounds_invalid2 = getelementptr inbounds i8, ptr %alloc, i64 5
+  ; During the successive addition of offsets to the address, the resulting pointer
+  ; must remain in bounds of the allocated object at each step.
+  %gep_inbounds_invalid3 = getelementptr inbounds %struct, ptr %alloc_struct, i64 1, i32 1, i32 -2
+  %dead_stack_ptr = call ptr @dead_stack_object()
+  ; The object does not have to be live anymore; being in-bounds of a deallocated object is sufficient.
+  %gep_inbounds_valid4 = getelementptr inbounds i8, ptr %dead_stack_ptr, i64 4
+  %gep_inbounds_invalid4 = getelementptr inbounds i8, ptr %dead_stack_ptr, i64 5
+
+  ; If the type of an index is larger than the pointer index type, the truncation to the pointer index
+  ; type preserves the signed value (trunc nsw).
+  %gep_nusw_valid1 = getelementptr nusw i8, ptr %alloc, i64 -1
+  %gep_nusw_invalid1 = getelementptr nusw i8, ptr %large_address, i64 -2147483649
+  ; The multiplication of an index by the type size does not wrap the pointer index type in a signed sense (mul nsw).
+  %gep_nusw_valid2 = getelementptr nusw i32, ptr null, i32 536870911
+  %gep_nusw_invalid2 = getelementptr nusw i32, ptr null, i32 536870912
+  ; The successive addition of each offset (without adding the base address) does not wrap the pointer
+  ; index type in a signed sense (add nsw).
+  %gep_nusw_valid3 = getelementptr nusw [2 x i16], ptr null, i32 536870911, i32 1
+  %gep_nusw_invalid3 = getelementptr nusw [2 x i16], ptr null, i32 536870911, i32 2
+  ; 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.
+  %large_address2 = inttoptr i64 -4 to ptr
+  %gep_nusw_valid4 = getelementptr nusw i8, ptr %large_address2, i64 3
+  %gep_nusw_invalid4 = getelementptr nusw i8, ptr %large_address2, i64 4
+  %gep_nusw_valid5 = getelementptr nusw i8, ptr %large_address2, i64 -4
+  %gep_nusw_invalid5 = getelementptr nusw i8, ptr %alloc, i64 -16
+
+  ; If the type of an index is larger than the pointer index type,
+  ; the truncation to the pointer index type preserves the unsigned value (trunc nuw).
+  %gep_nuw_valid1 = getelementptr nuw i8, ptr null, i64 2147483647
+  %gep_nuw_invalid1 = getelementptr nuw i8, ptr null, i64 -1
+  ; The multiplication of an index by the type size does not wrap
+  ; the pointer index type in an unsigned sense (mul nuw).
+  %gep_nuw_valid2 = getelementptr nuw i32, ptr null, i32 1073741823
+  %gep_nuw_invalid2 = getelementptr nuw i32, ptr null, i32 1073741824
+  ; The successive addition of each offset (without adding the base address) does not
+  ; wrap the pointer index type in an unsigned sense (add nuw).
+  %gep_nuw_valid3 = getelementptr nuw [2 x i16], ptr null, i32 1073741823, i32 1
+  %gep_nuw_invalid3 = getelementptr nuw [2 x i16], ptr null, i32 1073741823, i32 2
+  ; The successive addition of the current address, truncated to the pointer index type and interpreted
+  ; as an unsigned number, and each offset, also interpreted as an unsigned number, does not wrap the
+  ; pointer index type (add nuw).
+  %gep_nuw_valid4 = getelementptr nuw i32, ptr %alloc, i64 1073741821
+  %gep_nuw_invalid4 = getelementptr nuw i32, ptr %alloc, i64 1073741822
+
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT:   %alloc_struct = alloca %struct, align 8 => ptr 0x10 [alloc_struct]
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %alloc, i64 3 => ptr 0xB [alloc + 3]
+; CHECK-NEXT:   %gep_poison_base = getelementptr i8, ptr poison, i64 1 => poison
+; CHECK-NEXT:   %gep_poison_idx = getelementptr i8, ptr %alloc, i64 poison => poison
+; CHECK-NEXT:   %gep_trunc_idx = getelementptr i32, ptr %alloc, i128 18446744073709551616 => ptr 0x8 [alloc]
+; CHECK-NEXT:   %gep_sext_idx = getelementptr i32, ptr %alloc, i8 -1 => ptr 0x4 [alloc + -4]
+; CHECK-NEXT:   %large_address = inttoptr i64 -1 to ptr => ptr 0xFFFFFFFFFFFFFFFF [dangling]
+; CHECK-NEXT:   %gep_update_idx_bits = getelementptr i32, ptr %large_address, i64 1 => ptr 0xFFFFFFFF00000003 [dangling]
+; CHECK-NEXT:   %gep_struct = getelementptr %struct, ptr %alloc_struct, i64 0, i32 1, i32 1 => ptr 0x1C [alloc_struct + 12]
+; CHECK-NEXT:   %gep_scalable_vec = getelementptr <vscale x 4 x i32>, ptr null, i64 4 => ptr 0x100 [dangling]
+; CHECK-NEXT:   %gep_vec_idx = getelementptr [2 x i32], ptr %alloc, i64 1, <2 x i64> <i64 0, i64 2> => { ptr 0x10 [alloc + 8], ptr 0x18 [alloc + 16] }
+; CHECK-NEXT:   %ptr_vec_insert = insertelement <2 x ptr> poison, ptr %alloc, i32 0 => { ptr 0x8 [alloc], poison }
+; CHECK-NEXT:   %ptr_vec_splat = shufflevector <2 x ptr> %ptr_vec_insert, <2 x ptr> poison, <2 x i32> zeroinitializer => { ptr 0x8 [alloc], ptr 0x8 [alloc] }
+; CHECK-NEXT:   %gep_vec_ptr = getelementptr i32, <2 x ptr> %ptr_vec_splat, i64 1 => { ptr 0xC [alloc + 4], ptr 0xC [alloc + 4] }
+; CHECK-NEXT:   %gep_vec_ptr_vec_idx = getelementptr i32, <2 x ptr> %ptr_vec_splat, <2 x i64> <i64 0, i64 2> => { ptr 0x8 [alloc], ptr 0x10 [alloc + 8] }
+; CHECK-NEXT:   %gep_inbounds_valid_null = getelementptr inbounds i8, ptr null, i64 0 => ptr 0x0 [dangling]
+; CHECK-NEXT:   %gep_inbounds_invalid_null = getelementptr inbounds i8, ptr null, i64 1 => poison
+; CHECK-NEXT:   %gep_inbounds_valid1 = getelementptr inbounds i8, ptr %alloc, i64 3 => ptr 0xB [alloc + 3]
+; CHECK-NEXT:   %gep_inbounds_valid2 = getelementptr inbounds i8, ptr %alloc, i64 4 => ptr 0xC [alloc + 4]
+; CHECK-NEXT:   %gep_inbounds_invalid1 = getelementptr inbounds i8, ptr %alloc, i64 -1 => poison
+; CHECK-NEXT:   %gep_inbounds_invalid2 = getelementptr inbounds i8, ptr %alloc, i64 5 => poison
+; CHECK-NEXT:   %gep_inbounds_invalid3 = getelementptr inbounds %struct, ptr %alloc_struct, i64 1, i32 1, i32 -2 => poison
+; CHECK-NEXT: Entering function: dead_stack_object
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x28 [alloc]
+; CHECK-NEXT:   ret ptr %alloc
+; CHECK-NEXT: Exiting function: dead_stack_object
+; CHECK-NEXT:   %dead_stack_ptr = call ptr @dead_stack_object() => ptr 0x28 [dangling]
+; CHECK-NEXT:   %gep_inbounds_valid4 = getelementptr inbounds i8, ptr %dead_stack_ptr, i64 4 => ptr 0x2C [dangling]
+; CHECK-NEXT:   %gep_inbounds_invalid4 = getelementptr inbounds i8, ptr %dead_stack_ptr, i64 5 => poison
+; CHECK-NEXT:   %gep_nusw_valid1 = getelementptr nusw i8, ptr %alloc, i64 -1 => ptr 0x7 [alloc + -1]
+; CHECK-NEXT:   %gep_nusw_invalid1 = getelementptr nusw i8, ptr %large_address, i64 -2147483649 => poison
+; CHECK-NEXT:   %gep_nusw_valid2 = getelementptr nusw i32, ptr null, i32 536870911 => ptr 0x7FFFFFFC [dangling]
+; CHECK-NEXT:   %gep_nusw_invalid2 = getelementptr nusw i32, ptr null, i32 536870912 => poison
+; CHECK-NEXT:   %gep_nusw_valid3 = getelementptr nusw [2 x i16], ptr null, i32 536870911, i32 1 => ptr 0x7FFFFFFE [dangling]
+; CHECK-NEXT:   %gep_nusw_invalid3 = getelementptr nusw [2 x i16], ptr null, i32 536870911, i32 2 => poison
+; CHECK-NEXT:   %large_address2 = inttoptr i64 -4 to ptr => ptr 0xFFFFFFFFFFFFFFFC [dangling]
+; CHECK-NEXT:   %gep_nusw_valid4 = getelementptr nusw i8, ptr %large_address2, i64 3 => ptr 0xFFFFFFFFFFFFFFFF [dangling]
+; CHECK-NEXT:   %gep_nusw_invalid4 = getelementptr nusw i8, ptr %large_address2, i64 4 => poison
+; CHECK-NEXT:   %gep_nusw_valid5 = getelementptr nusw i8, ptr %large_address2, i64 -4 => ptr 0xFFFFFFFFFFFFFFF8 [dangling]
+; CHECK-NEXT:   %gep_nusw_invalid5 = getelementptr nusw i8, ptr %alloc, i64 -16 => poison
+; CHECK-NEXT:   %gep_nuw_valid1 = getelementptr nuw i8, ptr null, i64 2147483647 => ptr 0x7FFFFFFF [dangling]
+; CHECK-NEXT:   %gep_nuw_invalid1 = getelementptr nuw i8, ptr null, i64 -1 => poison
+; CHECK-NEXT:   %gep_nuw_valid2 = getelementptr nuw i32, ptr null, i32 1073741823 => ptr 0xFFFFFFFC [dangling]
+; CHECK-NEXT:   %gep_nuw_invalid2 = getelementptr nuw i32, ptr null, i32 1073741824 => poison
+; CHECK-NEXT:   %gep_nuw_valid3 = getelementptr nuw [2 x i16], ptr null, i32 1073741823, i32 1 => ptr 0xFFFFFFFE [dangling]
+; CHECK-NEXT:   %gep_nuw_invalid3 = getelementptr nuw [2 x i16], ptr null, i32 1073741823, i32 2 => poison
+; CHECK-NEXT:   %gep_nuw_valid4 = getelementptr nuw i32, ptr %alloc, i64 1073741821 => ptr 0xFFFFFFFC [alloc + 4294967284]
+; CHECK-NEXT:   %gep_nuw_invalid4 = getelementptr nuw i32, ptr %alloc, i64 1073741822 => poison
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main

diff  --git a/llvm/test/tools/llubi/inttoptr.ll b/llvm/test/tools/llubi/inttoptr.ll
new file mode 100644
index 0000000000000..4e672c38fb6fd
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr.ll
@@ -0,0 +1,17 @@
+; 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 = "e-p:64:64:64"
+
+define void @main() {
+  %ptr1 = inttoptr i64 0 to ptr
+  %ptr2 = inttoptr i8 255 to ptr
+  %ptr3 = inttoptr i128 -1 to ptr
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %ptr1 = inttoptr i64 0 to ptr => ptr 0x0 [dangling]
+; CHECK-NEXT:   %ptr2 = inttoptr i8 -1 to ptr => ptr 0xFF [dangling]
+; CHECK-NEXT:   %ptr3 = inttoptr i128 -1 to ptr => ptr 0xFFFFFFFFFFFFFFFF [dangling]
+; 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 9e3647be00988..adedfbcc3886b 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -59,6 +59,10 @@ AnyValue Context::getConstantValueImpl(Constant *C) {
   if (isa<ConstantAggregateZero>(C))
     return AnyValue::getNullValue(*this, C->getType());
 
+  if (isa<ConstantPointerNull>(C))
+    return Pointer::null(
+        DL.getPointerSizeInBits(C->getType()->getPointerAddressSpace()));
+
   if (auto *CI = dyn_cast<ConstantInt>(C)) {
     if (auto *VecTy = dyn_cast<VectorType>(CI->getType()))
       return std::vector<AnyValue>(getEVL(VecTy->getElementCount()),
@@ -148,10 +152,8 @@ bool Context::free(uint64_t Address) {
 
 Pointer Context::deriveFromMemoryObject(IntrusiveRefCntPtr<MemoryObject> Obj) {
   assert(Obj && "Cannot determine the address space of a null memory object");
-  return Pointer(
-      Obj,
-      APInt(DL.getPointerSizeInBits(Obj->getAddressSpace()), Obj->getAddress()),
-      /*Offset=*/0);
+  return Pointer(Obj, APInt(DL.getPointerSizeInBits(Obj->getAddressSpace()),
+                            Obj->getAddress()));
 }
 
 Function *Context::getTargetFunction(const Pointer &Ptr) {

diff  --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 29621943c800e..25ba940323ebf 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -68,6 +68,10 @@ class MemoryObject : public RefCountedBase<MemoryObject> {
   bool isConstant() const { return IsConstant; }
   void setIsConstant(bool C) { IsConstant = C; }
 
+  bool inBounds(const APInt &NewAddr) const {
+    return NewAddr.uge(Address) && NewAddr.ule(Address + Size);
+  }
+
   Byte &operator[](uint64_t Offset) {
     assert(Offset < Size && "Offset out of bounds");
     return Bytes[Offset];
@@ -129,6 +133,13 @@ class Context {
   // For now we don't model the behavior of address reuse, which is common
   // with stack coloring.
   uint64_t AllocationBase = 8;
+  // Maintains a global list of 'exposed' provenances. This is used to form a
+  // pointer with an exposed provenance.
+  // FIXME: Currently all the allocations are considered exposed, regardless of
+  // their interaction with ptrtoint. That is, ptrtoint is allowed to recover
+  // the provenance of any allocation. We may track the exposed provenances more
+  // precisely after we make ptrtoint have the implicit side-effect of exposing
+  // the provenance.
   std::map<uint64_t, IntrusiveRefCntPtr<MemoryObject>> MemoryObjects;
 
   // Constants

diff  --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index a5d39012e5c53..7961f7551cf48 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -12,6 +12,7 @@
 
 #include "Context.h"
 #include "Value.h"
+#include "llvm/IR/GetElementPtrTypeIterator.h"
 #include "llvm/IR/InlineAsm.h"
 #include "llvm/IR/InstVisitor.h"
 #include "llvm/IR/Operator.h"
@@ -119,6 +120,7 @@ static AnyValue mulNoWrap(const APInt &LHS, const APInt &RHS, bool HasNSW,
 /// InstExecutor only maintains the state for call frames.
 class InstExecutor : public InstVisitor<InstExecutor, void> {
   Context &Ctx;
+  const DataLayout &DL;
   EventHandler &Handler;
   std::list<Frame> CallStack;
   // Used to indicate whether the interpreter should continue execution.
@@ -246,10 +248,92 @@ 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);
+
+    auto *MO = Ptr.getMemoryObject();
+    if (Flags.isInBounds() && (!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.
+    return Ptr.getWithNewAddr(NewAddr);
+  }
+
+  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);
+  }
+
 public:
   InstExecutor(Context &C, EventHandler &H, Function &F,
                ArrayRef<AnyValue> Args, AnyValue &RetVal)
-      : Ctx(C), Handler(H), Status(true) {
+      : Ctx(C), DL(Ctx.getDataLayout()), Handler(H), Status(true) {
     CallStack.emplace_back(F, /*CallSite=*/nullptr, /*LastFrame=*/nullptr, Args,
                            RetVal, Ctx.getTLIImpl());
   }
@@ -714,6 +798,128 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
     setResult(SI, std::move(Res));
   }
 
+  void visitAllocaInst(AllocaInst &AI) {
+    uint64_t AllocSize =
+        DL.getTypeAllocSize(AI.getAllocatedType()).getFixedValue();
+    if (AI.isArrayAllocation()) {
+      auto &Size = getValue(AI.getArraySize());
+      if (Size.isPoison()) {
+        reportImmediateUB("Alloca with poison array size.");
+        return;
+      }
+      if (Size.asInteger().getActiveBits() > 64) {
+        reportImmediateUB(
+            "Alloca with large array size that overflows uint64_t.");
+        return;
+      }
+      bool Overflowed = false;
+      AllocSize = SaturatingMultiply(AllocSize, Size.asInteger().getZExtValue(),
+                                     &Overflowed);
+      if (Overflowed) {
+        reportImmediateUB(
+            "Alloca with allocation size that overflows uint64_t.");
+        return;
+      }
+    }
+    // FIXME: If it is used by llvm.lifetime.start, it should be initially dead.
+    auto Obj = Ctx.allocate(AllocSize, AI.getPointerAlignment(DL).value(),
+                            AI.getName(), AI.getAddressSpace(),
+                            MemInitKind::Uninitialized);
+    if (!Obj) {
+      reportError("Insufficient stack space.");
+      return;
+    }
+    CurrentFrame->Allocas.push_back(Obj);
+    setResult(AI, Ctx.deriveFromMemoryObject(Obj));
+  }
+
+  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,
+                  Offset.isScalable()
+                      ? Offset.getKnownMinValue() * Ctx.getVScale()
+                      : Offset.getFixedValue(),
+                  /*isSigned=*/false, /*implicitTrunc=*/true);
+      if (!Scale.isZero())
+        ApplyScaledOffset(getValue(V), Scale);
+    }
+
+    setResult(GEP, std::move(Res));
+  }
+
+  void visitIntToPtr(IntToPtrInst &I) {
+    return visitUnOp(I, [&](const AnyValue &V) -> AnyValue {
+      if (V.isPoison())
+        return AnyValue::poison();
+      // TODO: expose provenance
+      // TODO: check metadata
+      return Pointer(V.asInteger().zextOrTrunc(
+          DL.getPointerSizeInBits(I.getType()->getPointerAddressSpace())));
+    });
+  }
+
   void visitInstruction(Instruction &I) {
     Handler.onUnrecognizedInstruction(I);
     Status = false;
@@ -834,6 +1040,9 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
         assert((Top.Func.getReturnType()->isVoidTy() || !Top.RetVal.isNone()) &&
                "Expected return value to be set on function exit.");
         Handler.onFunctionExit(Top.Func, Top.RetVal);
+        // Free stack objects allocated in this frame.
+        for (auto &Obj : Top.Allocas)
+          Ctx.free(Obj->getAddress());
         CallStack.pop_back();
       } else {
         assert(Top.State == FrameState::Pending &&

diff  --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 57cd94ef0f7bb..172e810bcb77e 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -20,10 +20,11 @@ void Pointer::print(raw_ostream &OS) const {
   SmallString<32> AddrStr;
   Address.toStringUnsigned(AddrStr, 16);
   OS << "ptr 0x" << AddrStr << " [";
-  if (Obj) {
+  if (Obj && Obj->getState() != MemoryObjectState::Freed) {
     OS << Obj->getName();
-    if (Offset)
-      OS << " + " << Offset;
+    // TODO: print " (dead)" if the stack object is out of lifetime.
+    if (Address != Obj->getAddress())
+      OS << " + " << (Address - Obj->getAddress());
   } else {
     OS << "dangling";
   }
@@ -31,7 +32,7 @@ void Pointer::print(raw_ostream &OS) const {
 }
 
 AnyValue Pointer::null(unsigned BitWidth) {
-  return AnyValue(Pointer(nullptr, APInt::getZero(BitWidth), 0));
+  return AnyValue(Pointer(nullptr, APInt::getZero(BitWidth)));
 }
 
 void AnyValue::print(raw_ostream &OS) const {
@@ -227,4 +228,9 @@ AnyValue AnyValue::getNullValue(Context &Ctx, Type *Ty) {
   llvm_unreachable("Unsupported type");
 }
 
+AnyValue AnyValue::getVectorSplat(const AnyValue &Scalar, size_t NumElements) {
+  assert(!Scalar.isAggregate() && !Scalar.isNone() && "Expect a scalar value");
+  return AnyValue(std::vector<AnyValue>(NumElements, Scalar));
+}
+
 } // namespace llvm::ubi

diff  --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index 44fd4727bd78f..6add6e7f06304 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -64,14 +64,15 @@ class Pointer {
   // The address of the pointer. The bit width is determined by
   // DataLayout::getPointerSizeInBits.
   APInt Address;
-  // The offset within the memory object.
-  uint64_t Offset;
   // TODO: modeling inrange(Start, End) attribute
 
 public:
-  explicit Pointer(IntrusiveRefCntPtr<MemoryObject> Obj, const APInt &Address,
-                   uint64_t Offset)
-      : Obj(std::move(Obj)), Address(Address), Offset(Offset) {}
+  explicit Pointer(const APInt &Address) : Obj(nullptr), Address(Address) {}
+  explicit Pointer(IntrusiveRefCntPtr<MemoryObject> Obj, const APInt &Address)
+      : Obj(std::move(Obj)), Address(Address) {}
+  Pointer getWithNewAddr(const APInt &NewAddr) const {
+    return Pointer(Obj, NewAddr);
+  }
   static AnyValue null(unsigned BitWidth);
   void print(raw_ostream &OS) const;
   const APInt &address() const { return Address; }
@@ -112,9 +113,14 @@ class [[nodiscard]] AnyValue {
   static AnyValue boolean(bool Val) { return AnyValue(APInt(1, Val)); }
   static AnyValue getPoisonValue(Context &Ctx, Type *Ty);
   static AnyValue getNullValue(Context &Ctx, Type *Ty);
+  static AnyValue getVectorSplat(const AnyValue &Scalar, size_t NumElements);
 
   bool isNone() const { return Kind == StorageKind::None; }
   bool isPoison() const { return Kind == StorageKind::Poison; }
+  bool isInteger() const { return Kind == StorageKind::Integer; }
+  bool isFloat() const { return Kind == StorageKind::Float; }
+  bool isPointer() const { return Kind == StorageKind::Pointer; }
+  bool isAggregate() const { return Kind == StorageKind::Aggregate; }
 
   const APInt &asInteger() const {
     assert(Kind == StorageKind::Integer && "Expect an integer value");


        


More information about the llvm-commits mailing list