[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