[llvm] 9d43f86 - [llubi] Add support for exposed provenance (#200596)

via llvm-commits llvm-commits at lists.llvm.org
Sat Jun 13 21:57:39 PDT 2026


Author: Yingwei Zheng
Date: 2026-06-14T04:57:34Z
New Revision: 9d43f86e6f302604b109ffa9df7de793aeb801d8

URL: https://github.com/llvm/llvm-project/commit/9d43f86e6f302604b109ffa9df7de793aeb801d8
DIFF: https://github.com/llvm/llvm-project/commit/9d43f86e6f302604b109ffa9df7de793aeb801d8.diff

LOG: [llubi] Add support for exposed provenance (#200596)

This patch implements the semantics of exposed provenance, as described
in [nikic's RFC draft](https://hackmd.io/@nikic/SJBt4mFCll) and
[Miri](https://doc.rust-lang.org/beta/nightly-rustc/miri/enum.Provenance.html).

The provenance of an inttoptr is marked as "wildcard", which picks one
from previously exposed provenances each time a memory access is
performed. For angelic non-determinism, a snapshot of the exposed
provenance set is recorded when inttoptr executes. When a memory access
is performed, all invalid provenances are masked out. If we fail to pick
one, it is UB.

Since all memory objects in llubi are non-overlapping (i.e., there is at
most one memory object satisfying `Obj->inBounds(Addr)` for each
address), we can determine a unique memory object for a wildcard
provenance when the first memory access is performed.
This matches Miri's behavior. Another variant is to resolve the memory
object when inttoptr executes, which gives a limited provenance set
(implemented in the second commit). See also the comments in nikic's RFC
(`For example, one could limit inttoptr to only recover the provenance
of an object that the address is part of.`).

Another thing to be noted is that `gep inbounds` cannot determine the
associated memory object of a wildcard provenance, because it doesn't
cause UB. Instead, I reuse the Obj field to represent something like
`this pointer has a wildcard provenance, but it is only allowed to use
exposed provenances associated with a specific memory object, when
performing load/store via the pointer`. This is an approximation: we
cannot update the wildcard provenance if the gep is used by an icmp and
a branch.

As we don't support `captures` for now, all checkers just return true.
I'll add more tests in the `captures` patch.

Added: 
    llvm/test/tools/llubi/inttoptr_freed.ll
    llvm/test/tools/llubi/inttoptr_generation.ll
    llvm/test/tools/llubi/inttoptr_generation2.ll
    llvm/test/tools/llubi/inttoptr_gep.ll
    llvm/test/tools/llubi/inttoptr_multiobj.ll
    llvm/test/tools/llubi/inttoptr_multiobj2.ll
    llvm/test/tools/llubi/inttoptr_oob.ll
    llvm/test/tools/llubi/inttoptr_oob2.ll

Modified: 
    llvm/test/tools/llubi/alloca.ll
    llvm/test/tools/llubi/attributes.ll
    llvm/test/tools/llubi/gep.ll
    llvm/test/tools/llubi/inttoptr.ll
    llvm/test/tools/llubi/lib_abort.ll
    llvm/test/tools/llubi/lib_exit.ll
    llvm/test/tools/llubi/lib_io.ll
    llvm/test/tools/llubi/lib_printf_format.ll
    llvm/test/tools/llubi/lib_terminate.ll
    llvm/test/tools/llubi/loadstore_be.ll
    llvm/test/tools/llubi/loadstore_le.ll
    llvm/test/tools/llubi/metadata.ll
    llvm/tools/llubi/lib/Context.cpp
    llvm/tools/llubi/lib/Context.h
    llvm/tools/llubi/lib/ExecutorBase.cpp
    llvm/tools/llubi/lib/ExecutorBase.h
    llvm/tools/llubi/lib/Interpreter.cpp
    llvm/tools/llubi/lib/Library.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
index e45d499d3205b..3445ced01331b 100644
--- a/llvm/test/tools/llubi/alloca.ll
+++ b/llvm/test/tools/llubi/alloca.ll
@@ -30,12 +30,12 @@ define void @main() {
 ; 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:   %alloc_dyn = alloca i32, i32 %count, align 4 => ptr 0x48 [alloc_dyn]
+; CHECK-NEXT:   %alloc_struct = alloca %struct, align 8 => ptr 0x60 [alloc_struct]
 ; CHECK-NEXT: Entering function: stack_address
-; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x70 [alloc]
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x7C [alloc]
 ; CHECK-NEXT:   ret ptr %alloc
 ; CHECK-NEXT: Exiting function: stack_address
-; CHECK-NEXT:   %ptr = call ptr @stack_address() => ptr 0x70 [alloc (dangling)]
+; CHECK-NEXT:   %ptr = call ptr @stack_address() => ptr 0x7C [alloc (dangling)]
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main

diff  --git a/llvm/test/tools/llubi/attributes.ll b/llvm/test/tools/llubi/attributes.ll
index cc8fcf62a46f9..87057ab816ba7 100644
--- a/llvm/test/tools/llubi/attributes.ll
+++ b/llvm/test/tools/llubi/attributes.ll
@@ -320,7 +320,7 @@ define void @main() {
 ; CHECK-NEXT:   ret ptr %p
 ; CHECK-NEXT: Exiting function: identity_dereferenceable_or_null
 ; CHECK-NEXT:   %deref_or_null_mixed = call dereferenceable(1) dereferenceable_or_null(1) ptr @identity_dereferenceable_or_null(ptr dereferenceable(1) dereferenceable_or_null(1) %alloc) => ptr 0x8 [alloc]
-; CHECK-NEXT:   %fmt_n_out = alloca [6 x i8], align 1 => ptr 0xC [fmt_n_out]
+; CHECK-NEXT:   %fmt_n_out = alloca [6 x i8], align 1 => ptr 0xD [fmt_n_out]
 ; CHECK-NEXT:   store [6 x i8] c"N=%d\0A\00", ptr %fmt_n_out, align 1
 ; CHECK-NEXT: N=6
 ; CHECK-NEXT:   %res = call noundef range(i32 0, 15) i32 (ptr, ...) @printf(ptr noundef nonnull %fmt_n_out, i32 noundef range(i32 0, 15) 6) => i32 4

diff  --git a/llvm/test/tools/llubi/gep.ll b/llvm/test/tools/llubi/gep.ll
index 3426249fa138c..8cfa63b105b4b 100644
--- a/llvm/test/tools/llubi/gep.ll
+++ b/llvm/test/tools/llubi/gep.ll
@@ -117,11 +117,11 @@ define void @main() {
 ; 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:   %alloc = alloca i32, align 4 => ptr 0x2C [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 [alloc (dangling)]
-; CHECK-NEXT:   %gep_inbounds_valid4 = getelementptr inbounds i8, ptr %dead_stack_ptr, i64 4 => ptr 0x2C [alloc + 4 (dangling)]
+; CHECK-NEXT:   %dead_stack_ptr = call ptr @dead_stack_object() => ptr 0x2C [alloc (dangling)]
+; CHECK-NEXT:   %gep_inbounds_valid4 = getelementptr inbounds i8, ptr %dead_stack_ptr, i64 4 => ptr 0x30 [alloc + 4 (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

diff  --git a/llvm/test/tools/llubi/inttoptr.ll b/llvm/test/tools/llubi/inttoptr.ll
index d29d26f69e2ad..53310b84af1e9 100644
--- a/llvm/test/tools/llubi/inttoptr.ll
+++ b/llvm/test/tools/llubi/inttoptr.ll
@@ -7,11 +7,19 @@ define void @main() {
   %ptr1 = inttoptr i64 0 to ptr
   %ptr2 = inttoptr i8 255 to ptr
   %ptr3 = inttoptr i128 -1 to ptr
+  %alloc = alloca i32
+  %off = ptrtoint ptr %alloc to i64
+  %gep = getelementptr i8, ptr %ptr1, i64 %off
+  %cast = inttoptr i64 %off to ptr
   ret void
 }
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %ptr1 = inttoptr i64 0 to ptr => ptr 0x0 [nullary]
 ; CHECK-NEXT:   %ptr2 = inttoptr i8 -1 to ptr => ptr 0xFF [nullary]
 ; CHECK-NEXT:   %ptr3 = inttoptr i128 -1 to ptr => ptr 0xFFFFFFFFFFFFFFFF [nullary]
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT:   %off = ptrtoint ptr %alloc to i64 => i64 8
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %ptr1, i64 %off => ptr 0x8 [nullary]
+; CHECK-NEXT:   %cast = inttoptr i64 %off to ptr => ptr 0x8 [wildcard]
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main

diff  --git a/llvm/test/tools/llubi/inttoptr_freed.ll b/llvm/test/tools/llubi/inttoptr_freed.ll
new file mode 100644
index 0000000000000..f56d4e1f4a950
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_freed.ll
@@ -0,0 +1,31 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+  %alloc = call ptr @malloc(i64 4)
+  %int = ptrtoint ptr %alloc to i64
+  %ptr = inttoptr i64 %int to ptr
+
+  store i32 0, ptr %ptr
+  call void @free(ptr %ptr)
+
+  ; We cannot access a freed object via wildcard provenance.
+  store i32 0, ptr %ptr
+
+  ret void
+}
+
+declare ptr @malloc(i64)
+declare void @free(ptr)
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = call ptr @malloc(i64 4) => ptr 0x10 [alloc]
+; CHECK-NEXT:   %int = ptrtoint ptr %alloc to i64 => i64 16
+; CHECK-NEXT:   %ptr = inttoptr i64 %int to ptr => ptr 0x10 [wildcard]
+; CHECK-NEXT:   store i32 0, ptr %ptr, align 4
+; CHECK-NEXT:   call void @free(ptr %ptr)
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   store i32 0, ptr %ptr, align 4 at @main <stdin>:15
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.

diff  --git a/llvm/test/tools/llubi/inttoptr_generation.ll b/llvm/test/tools/llubi/inttoptr_generation.ll
new file mode 100644
index 0000000000000..575a669f785fd
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_generation.ll
@@ -0,0 +1,34 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+  ; Assume the pointer address is 8.
+  %alloc = alloca i32
+  %ptr1 = inttoptr i64 8 to ptr
+  %int = ptrtoint ptr %alloc to i64
+  %ptr2 = inttoptr i64 %int to ptr
+  %ptr3 = inttoptr i64 8 to ptr
+
+  ; We can access the wildcard pointer through exposed provenances.
+  store i32 0, ptr %ptr2
+  ; We can access the wildcard pointer through exposed provenances, even if there are no dependencies.
+  store i32 0, ptr %ptr3
+  ; We cannot access the pointer created before any provenance is exposed.
+  store i32 0, ptr %ptr1
+
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT:   %ptr1 = inttoptr i64 8 to ptr => ptr 0x8 [nullary]
+; CHECK-NEXT:   %int = ptrtoint ptr %alloc to i64 => i64 8
+; CHECK-NEXT:   %ptr2 = inttoptr i64 %int to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT:   %ptr3 = inttoptr i64 8 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT:   store i32 0, ptr %ptr2, align 4
+; CHECK-NEXT:   store i32 0, ptr %ptr3, align 4
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   store i32 0, ptr %ptr1, align 4 at @main <stdin>:19
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.

diff  --git a/llvm/test/tools/llubi/inttoptr_generation2.ll b/llvm/test/tools/llubi/inttoptr_generation2.ll
new file mode 100644
index 0000000000000..e04595ad44cb4
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_generation2.ll
@@ -0,0 +1,39 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+  ; Assume the pointer address is 8.
+  %alloc = alloca i32
+  %alloc2 = alloca i32
+  ; We expose the provenance of another object so that the slow path is hitted.
+  %tmp = ptrtoint ptr %alloc2 to i64
+  %ptr1 = inttoptr i64 8 to ptr
+  %int = ptrtoint ptr %alloc to i64
+  %ptr2 = inttoptr i64 %int to ptr
+  %ptr3 = inttoptr i64 8 to ptr
+
+  ; We can access the wildcard pointer through exposed provenances.
+  store i32 0, ptr %ptr2
+  ; We can access the wildcard pointer through exposed provenances, even if there are no dependencies.
+  store i32 0, ptr %ptr3
+  ; We cannot access the pointer created before any provenance is exposed.
+  store i32 0, ptr %ptr1
+
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT:   %alloc2 = alloca i32, align 4 => ptr 0x10 [alloc2]
+; CHECK-NEXT:   %tmp = ptrtoint ptr %alloc2 to i64 => i64 16
+; CHECK-NEXT:   %ptr1 = inttoptr i64 8 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT:   %int = ptrtoint ptr %alloc to i64 => i64 8
+; CHECK-NEXT:   %ptr2 = inttoptr i64 %int to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT:   %ptr3 = inttoptr i64 8 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT:   store i32 0, ptr %ptr2, align 4
+; CHECK-NEXT:   store i32 0, ptr %ptr3, align 4
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   store i32 0, ptr %ptr1, align 4 at @main <stdin>:22
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.

diff  --git a/llvm/test/tools/llubi/inttoptr_gep.ll b/llvm/test/tools/llubi/inttoptr_gep.ll
new file mode 100644
index 0000000000000..5de7093a696af
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_gep.ll
@@ -0,0 +1,41 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+  %alloc1 = alloca i32
+  %alloc2 = alloca i32
+
+  %addr1 = ptrtoint ptr %alloc1 to i64
+  %addr2 = ptrtoint ptr %alloc2 to i64
+  %ptr
diff 1 = sub i64 %addr2, %addr1
+  %ptr
diff 2 = sub i64 %addr1, %addr2
+
+  %p = inttoptr i64 %addr1 to ptr
+  ; For out-of-bounds GEP with wildcard provenance, it returns poison.
+  %gep_oob = getelementptr inbounds i8, ptr %p, i64 %ptr
diff 1
+  %gep1 = getelementptr i8, ptr %p, i64 %ptr
diff 1
+  %gep2 = getelementptr inbounds i8, ptr %gep1, i64 2
+  %gep3 = getelementptr i8, ptr %gep2, i64 %ptr
diff 2
+
+  ; We cannot access %alloc1 because %gep2 has been resolved to %alloc2.
+  store i16 0, ptr %gep3
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc1 = alloca i32, align 4 => ptr 0x8 [alloc1]
+; CHECK-NEXT:   %alloc2 = alloca i32, align 4 => ptr 0x10 [alloc2]
+; CHECK-NEXT:   %addr1 = ptrtoint ptr %alloc1 to i64 => i64 8
+; CHECK-NEXT:   %addr2 = ptrtoint ptr %alloc2 to i64 => i64 16
+; CHECK-NEXT:   %ptr
diff 1 = sub i64 %addr2, %addr1 => i64 8
+; CHECK-NEXT:   %ptr
diff 2 = sub i64 %addr1, %addr2 => i64 -8
+; CHECK-NEXT:   %p = inttoptr i64 %addr1 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT:   %gep_oob = getelementptr inbounds i8, ptr %p, i64 %ptr
diff 1 => poison
+; CHECK-NEXT:   %gep1 = getelementptr i8, ptr %p, i64 %ptr
diff 1 => ptr 0x10 [wildcard]
+; CHECK-NEXT:   %gep2 = getelementptr inbounds i8, ptr %gep1, i64 2 => ptr 0x12 [alloc2 + 2]
+; CHECK-NEXT:   %gep3 = getelementptr i8, ptr %gep2, i64 %ptr
diff 2 => ptr 0xA [alloc2 + -6]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   store i16 0, ptr %gep3, align 2 at @main <stdin>:23
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.

diff  --git a/llvm/test/tools/llubi/inttoptr_multiobj.ll b/llvm/test/tools/llubi/inttoptr_multiobj.ll
new file mode 100644
index 0000000000000..aaf305b3851c8
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_multiobj.ll
@@ -0,0 +1,40 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+  %alloc1 = alloca i32
+  %alloc2 = alloca i32
+
+  %addr1 = ptrtoint ptr %alloc1 to i64
+  %addr2 = ptrtoint ptr %alloc2 to i64
+  %ptr
diff  = sub i64 %addr2, %addr1
+
+  %p = inttoptr i64 %addr1 to ptr
+
+  %gep1 = getelementptr i8, ptr %p, i64 %ptr
diff 
+  %gep2 = getelementptr inbounds i8, ptr %gep1, i64 2
+
+  ; We can access %alloc1, although %gep2 has been resolved to %alloc2.
+  store i32 0, ptr %p
+
+  ; Now we cannot access %alloc2 through %gep2, since the wildcard provenance has been resolved to %alloc1.
+  store i16 0, ptr %gep2
+
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc1 = alloca i32, align 4 => ptr 0x8 [alloc1]
+; CHECK-NEXT:   %alloc2 = alloca i32, align 4 => ptr 0x10 [alloc2]
+; CHECK-NEXT:   %addr1 = ptrtoint ptr %alloc1 to i64 => i64 8
+; CHECK-NEXT:   %addr2 = ptrtoint ptr %alloc2 to i64 => i64 16
+; CHECK-NEXT:   %ptr
diff  = sub i64 %addr2, %addr1 => i64 8
+; CHECK-NEXT:   %p = inttoptr i64 %addr1 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT:   %gep1 = getelementptr i8, ptr %p, i64 %ptr
diff  => ptr 0x10 [wildcard]
+; CHECK-NEXT:   %gep2 = getelementptr inbounds i8, ptr %gep1, i64 2 => ptr 0x12 [alloc2 + 2]
+; CHECK-NEXT:   store i32 0, ptr %p, align 4
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   store i16 0, ptr %gep2, align 2 at @main <stdin>:23
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.

diff  --git a/llvm/test/tools/llubi/inttoptr_multiobj2.ll b/llvm/test/tools/llubi/inttoptr_multiobj2.ll
new file mode 100644
index 0000000000000..f44c07722fac0
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_multiobj2.ll
@@ -0,0 +1,41 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+  %alloc1 = alloca i32
+  %alloc2 = alloca i32
+
+  %addr1 = ptrtoint ptr %alloc1 to i64
+  %addr2 = ptrtoint ptr %alloc2 to i64
+  %ptr
diff  = sub i64 %addr2, %addr1
+
+  %p = inttoptr i64 %addr1 to ptr
+  %gep = getelementptr i8, ptr %p, i64 %ptr
diff 
+
+  ; After %alloc1 is accessed, we cannot access other memory objects later.
+  store i32 0, ptr %p
+
+  ; %gep2 is poison as the wildcard provenance has been resolved to %alloc1.
+  %gep2 = getelementptr inbounds i8, ptr %p, i64 %ptr
diff 
+
+  ; Now we cannot access %alloc2 via the pointer derived from %p.
+  store i32 0, ptr %gep
+
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc1 = alloca i32, align 4 => ptr 0x8 [alloc1]
+; CHECK-NEXT:   %alloc2 = alloca i32, align 4 => ptr 0x10 [alloc2]
+; CHECK-NEXT:   %addr1 = ptrtoint ptr %alloc1 to i64 => i64 8
+; CHECK-NEXT:   %addr2 = ptrtoint ptr %alloc2 to i64 => i64 16
+; CHECK-NEXT:   %ptr
diff  = sub i64 %addr2, %addr1 => i64 8
+; CHECK-NEXT:   %p = inttoptr i64 %addr1 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT:   %gep = getelementptr i8, ptr %p, i64 %ptr
diff  => ptr 0x10 [wildcard]
+; CHECK-NEXT:   store i32 0, ptr %p, align 4
+; CHECK-NEXT:   %gep2 = getelementptr inbounds i8, ptr %p, i64 %ptr
diff  => poison
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   store i32 0, ptr %gep, align 4 at @main <stdin>:24
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.

diff  --git a/llvm/test/tools/llubi/inttoptr_oob.ll b/llvm/test/tools/llubi/inttoptr_oob.ll
new file mode 100644
index 0000000000000..baf64792d6cf4
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_oob.ll
@@ -0,0 +1,30 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+  %alloc = alloca i32
+  %int = ptrtoint ptr %alloc to i64
+  %ptr = inttoptr i64 %int to ptr
+
+  %ptr_oob = getelementptr i8, ptr %ptr, i64 5
+  %ptr_inbounds_resolved = getelementptr inbounds i8, ptr %ptr, i64 4
+  %ptr_inbounds_oob = getelementptr inbounds i8, ptr %ptr, i64 5
+
+  ; We cannot resolve to any exposed provenance.
+  store i64 0, ptr %ptr_oob
+
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT:   %int = ptrtoint ptr %alloc to i64 => i64 8
+; CHECK-NEXT:   %ptr = inttoptr i64 %int to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT:   %ptr_oob = getelementptr i8, ptr %ptr, i64 5 => ptr 0xD [wildcard]
+; CHECK-NEXT:   %ptr_inbounds_resolved = getelementptr inbounds i8, ptr %ptr, i64 4 => ptr 0xC [alloc + 4]
+; CHECK-NEXT:   %ptr_inbounds_oob = getelementptr inbounds i8, ptr %ptr, i64 5 => poison
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   store i64 0, ptr %ptr_oob, align 4 at @main <stdin>:16
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.

diff  --git a/llvm/test/tools/llubi/inttoptr_oob2.ll b/llvm/test/tools/llubi/inttoptr_oob2.ll
new file mode 100644
index 0000000000000..a2e85b1fa697b
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_oob2.ll
@@ -0,0 +1,33 @@
+; 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
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+  %alloc = alloca i32
+  ; Another memory object with high address.
+  %alloc2 = alloca i32, align 256
+  %int = ptrtoint ptr %alloc to i64
+  %ptr = inttoptr i64 %int to ptr
+
+  %ptr_oob = getelementptr i8, ptr %ptr, i64 5
+  %ptr_inbounds_resolved = getelementptr inbounds i8, ptr %ptr, i64 4
+  %ptr_inbounds_oob = getelementptr inbounds i8, ptr %ptr, i64 5
+
+  ; We cannot resolve to any exposed provenance.
+  store i64 0, ptr %ptr_oob
+
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT:   %alloc2 = alloca i32, align 256 => ptr 0x100 [alloc2]
+; CHECK-NEXT:   %int = ptrtoint ptr %alloc to i64 => i64 8
+; CHECK-NEXT:   %ptr = inttoptr i64 %int to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT:   %ptr_oob = getelementptr i8, ptr %ptr, i64 5 => ptr 0xD [wildcard]
+; CHECK-NEXT:   %ptr_inbounds_resolved = getelementptr inbounds i8, ptr %ptr, i64 4 => ptr 0xC [alloc + 4]
+; CHECK-NEXT:   %ptr_inbounds_oob = getelementptr inbounds i8, ptr %ptr, i64 5 => poison
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   store i64 0, ptr %ptr_oob, align 4 at @main <stdin>:18
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.

diff  --git a/llvm/test/tools/llubi/lib_abort.ll b/llvm/test/tools/llubi/lib_abort.ll
index 78ed03c6c70ca..f1d3b829d8076 100644
--- a/llvm/test/tools/llubi/lib_abort.ll
+++ b/llvm/test/tools/llubi/lib_abort.ll
@@ -23,7 +23,7 @@ entry:
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %before = alloca [7 x i8], align 1 => ptr 0x8 [before]
 ; CHECK-NEXT:   store [7 x i8] c"Before\00", ptr %before, align 1
-; CHECK-NEXT:   %after = alloca [6 x i8], align 1 => ptr 0xF [after]
+; CHECK-NEXT:   %after = alloca [6 x i8], align 1 => ptr 0x10 [after]
 ; CHECK-NEXT:   store [6 x i8] c"After\00", ptr %after, align 1
 ; CHECK-NEXT: Before
 ; CHECK-NEXT:   %0 = call i32 @puts(ptr %before) => i32 1

diff  --git a/llvm/test/tools/llubi/lib_exit.ll b/llvm/test/tools/llubi/lib_exit.ll
index 7239352f18182..f5b0e923fcf44 100644
--- a/llvm/test/tools/llubi/lib_exit.ll
+++ b/llvm/test/tools/llubi/lib_exit.ll
@@ -23,7 +23,7 @@ entry:
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %before = alloca [7 x i8], align 1 => ptr 0x8 [before]
 ; CHECK-NEXT:   store [7 x i8] c"Before\00", ptr %before, align 1
-; CHECK-NEXT:   %after = alloca [6 x i8], align 1 => ptr 0xF [after]
+; CHECK-NEXT:   %after = alloca [6 x i8], align 1 => ptr 0x10 [after]
 ; CHECK-NEXT:   store [6 x i8] c"After\00", ptr %after, align 1
 ; CHECK-NEXT: Before
 ; CHECK-NEXT:   %0 = call i32 @puts(ptr %before) => i32 1

diff  --git a/llvm/test/tools/llubi/lib_io.ll b/llvm/test/tools/llubi/lib_io.ll
index 157d11942a64c..d6f71c61d73d5 100644
--- a/llvm/test/tools/llubi/lib_io.ll
+++ b/llvm/test/tools/llubi/lib_io.ll
@@ -26,9 +26,9 @@ entry:
 ; CHECK-NEXT:   store [13 x i8] c"Hello, puts!\00", ptr %puts.str, align 1
 ; CHECK-NEXT: Hello, puts!
 ; CHECK-NEXT:   %0 = call i32 @puts(ptr %puts.str) => i32 1
-; CHECK-NEXT:   %fmt.str = alloca [18 x i8], align 1 => ptr 0x15 [fmt.str]
+; CHECK-NEXT:   %fmt.str = alloca [18 x i8], align 1 => ptr 0x16 [fmt.str]
 ; CHECK-NEXT:   store [18 x i8] c"Int: %d, Str: %s\0A\00", ptr %fmt.str, align 1
-; CHECK-NEXT:   %arg.str = alloca [5 x i8], align 1 => ptr 0x27 [arg.str]
+; CHECK-NEXT:   %arg.str = alloca [5 x i8], align 1 => ptr 0x29 [arg.str]
 ; CHECK-NEXT:   store [5 x i8] c"test\00", ptr %arg.str, align 1
 ; CHECK-NEXT: Int: 42, Str: test
 ; CHECK-NEXT:   %1 = call i32 (ptr, ...) @printf(ptr %fmt.str, i32 42, ptr %arg.str) => i32 19

diff  --git a/llvm/test/tools/llubi/lib_printf_format.ll b/llvm/test/tools/llubi/lib_printf_format.ll
index e4d5dea1b6a27..b14b0f618f4a0 100644
--- a/llvm/test/tools/llubi/lib_printf_format.ll
+++ b/llvm/test/tools/llubi/lib_printf_format.ll
@@ -46,27 +46,27 @@ entry:
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %fmt_int = alloca [36 x i8], align 1 => ptr 0x8 [fmt_int]
 ; CHECK-NEXT:   store [36 x i8] c"Ints: %d, %i, %u, %o, %x, %X, %05d\0A\00", ptr %fmt_int, align 1
-; CHECK-NEXT:   %fmt_len = alloca [35 x i8], align 1 => ptr 0x2C [fmt_len]
+; CHECK-NEXT:   %fmt_len = alloca [35 x i8], align 1 => ptr 0x2D [fmt_len]
 ; CHECK-NEXT:   store [35 x i8] c"Lengths: %ld, %lld, %hd, %hhu, %c\0A\00", ptr %fmt_len, align 1
-; CHECK-NEXT:   %fmt_str_ptr = alloca [18 x i8], align 1 => ptr 0x4F [fmt_str_ptr]
+; CHECK-NEXT:   %fmt_str_ptr = alloca [18 x i8], align 1 => ptr 0x51 [fmt_str_ptr]
 ; CHECK-NEXT:   store [18 x i8] c"Str: %s, Ptr: %p\0A\00", ptr %fmt_str_ptr, align 1
-; CHECK-NEXT:   %fmt_pct = alloca [15 x i8], align 1 => ptr 0x61 [fmt_pct]
+; CHECK-NEXT:   %fmt_pct = alloca [15 x i8], align 1 => ptr 0x64 [fmt_pct]
 ; CHECK-NEXT:   store [15 x i8] c"Percent: %d%%\0A\00", ptr %fmt_pct, align 1
-; CHECK-NEXT:   %dummy_str = alloca [6 x i8], align 1 => ptr 0x70 [dummy_str]
+; CHECK-NEXT:   %dummy_str = alloca [6 x i8], align 1 => ptr 0x74 [dummy_str]
 ; CHECK-NEXT:   store [6 x i8] c"llubi\00", ptr %dummy_str, align 1
-; CHECK-NEXT:   %fmt_float = alloca [20 x i8], align 1 => ptr 0x76 [fmt_float]
+; CHECK-NEXT:   %fmt_float = alloca [20 x i8], align 1 => ptr 0x7B [fmt_float]
 ; CHECK-NEXT:   store [20 x i8] c"Floats: %f, %e, %g\0A\00", ptr %fmt_float, align 1
-; CHECK-NEXT:   %fmt_n = alloca [15 x i8], align 1 => ptr 0x8A [fmt_n]
+; CHECK-NEXT:   %fmt_n = alloca [15 x i8], align 1 => ptr 0x90 [fmt_n]
 ; CHECK-NEXT:   store [15 x i8] c"Count: %nDone\0A\00", ptr %fmt_n, align 1
-; CHECK-NEXT:   %fmt_n_out = alloca [6 x i8], align 1 => ptr 0x99 [fmt_n_out]
+; CHECK-NEXT:   %fmt_n_out = alloca [6 x i8], align 1 => ptr 0xA0 [fmt_n_out]
 ; CHECK-NEXT:   store [6 x i8] c"N=%d\0A\00", ptr %fmt_n_out, align 1
-; CHECK-NEXT:   %n_count = alloca i32, align 4 => ptr 0xA0 [n_count]
+; CHECK-NEXT:   %n_count = alloca i32, align 4 => ptr 0xA8 [n_count]
 ; CHECK-NEXT:   store i32 0, ptr %n_count, align 4
 ; CHECK-NEXT: Ints: 42, -42, 255, 377, ff, FF, 00042
 ; CHECK-NEXT:   %0 = call i32 (ptr, ...) @printf(ptr %fmt_int, i32 42, i32 -42, i32 255, i32 255, i32 255, i32 255, i32 42) => i32 39
 ; CHECK-NEXT: Lengths: 123456789, 987654321, 100, 50, A
 ; CHECK-NEXT:   %1 = call i32 (ptr, ...) @printf(ptr %fmt_len, i64 123456789, i64 987654321, i32 100, i32 50, i32 65) => i32 42
-; CHECK-NEXT: Str: llubi, Ptr: 0x70
+; CHECK-NEXT: Str: llubi, Ptr: 0x74
 ; CHECK-NEXT:   %2 = call i32 (ptr, ...) @printf(ptr %fmt_str_ptr, ptr %dummy_str, ptr %dummy_str) => i32 22
 ; CHECK-NEXT: Percent: 100%
 ; CHECK-NEXT:   %3 = call i32 (ptr, ...) @printf(ptr %fmt_pct, i32 100) => i32 14

diff  --git a/llvm/test/tools/llubi/lib_terminate.ll b/llvm/test/tools/llubi/lib_terminate.ll
index 8026e9aa24497..8849d356d34ae 100644
--- a/llvm/test/tools/llubi/lib_terminate.ll
+++ b/llvm/test/tools/llubi/lib_terminate.ll
@@ -23,7 +23,7 @@ entry:
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %before = alloca [7 x i8], align 1 => ptr 0x8 [before]
 ; CHECK-NEXT:   store [7 x i8] c"Before\00", ptr %before, align 1
-; CHECK-NEXT:   %after = alloca [6 x i8], align 1 => ptr 0xF [after]
+; CHECK-NEXT:   %after = alloca [6 x i8], align 1 => ptr 0x10 [after]
 ; CHECK-NEXT:   store [6 x i8] c"After\00", ptr %after, align 1
 ; CHECK-NEXT: Before
 ; CHECK-NEXT:   %0 = call i32 @puts(ptr %before) => i32 1

diff  --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index 8f2e0815f88fe..8f1f851ff13c2 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -146,7 +146,7 @@ define void @main() {
 ; CHECK-NEXT:   %val11 = load i25, ptr %alloc, align 4 => poison
 ; CHECK-NEXT:   call void @llvm.lifetime.start.p0(ptr poison)
 ; CHECK-NEXT:   call void @llvm.lifetime.end.p0(ptr poison)
-; CHECK-NEXT:   %alloc_lifetime = alloca i32, align 4 => ptr 0xC [alloc_lifetime (dead)]
+; CHECK-NEXT:   %alloc_lifetime = alloca i32, align 4 => ptr 0x10 [alloc_lifetime (dead)]
 ; CHECK-NEXT:   %val12 = load i32, ptr %alloc_lifetime, align 4 => poison
 ; CHECK-NEXT:   call void @llvm.lifetime.start.p0(ptr %alloc_lifetime)
 ; CHECK-NEXT:   %val13 = load i32, ptr %alloc_lifetime, align 4 => i32 -289830082
@@ -161,52 +161,52 @@ define void @main() {
 ; CHECK-NEXT:   %val16 = load i32, ptr %alloc_lifetime, align 4 => poison
 ; CHECK-NEXT:   store i32 -524288, ptr %alloc, align 4
 ; CHECK-NEXT:   %val17 = load float, ptr %alloc, align 4 => float 0xFFF80000
-; CHECK-NEXT:   %alloc_vscale = alloca <vscale x 2 x i32>, align 8 => ptr 0x10 [alloc_vscale]
+; CHECK-NEXT:   %alloc_vscale = alloca <vscale x 2 x i32>, align 8 => ptr 0x18 [alloc_vscale]
 ; CHECK-NEXT:   %insert = insertelement <vscale x 1 x i32> poison, i32 1, i32 0 => { i32 1, poison, poison, poison }
 ; CHECK-NEXT:   %ones = shufflevector <vscale x 1 x i32> %insert, <vscale x 1 x i32> poison, <vscale x 1 x i32> zeroinitializer => { i32 1, i32 1, i32 1, i32 1 }
 ; CHECK-NEXT:   %twos = add <vscale x 1 x i32> %ones, %ones => { i32 2, i32 2, i32 2, i32 2 }
 ; CHECK-NEXT:   store <vscale x 1 x i32> %ones, ptr %alloc_vscale, align 4
-; CHECK-NEXT:   %gep3 = getelementptr <vscale x 1 x i32>, ptr %alloc_vscale, i64 1 => ptr 0x20 [alloc_vscale + 16]
+; CHECK-NEXT:   %gep3 = getelementptr <vscale x 1 x i32>, ptr %alloc_vscale, i64 1 => ptr 0x28 [alloc_vscale + 16]
 ; CHECK-NEXT:   store <vscale x 1 x i32> %twos, ptr %gep3, align 4
 ; CHECK-NEXT:   %val18 = load <vscale x 2 x i32>, ptr %alloc_vscale, align 8 => { i32 1, i32 1, i32 1, i32 1, i32 2, i32 2, i32 2, i32 2 }
-; CHECK-NEXT:   %alloc_struct = alloca %struct, align 8 => ptr 0x30 [alloc_struct]
+; CHECK-NEXT:   %alloc_struct = alloca %struct, align 8 => ptr 0x40 [alloc_struct]
 ; CHECK-NEXT:   store %struct { [2 x i16] [i16 1, i16 2], i64 3 }, ptr %alloc_struct, align 8
 ; CHECK-NEXT:   %val19 = load %struct, ptr %alloc_struct, align 8 => { { i16 1, i16 2 }, i64 3 }
 ; CHECK-NEXT:   %val20 = load i64, ptr %alloc_struct, align 8 => i64 281486191577587
 ; CHECK-NEXT:   %val21 = load i64, ptr %alloc_struct, align 8 => i64 281486962127119
-; CHECK-NEXT:   %alloc_struct_packed = alloca %struct.packed, align 8 => ptr 0x40 [alloc_struct_packed]
+; CHECK-NEXT:   %alloc_struct_packed = alloca %struct.packed, align 8 => ptr 0x58 [alloc_struct_packed]
 ; CHECK-NEXT:   store %struct.packed <{ [2 x i16] [i16 1, i16 2], i64 3 }>, ptr %alloc_struct_packed, align 1
 ; CHECK-NEXT:   %val22 = load %struct.packed, ptr %alloc_struct_packed, align 1 => { { i16 1, i16 2 }, i64 3 }
 ; CHECK-NEXT:   %val23 = load i64, ptr %alloc_struct_packed, align 8 => i64 281483566645248
 ; CHECK-NEXT:   %val24 = load i64, ptr %alloc_struct_packed, align 8 => i64 281483566645248
-; CHECK-NEXT:   %alloc_struct_vscale = alloca %struct.vscale, align 8 => ptr 0x50 [alloc_struct_vscale]
+; CHECK-NEXT:   %alloc_struct_vscale = alloca %struct.vscale, align 8 => ptr 0x68 [alloc_struct_vscale]
 ; CHECK-NEXT:   store %struct.vscale zeroinitializer, ptr %alloc_struct_vscale, align 4
-; CHECK-NEXT:   %gep4 = getelementptr <vscale x 1 x i32>, ptr %alloc_struct_vscale, i32 1 => ptr 0x60 [alloc_struct_vscale + 16]
+; CHECK-NEXT:   %gep4 = getelementptr <vscale x 1 x i32>, ptr %alloc_struct_vscale, i32 1 => ptr 0x78 [alloc_struct_vscale + 16]
 ; CHECK-NEXT:   store <vscale x 1 x i32> %ones, ptr %gep4, align 4
 ; CHECK-NEXT:   %val25 = load %struct.vscale, ptr %alloc_struct_vscale, align 4 => { { i32 0, i32 0, i32 0, i32 0 }, { i32 1, i32 1, i32 1, i32 1 } }
-; CHECK-NEXT:   %alloc_array = alloca [2 x i32], align 4 => ptr 0x70 [alloc_array]
+; CHECK-NEXT:   %alloc_array = alloca [2 x i32], align 4 => ptr 0x8C [alloc_array]
 ; CHECK-NEXT:   store [2 x i32] [i32 1, i32 2], ptr %alloc_array, align 4
 ; CHECK-NEXT:   %val26 = load [2 x i32], ptr %alloc_array, align 4 => { i32 1, i32 2 }
-; CHECK-NEXT:   %alloc_i1_vec = alloca <4 x i1>, align 1 => ptr 0x78 [alloc_i1_vec]
+; CHECK-NEXT:   %alloc_i1_vec = alloca <4 x i1>, align 1 => ptr 0x95 [alloc_i1_vec]
 ; CHECK-NEXT:   store <4 x i1> <i1 true, i1 false, i1 poison, i1 false>, ptr %alloc_i1_vec, align 1
 ; CHECK-NEXT:   %val27 = load <4 x i1>, ptr %alloc_i1_vec, align 1 => { T, F, poison, F }
 ; CHECK-NEXT:   %val28 = load i8, ptr %alloc_i1_vec, align 1 => poison
-; CHECK-NEXT:   %alloc_padding = alloca i31, align 4 => ptr 0x7C [alloc_padding]
+; CHECK-NEXT:   %alloc_padding = alloca i31, align 4 => ptr 0x98 [alloc_padding]
 ; CHECK-NEXT:   store i32 0, ptr %alloc_padding, align 4
-; CHECK-NEXT:   %alloc_padding_vec = alloca i64, align 8 => ptr 0x80 [alloc_padding_vec]
+; CHECK-NEXT:   %alloc_padding_vec = alloca i64, align 8 => ptr 0xA0 [alloc_padding_vec]
 ; CHECK-NEXT:   store { <6 x i5>, i32 } { <6 x i5> zeroinitializer, i32 -1 }, ptr %alloc_padding_vec, align 4
 ; CHECK-NEXT:   %load_agg = load { <6 x i5>, i32 }, ptr %alloc_padding_vec, align 4 => { { i5 0, i5 0, i5 0, i5 0, i5 0, i5 0 }, i32 -1 }
 ; CHECK-NEXT:   %load_vec = load <6 x i5>, ptr %alloc_padding_vec, align 4 => { i5 0, i5 0, i5 0, i5 0, i5 0, i5 0 }
 ; CHECK-NEXT:   %load_int_non_zero_padding = load i33, ptr %alloc_padding_vec, align 8 => i33 255
 ; CHECK-NEXT:   %load_vec_non_zero_padding = load <3 x i11>, ptr %alloc_padding_vec, align 8 => { i11 0, i11 0, i11 255 }
-; CHECK-NEXT:   %alloc_struct_padding = alloca { i8, i32 }, align 8 => ptr 0x88 [alloc_struct_padding]
+; CHECK-NEXT:   %alloc_struct_padding = alloca { i8, i32 }, align 8 => ptr 0xB0 [alloc_struct_padding]
 ; CHECK-NEXT:   store { i8, i32 } zeroinitializer, ptr %alloc_struct_padding, align 4
 ; CHECK-NEXT:   %load_struct_noundef = load { i8, i32 }, ptr %alloc_struct_padding, align 4, !noundef !0 => { i8 0, i32 0 }
-; CHECK-NEXT:   %alloc_ptr = alloca ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT:   %alloc_ptr = alloca ptr, align 8 => ptr 0xC0 [alloc_ptr]
 ; CHECK-NEXT:   store ptr %alloc_ptr, ptr %alloc_ptr, align 8
-; CHECK-NEXT:   %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT:   %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0xC0 [alloc_ptr]
 ; CHECK-NEXT:   %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 0
 ; CHECK-NEXT:   store i8 %addr_bits, ptr %alloc_ptr, align 1
-; CHECK-NEXT:   %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [nullary]
+; CHECK-NEXT:   %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0xC0 [nullary]
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main

diff  --git a/llvm/test/tools/llubi/loadstore_le.ll b/llvm/test/tools/llubi/loadstore_le.ll
index 3caf02c3e6eaa..9ad92ac6e75e0 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -148,7 +148,7 @@ define void @main() {
 ; CHECK-NEXT:   %val11 = load i25, ptr %alloc, align 4 => poison
 ; CHECK-NEXT:   call void @llvm.lifetime.start.p0(ptr poison)
 ; CHECK-NEXT:   call void @llvm.lifetime.end.p0(ptr poison)
-; CHECK-NEXT:   %alloc_lifetime = alloca i32, align 4 => ptr 0xC [alloc_lifetime (dead)]
+; CHECK-NEXT:   %alloc_lifetime = alloca i32, align 4 => ptr 0x10 [alloc_lifetime (dead)]
 ; CHECK-NEXT:   %val12 = load i32, ptr %alloc_lifetime, align 4 => poison
 ; CHECK-NEXT:   call void @llvm.lifetime.start.p0(ptr %alloc_lifetime)
 ; CHECK-NEXT:   %val13 = load i32, ptr %alloc_lifetime, align 4 => i32 -289830082
@@ -163,52 +163,52 @@ define void @main() {
 ; CHECK-NEXT:   %val16 = load i32, ptr %alloc_lifetime, align 4 => poison
 ; CHECK-NEXT:   store i32 -524288, ptr %alloc, align 4
 ; CHECK-NEXT:   %val17 = load float, ptr %alloc, align 4 => float 0xFFF80000
-; CHECK-NEXT:   %alloc_vscale = alloca <vscale x 2 x i32>, align 8 => ptr 0x10 [alloc_vscale]
+; CHECK-NEXT:   %alloc_vscale = alloca <vscale x 2 x i32>, align 8 => ptr 0x18 [alloc_vscale]
 ; CHECK-NEXT:   %insert = insertelement <vscale x 1 x i32> poison, i32 1, i32 0 => { i32 1, poison, poison, poison }
 ; CHECK-NEXT:   %ones = shufflevector <vscale x 1 x i32> %insert, <vscale x 1 x i32> poison, <vscale x 1 x i32> zeroinitializer => { i32 1, i32 1, i32 1, i32 1 }
 ; CHECK-NEXT:   %twos = add <vscale x 1 x i32> %ones, %ones => { i32 2, i32 2, i32 2, i32 2 }
 ; CHECK-NEXT:   store <vscale x 1 x i32> %ones, ptr %alloc_vscale, align 4
-; CHECK-NEXT:   %gep3 = getelementptr <vscale x 1 x i32>, ptr %alloc_vscale, i64 1 => ptr 0x20 [alloc_vscale + 16]
+; CHECK-NEXT:   %gep3 = getelementptr <vscale x 1 x i32>, ptr %alloc_vscale, i64 1 => ptr 0x28 [alloc_vscale + 16]
 ; CHECK-NEXT:   store <vscale x 1 x i32> %twos, ptr %gep3, align 4
 ; CHECK-NEXT:   %val18 = load <vscale x 2 x i32>, ptr %alloc_vscale, align 8 => { i32 1, i32 1, i32 1, i32 1, i32 2, i32 2, i32 2, i32 2 }
-; CHECK-NEXT:   %alloc_struct = alloca %struct, align 8 => ptr 0x30 [alloc_struct]
+; CHECK-NEXT:   %alloc_struct = alloca %struct, align 8 => ptr 0x40 [alloc_struct]
 ; CHECK-NEXT:   store %struct { [2 x i16] [i16 1, i16 2], i64 3 }, ptr %alloc_struct, align 8
 ; CHECK-NEXT:   %val19 = load %struct, ptr %alloc_struct, align 8 => { { i16 1, i16 2 }, i64 3 }
 ; CHECK-NEXT:   %val20 = load i64, ptr %alloc_struct, align 8 => i64 -7172745523491635199
 ; CHECK-NEXT:   %val21 = load i64, ptr %alloc_struct, align 8 => i64 -3863260483603529727
-; CHECK-NEXT:   %alloc_struct_packed = alloca %struct.packed, align 8 => ptr 0x40 [alloc_struct_packed]
+; CHECK-NEXT:   %alloc_struct_packed = alloca %struct.packed, align 8 => ptr 0x58 [alloc_struct_packed]
 ; CHECK-NEXT:   store %struct.packed <{ [2 x i16] [i16 1, i16 2], i64 3 }>, ptr %alloc_struct_packed, align 1
 ; CHECK-NEXT:   %val22 = load %struct.packed, ptr %alloc_struct_packed, align 1 => { { i16 1, i16 2 }, i64 3 }
 ; CHECK-NEXT:   %val23 = load i64, ptr %alloc_struct_packed, align 8 => i64 12885032961
 ; CHECK-NEXT:   %val24 = load i64, ptr %alloc_struct_packed, align 8 => i64 12885032961
-; CHECK-NEXT:   %alloc_struct_vscale = alloca %struct.vscale, align 8 => ptr 0x50 [alloc_struct_vscale]
+; CHECK-NEXT:   %alloc_struct_vscale = alloca %struct.vscale, align 8 => ptr 0x68 [alloc_struct_vscale]
 ; CHECK-NEXT:   store %struct.vscale zeroinitializer, ptr %alloc_struct_vscale, align 4
-; CHECK-NEXT:   %gep4 = getelementptr <vscale x 1 x i32>, ptr %alloc_struct_vscale, i32 1 => ptr 0x60 [alloc_struct_vscale + 16]
+; CHECK-NEXT:   %gep4 = getelementptr <vscale x 1 x i32>, ptr %alloc_struct_vscale, i32 1 => ptr 0x78 [alloc_struct_vscale + 16]
 ; CHECK-NEXT:   store <vscale x 1 x i32> %ones, ptr %gep4, align 4
 ; CHECK-NEXT:   %val25 = load %struct.vscale, ptr %alloc_struct_vscale, align 4 => { { i32 0, i32 0, i32 0, i32 0 }, { i32 1, i32 1, i32 1, i32 1 } }
-; CHECK-NEXT:   %alloc_array = alloca [2 x i32], align 4 => ptr 0x70 [alloc_array]
+; CHECK-NEXT:   %alloc_array = alloca [2 x i32], align 4 => ptr 0x8C [alloc_array]
 ; CHECK-NEXT:   store [2 x i32] [i32 1, i32 2], ptr %alloc_array, align 4
 ; CHECK-NEXT:   %val26 = load [2 x i32], ptr %alloc_array, align 4 => { i32 1, i32 2 }
-; CHECK-NEXT:   %alloc_i1_vec = alloca <4 x i1>, align 1 => ptr 0x78 [alloc_i1_vec]
+; CHECK-NEXT:   %alloc_i1_vec = alloca <4 x i1>, align 1 => ptr 0x95 [alloc_i1_vec]
 ; CHECK-NEXT:   store <4 x i1> <i1 true, i1 false, i1 poison, i1 false>, ptr %alloc_i1_vec, align 1
 ; CHECK-NEXT:   %val27 = load <4 x i1>, ptr %alloc_i1_vec, align 1 => { T, F, poison, F }
 ; CHECK-NEXT:   %val28 = load i8, ptr %alloc_i1_vec, align 1 => poison
-; CHECK-NEXT:   %alloc_padding = alloca i31, align 4 => ptr 0x7C [alloc_padding]
+; CHECK-NEXT:   %alloc_padding = alloca i31, align 4 => ptr 0x98 [alloc_padding]
 ; CHECK-NEXT:   store i32 0, ptr %alloc_padding, align 4
-; CHECK-NEXT:   %alloc_padding_vec = alloca i64, align 8 => ptr 0x80 [alloc_padding_vec]
+; CHECK-NEXT:   %alloc_padding_vec = alloca i64, align 8 => ptr 0xA0 [alloc_padding_vec]
 ; CHECK-NEXT:   store { <6 x i5>, i32 } { <6 x i5> zeroinitializer, i32 -1 }, ptr %alloc_padding_vec, align 4
 ; CHECK-NEXT:   %load_agg = load { <6 x i5>, i32 }, ptr %alloc_padding_vec, align 4 => { { i5 0, i5 0, i5 0, i5 0, i5 0, i5 0 }, i32 -1 }
 ; CHECK-NEXT:   %load_vec = load <6 x i5>, ptr %alloc_padding_vec, align 4 => { i5 0, i5 0, i5 0, i5 0, i5 0, i5 0 }
 ; CHECK-NEXT:   %load_int_non_zero_padding = load i33, ptr %alloc_padding_vec, align 8 => poison
 ; CHECK-NEXT:   %load_vec_non_zero_padding = load <3 x i11>, ptr %alloc_padding_vec, align 8 => { poison, poison, poison }
-; CHECK-NEXT:   %alloc_struct_padding = alloca { i8, i32 }, align 8 => ptr 0x88 [alloc_struct_padding]
+; CHECK-NEXT:   %alloc_struct_padding = alloca { i8, i32 }, align 8 => ptr 0xB0 [alloc_struct_padding]
 ; CHECK-NEXT:   store { i8, i32 } zeroinitializer, ptr %alloc_struct_padding, align 4
 ; CHECK-NEXT:   %load_struct_noundef = load { i8, i32 }, ptr %alloc_struct_padding, align 4, !noundef !0 => { i8 0, i32 0 }
-; CHECK-NEXT:   %alloc_ptr = alloca ptr, align 8 => ptr 0x90 [alloc_ptr]
+; CHECK-NEXT:   %alloc_ptr = alloca ptr, align 8 => ptr 0xC0 [alloc_ptr]
 ; CHECK-NEXT:   store ptr %alloc_ptr, ptr %alloc_ptr, align 8
-; CHECK-NEXT:   %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [alloc_ptr]
-; CHECK-NEXT:   %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 -112
+; CHECK-NEXT:   %ptr_with_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0xC0 [alloc_ptr]
+; CHECK-NEXT:   %addr_bits = load i8, ptr %alloc_ptr, align 1 => i8 -64
 ; CHECK-NEXT:   store i8 %addr_bits, ptr %alloc_ptr, align 1
-; CHECK-NEXT:   %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0x90 [nullary]
+; CHECK-NEXT:   %ptr_without_provenance = load ptr, ptr %alloc_ptr, align 8 => ptr 0xC0 [nullary]
 ; CHECK-NEXT:   ret void
 ; CHECK-NEXT: Exiting function: main

diff  --git a/llvm/test/tools/llubi/metadata.ll b/llvm/test/tools/llubi/metadata.ll
index 4c2759698e260..bf971c8ad7ef8 100644
--- a/llvm/test/tools/llubi/metadata.ll
+++ b/llvm/test/tools/llubi/metadata.ll
@@ -56,9 +56,9 @@ define void @main() {
 ; CHECK-NEXT:   store float 0.000000e+00, ptr %alloc, align 4
 ; CHECK-NEXT:   %nofpclass_load_valid = load float, ptr %alloc, align 4, !noundef !1, !nofpclass !4 => float 0.000000e+00
 ; CHECK-NEXT:   %nofpclass_load_invalid = load float, ptr %alloc, align 4, !nofpclass !5 => poison
-; CHECK-NEXT:   %alloc_ptr = alloca ptr, align 8 => ptr 0x40 [alloc_ptr]
+; CHECK-NEXT:   %alloc_ptr = alloca ptr, align 8 => ptr 0x48 [alloc_ptr]
 ; CHECK-NEXT:   store ptr %alloc_ptr, ptr %alloc_ptr, align 8
-; CHECK-NEXT:   %align_nonnull_load_valid = load ptr, ptr %alloc_ptr, align 8, !nonnull !1, !dereferenceable !6, !dereferenceable_or_null !6, !align !6, !noundef !1 => ptr 0x40 [alloc_ptr]
+; CHECK-NEXT:   %align_nonnull_load_valid = load ptr, ptr %alloc_ptr, align 8, !nonnull !1, !dereferenceable !6, !dereferenceable_or_null !6, !align !6, !noundef !1 => ptr 0x48 [alloc_ptr]
 ; CHECK-NEXT:   store ptr null, ptr %alloc_ptr, align 8
 ; CHECK-NEXT:   %align_load_valid = load ptr, ptr %alloc_ptr, align 8, !dereferenceable_or_null !6, !align !6, !noundef !1 => ptr 0x0 [nullary]
 ; CHECK-NEXT:   %nonnull_load_invalid = load ptr, ptr %alloc_ptr, align 8, !nonnull !1 => poison
@@ -79,10 +79,10 @@ define void @main() {
 ; CHECK-NEXT: Exiting function: callee_fp
 ; CHECK-NEXT:   %nofpclass_call_invalid = call float @callee_fp(), !nofpclass !5 => poison
 ; CHECK-NEXT: Entering function: callee_ptr
-; CHECK-NEXT:   ptr %x = ptr 0x40 [alloc_ptr]
+; CHECK-NEXT:   ptr %x = ptr 0x48 [alloc_ptr]
 ; CHECK-NEXT:   ret ptr %x
 ; CHECK-NEXT: Exiting function: callee_ptr
-; CHECK-NEXT:   %nonnull_align_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !nonnull !1, !align !6, !noundef !1 => ptr 0x40 [alloc_ptr]
+; CHECK-NEXT:   %nonnull_align_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !nonnull !1, !align !6, !noundef !1 => ptr 0x48 [alloc_ptr]
 ; CHECK-NEXT: Entering function: callee_ptr
 ; CHECK-NEXT:   ptr %x = ptr 0x0 [nullary]
 ; CHECK-NEXT:   ret ptr %x
@@ -94,15 +94,15 @@ define void @main() {
 ; CHECK-NEXT: Exiting function: callee_ptr
 ; CHECK-NEXT:   %nonnull_call_invalid = call ptr @callee_ptr(ptr null), !nonnull !1 => poison
 ; CHECK-NEXT: Entering function: callee_ptr
-; CHECK-NEXT:   ptr %x = ptr 0x40 [alloc_ptr]
+; CHECK-NEXT:   ptr %x = ptr 0x48 [alloc_ptr]
 ; CHECK-NEXT:   ret ptr %x
 ; CHECK-NEXT: Exiting function: callee_ptr
-; CHECK-NEXT:   %dereferenceable_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable !6 => ptr 0x40 [alloc_ptr]
+; CHECK-NEXT:   %dereferenceable_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable !6 => ptr 0x48 [alloc_ptr]
 ; CHECK-NEXT: Entering function: callee_ptr
-; CHECK-NEXT:   ptr %x = ptr 0x40 [alloc_ptr]
+; CHECK-NEXT:   ptr %x = ptr 0x48 [alloc_ptr]
 ; CHECK-NEXT:   ret ptr %x
 ; CHECK-NEXT: Exiting function: callee_ptr
-; CHECK-NEXT:   %dereferenceable_or_null_call_valid1 = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable_or_null !6 => ptr 0x40 [alloc_ptr]
+; CHECK-NEXT:   %dereferenceable_or_null_call_valid1 = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable_or_null !6 => ptr 0x48 [alloc_ptr]
 ; CHECK-NEXT: Entering function: callee_ptr
 ; CHECK-NEXT:   ptr %x = ptr 0x0 [nullary]
 ; CHECK-NEXT:   ret ptr %x

diff  --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index dc503a788754a..7b9d0d849e5f3 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -71,7 +71,7 @@ bool Context::initGlobalValues() {
     if (!GV.hasInitializer())
       continue;
 
-    MemoryObject *Obj = GlobalAddrMap.at(&GV).getMemoryObject();
+    MemoryObject *Obj = GlobalAddrMap.at(&GV).provenance().getMemoryObject();
     assert(Obj && "global pointer should have memory object provenance");
 
     Constant *Init = GV.getInitializer();
@@ -581,7 +581,9 @@ Context::allocate(uint64_t Size, uint64_t Align, StringRef Name, unsigned AS,
   auto MemObj = makeIntrusiveRefCnt<MemoryObject>(
       AlignedAddr, Size, Name, AS, InitKind, AllocKind, IsIRGlobalValue);
   MemoryObjects[AlignedAddr] = MemObj;
-  AllocationBase = AlignedAddr + AllocateSize;
+  // Extra padding to make sure getWildcardProvenance resolves to at most one
+  // memory object.
+  AllocationBase = AlignedAddr + AllocateSize + 1;
   UsedMem += AllocateSize;
   return MemObj;
 }
@@ -600,6 +602,7 @@ bool Context::free(const MemoryObject &Obj) {
   for (const APInt &Tag : MutableObj.AssociatedTags)
     TaggedProvenances.erase(Tag);
   MutableObj.AssociatedTags.clear();
+  ExposedProvenances.erase(Address);
 
   MemoryObjects.erase(It);
   return true;
@@ -612,6 +615,103 @@ Pointer Context::deriveFromMemoryObject(IntrusiveRefCntPtr<MemoryObject> Obj) {
                        Obj->getAddress()));
 }
 
+void Context::exposeProvenance(Provenance &Prov) {
+  if (Prov.Wildcard)
+    return;
+  MemoryObject *Obj = Prov.getMemoryObject();
+  if (!Obj)
+    return;
+  uint64_t Address = Obj->getAddress();
+  ExposedProvenanceSet &Set = ExposedProvenances[Address];
+  if (Set.Set.insert(&Prov).second)
+    Set.List.push_back({&Prov, ++ExposedProvenanceSetGeneration});
+}
+
+MemoryObject *
+Context::checkProvenance(const Pointer &Ptr,
+                         function_ref<bool(const Provenance &)> Check,
+                         bool HasSideEffect) {
+  auto &Prov = Ptr.provenance();
+  if (!Check(Prov))
+    return nullptr;
+  // Early return for concrete provenances.
+  if (!Prov.Wildcard)
+    return Prov.Obj.get();
+
+  MemoryObject *MO = nullptr;
+  APInt &Mask = Prov.Wildcard->ActiveMask;
+  SmallVector<ExposedProvenance> *List = nullptr;
+  uint32_t ProvenanceCount = 0;
+  if (Mask.isZero()) {
+    // The memory object hasn't been determined.
+    uint64_t Addr = Ptr.address().getLimitedValue();
+    auto Iter = ExposedProvenances.upper_bound(Addr);
+    if (Iter == ExposedProvenances.begin())
+      return nullptr;
+    auto &[BaseAddress, Set] = *std::prev(Iter);
+    auto &Obj = MemoryObjects.at(BaseAddress);
+    if (!Obj->inBounds(Ptr.address()))
+      return nullptr;
+    MO = Obj.get();
+    // We only inspect the first N exposed provenances according to the global
+    // generation number of the wildcard pointer.
+    ProvenanceCount = std::distance(
+        Set.List.begin(),
+        upper_bound(Set.List,
+                    ExposedProvenance{nullptr, Prov.Wildcard->Generation}));
+    if (HasSideEffect) {
+      Mask = APInt::getAllOnes(ProvenanceCount);
+      Prov.Wildcard->BaseAddress = BaseAddress;
+    }
+    List = &Set.List;
+  } else {
+    // We already determined the memory object in a previous memory access.
+    uint64_t BaseAddress = Prov.Wildcard->BaseAddress;
+    auto Iter = ExposedProvenances.find(BaseAddress);
+    // The memory object has been freed.
+    if (Iter == ExposedProvenances.end())
+      return nullptr;
+    MO = MemoryObjects.at(BaseAddress).get();
+    if (!MO->inBounds(Ptr.address()))
+      return nullptr;
+    List = &Iter->second.List;
+    ProvenanceCount = Mask.getBitWidth();
+  }
+  if (Prov.Obj) {
+    // We already determined the memory object via speculatable operations like
+    // gep inbounds.
+    if (Prov.Obj.get() != MO)
+      return nullptr;
+  }
+
+  bool Valid = false;
+  for (uint32_t I = 0; I != ProvenanceCount; ++I) {
+    assert((!HasSideEffect || !Mask.isZero()) &&
+           "Mask must be initialized if HasSideEffect is true.");
+    if (!Mask.isZero() && !Mask[I])
+      continue;
+    if (Check(*(*List)[I].Prov)) {
+      Valid = true;
+      // Early return as we don't need to update the Mask.
+      if (!HasSideEffect)
+        break;
+    } else if (HasSideEffect)
+      Mask.clearBit(I);
+  }
+
+  return Valid ? MO : nullptr;
+}
+
+IntrusiveRefCntPtr<Provenance> Context::getWildcardProvenance() {
+  // No exposed provenances.
+  if (ExposedProvenanceSetGeneration == 0)
+    return Provenance::nullary();
+  auto Prov = makeIntrusiveRefCnt<Provenance>(nullptr);
+  Prov->Wildcard =
+      makeIntrusiveRefCnt<WildcardProvenance>(ExposedProvenanceSetGeneration);
+  return Prov;
+}
+
 Function *Context::getTargetFunction(const Pointer &Ptr) {
   if (Ptr.address().getActiveBits() > 64)
     return nullptr;

diff  --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 5daa9e816f145..cc947bd6af51a 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -237,8 +237,40 @@ class Context {
   // Mapping from tags to provenances. Tags are lazily generated when a
   // pointer is captured by memory.
   DenseMap<APInt, IntrusiveRefCntPtr<Provenance>> TaggedProvenances;
-  // TODO: Maintains a global list of 'exposed' provenances. This is used to
-  // convert an address back to a pointer with a previously exposed provenance.
+  // Maintains a global list of 'exposed' provenances. This is used to convert
+  // an address back to a pointer with a previously exposed provenance. In
+  // theory the provenance is picked from all previously exposed provenances
+  // using angelic non-determinism. Since llubi is just an interpreter, we make
+  // two approximations:
+  //   1. Each address maps to at most one memory object during the execution of
+  //   the program, as AllocationBase increases monotonically.
+  //   2. We maintain the set of exposed provenances. When ptrtoint executes,
+  //   the provenance is inserted to the set. When inttoptr executes, it yields
+  //   a pointer with a wildcard provenance. That is, each later use will check
+  //   whether there is an exposed provenance in the snapshot allowing the
+  //   operation. The invalid provenance will be masked out after the operation.
+  //   If we cannot pick one, it is UB.
+
+  /// Exposed provenances are grouped by associated memory objects for efficient
+  /// invalidation.
+  struct ExposedProvenance {
+    IntrusiveRefCntPtr<Provenance> Prov;
+    uint64_t Generation;
+
+    bool operator<(const ExposedProvenance &RHS) const {
+      return Generation < RHS.Generation;
+    }
+  };
+  struct ExposedProvenanceSet {
+    // (Provenance, Generation)
+    SmallVector<ExposedProvenance> List;
+    // FIXME: Implement a partial order comparator for provenance instead of
+    // deduplicating by pointers.
+    SmallPtrSet<Provenance *, 4> Set;
+  };
+  std::map<uint64_t, ExposedProvenanceSet> ExposedProvenances;
+  // Global version number for the set of exposed provenances.
+  uint64_t ExposedProvenanceSetGeneration = 0;
 
   /// Get the tag for the given pointer provenance.
   APInt getTag(uint32_t BitWidth, Provenance &Prov);
@@ -329,6 +361,22 @@ class Context {
   /// Derive a pointer from a memory object with offset 0.
   /// Please use Pointer's interface for further manipulations.
   Pointer deriveFromMemoryObject(IntrusiveRefCntPtr<MemoryObject> Obj);
+  /// Mark this provenance as exposed. It is no-op if it is not associated with
+  /// a memory object or a wildcard provenance.
+  void exposeProvenance(Provenance &Prov);
+  /// A helper to check both concrete and wildcard provenance. Please don't
+  /// report UB inside the \p Check callback due to the existence of wildcard
+  /// provenance.
+  /// Returns the resolved memory object if success. \p Ptr is guaranteed to be
+  /// within the bounds of the returned memory object. But the state is not
+  /// checked, for better diagnostic messages. If \p HasSideEffect is true, some
+  /// invalid provenances will be masked out. Note that in this case the caller
+  /// must report UB when the result is nullptr.
+  MemoryObject *checkProvenance(const Pointer &Ptr,
+                                function_ref<bool(const Provenance &)> Check,
+                                bool HasSideEffect = true);
+  /// Returns the snapshot of currently exposed provenances.
+  IntrusiveRefCntPtr<Provenance> getWildcardProvenance();
   /// Convert byte sequence to a value of the given type. Uninitialized bits are
   /// flushed according to the options.
   /// If \p ContainsUndefinedBits is provided, it will be set to true when there

diff  --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index 1433526f18d3a..482930af81b5b 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -53,51 +53,60 @@ void ExecutorBase::reportErrorString(StringRef Msg) {
   Handler.onError(Msg);
 }
 
-std::optional<uint64_t> ExecutorBase::verifyMemAccess(const MemoryObject &MO,
-                                                      const APInt &Address,
-                                                      uint64_t AccessSize,
-                                                      Align Alignment,
-                                                      bool IsStore) {
+std::pair<MemoryObject *, uint64_t>
+ExecutorBase::verifyMemAccess(const Pointer &Ptr, uint64_t AccessSize,
+                              Align Alignment, bool IsStore) {
+  auto *MO = Ctx.checkProvenance(Ptr, [](const Provenance &) {
+    // TODO: check provenance
+    // TODO: check inrange(S, E)
+    return true;
+  });
+  if (!MO) {
+    reportImmediateUB()
+        << "Invalid memory access via a pointer with nullary provenance.";
+    return {};
+  }
+  const APInt &Address = Ptr.address();
   // Loading from a stack object outside its lifetime is not undefined
   // behavior and returns a poison value instead. Storing to it is still
   // undefined behavior.
-  if (IsStore ? MO.getState() != MemoryObjectState::Alive
-              : MO.getState() == MemoryObjectState::Freed) {
+  if (IsStore ? MO->getState() != MemoryObjectState::Alive
+              : MO->getState() == MemoryObjectState::Freed) {
     reportImmediateUB() << "Try to access a dead memory object at address 0x"
                         << Twine::utohexstr(Address.getZExtValue()) << ".";
-    return std::nullopt;
+    return {};
   }
 
   if (Address.countr_zero() < Log2(Alignment)) {
     reportImmediateUB() << "Misaligned memory access. Address: 0x"
                         << Twine::utohexstr(Address.getZExtValue())
                         << ", Required alignment: " << Alignment.value() << ".";
-    return std::nullopt;
+    return {};
   }
 
-  if (AccessSize > MO.getSize() || Address.ult(MO.getAddress())) {
+  if (AccessSize > MO->getSize() || Address.ult(MO->getAddress())) {
     reportImmediateUB() << "Memory access is out of bounds. Accessed size: "
                         << AccessSize << ", Address: 0x"
                         << Twine::utohexstr(Address.getZExtValue())
                         << ", Object base: 0x"
-                        << Twine::utohexstr(MO.getAddress())
-                        << ", Object size: " << MO.getSize() << ".";
-    return std::nullopt;
+                        << Twine::utohexstr(MO->getAddress())
+                        << ", Object size: " << MO->getSize() << ".";
+    return {};
   }
 
-  APInt Offset = Address - MO.getAddress();
+  APInt Offset = Address - MO->getAddress();
 
-  if (Offset.ugt(MO.getSize() - AccessSize)) {
+  if (Offset.ugt(MO->getSize() - AccessSize)) {
     reportImmediateUB() << "Memory access is out of bounds. Accessed size: "
                         << AccessSize << ", Address: 0x"
                         << Twine::utohexstr(Address.getZExtValue())
                         << ", Object base: 0x"
-                        << Twine::utohexstr(MO.getAddress())
-                        << ", Object size: " << MO.getSize() << ".";
-    return std::nullopt;
+                        << Twine::utohexstr(MO->getAddress())
+                        << ", Object size: " << MO->getSize() << ".";
+    return {};
   }
 
-  return Offset.getZExtValue();
+  return {MO, Offset.getZExtValue()};
 }
 
 AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
@@ -107,23 +116,16 @@ AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
     return AnyValue::getPoisonValue(Ctx, ValTy);
   }
   auto &PtrVal = Ptr.asPointer();
-  auto *MO = PtrVal.getMemoryObject();
-  if (!MO) {
-    reportImmediateUB()
-        << "Invalid memory access via a pointer with nullary provenance.";
-    return AnyValue::getPoisonValue(Ctx, ValTy);
-  }
-  // TODO: pointer capability check
-  if (auto Offset =
-          verifyMemAccess(*MO, PtrVal.address(),
-                          Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
-                          /*IsStore=*/false)) {
+  if (auto [MO, Offset] = verifyMemAccess(
+          PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
+          /*IsStore=*/false);
+      MO) {
     // Load from a dead stack object yields poison value.
     if (MO->getState() == MemoryObjectState::Dead)
       return AnyValue::getPoisonValue(Ctx, ValTy);
 
     bool ContainsUndefinedBits = false;
-    AnyValue Res = Ctx.load(*MO, *Offset, ValTy,
+    AnyValue Res = Ctx.load(*MO, Offset, ValTy,
                             NoUndef ? &ContainsUndefinedBits : nullptr);
     if (NoUndef && ContainsUndefinedBits)
       reportImmediateUB() << "The value loaded contains undefined bits.";
@@ -139,23 +141,17 @@ void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
     return;
   }
   auto &PtrVal = Ptr.asPointer();
-  auto *MO = PtrVal.getMemoryObject();
-  if (!MO) {
-    reportImmediateUB()
-        << "Invalid memory access via a pointer with nullary provenance.";
-    return;
-  }
-  if (MO->isConstant()) {
-    reportImmediateUB() << "Try to write to a constant memory object: "
-                        << PtrVal << ".";
-    return;
+  if (auto [MO, Offset] = verifyMemAccess(
+          PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
+          /*IsStore=*/true);
+      MO) {
+    if (MO->isConstant()) {
+      reportImmediateUB() << "Try to write to a constant memory object: "
+                          << PtrVal << ".";
+      return;
+    }
+    Ctx.store(*MO, Offset, Val, ValTy);
   }
-  // TODO: pointer capability check
-  if (auto Offset =
-          verifyMemAccess(*MO, PtrVal.address(),
-                          Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
-                          /*IsStore=*/true))
-    Ctx.store(*MO, *Offset, Val, ValTy);
 }
 
 void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,

diff  --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index d07b4dae1792e..d165192b55d63 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -99,12 +99,12 @@ class ExecutorBase {
   DiagnosticReporter reportImmediateUB();
   DiagnosticReporter reportError();
 
-  /// Check if the upcoming memory access is valid. Returns the offset relative
-  /// to the underlying object if it is valid.
-  std::optional<uint64_t> verifyMemAccess(const MemoryObject &MO,
-                                          const APInt &Address,
-                                          uint64_t AccessSize, Align Alignment,
-                                          bool IsStore);
+  /// Check if the upcoming memory access is valid. Returns the resolved memory
+  /// object and offset if it is valid.
+  std::pair<MemoryObject *, uint64_t> verifyMemAccess(const Pointer &Ptr,
+                                                      uint64_t AccessSize,
+                                                      Align Alignment,
+                                                      bool IsStore);
 
   AnyValue load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
                 bool NoUndef);

diff  --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 90d1966422a4c..341bf8fd7fca9 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -120,23 +120,25 @@ static bool violatesNoUndefAttr(AnyValue &V) {
 /// Assumes V is either a poison or a pointer.
 static bool violatesDereferenceableBytesAttr(const AnyValue &V, uint64_t Bytes,
                                              bool OrNull, unsigned AS,
-                                             const DataLayout &DL) {
+                                             Context &Ctx) {
   if (V.isPoison())
     return true;
 
   auto &Ptr = V.asPointer();
-  if (Ptr.isNullPtr(AS, DL)) {
+  if (Ptr.isNullPtr(AS, Ctx.getDataLayout())) {
     if (OrNull)
       return false;
     return true;
   }
-  auto *MO = Ptr.getMemoryObject();
+
+  auto *MO = Ctx.checkProvenance(Ptr, [&](const Provenance &) {
+    // TODO: check read_provenance
+    // TODO: check nofree for attributes/metadata.
+    return true;
+  });
   if (!MO)
     return true;
 
-  // TODO: check read_provenance
-  // TODO: check nofree for attributes/metadata.
-
   const APInt &PtrAddr = Ptr.address();
   return Bytes > MO->getSize() || PtrAddr.ult(MO->getAddress()) ||
          PtrAddr.ugt(MO->getAddress() + MO->getSize() - Bytes);
@@ -732,9 +734,14 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     APInt NewAddr = Ptr.address();
     NewAddr.insertBits(NewIndex.asInteger(), 0);
 
-    auto *MO = Ptr.getMemoryObject();
-    if (Flags.isInBounds() && (!MO || !MO->inBounds(NewAddr)))
-      return AnyValue::poison();
+    MemoryObject *MO = nullptr;
+    if (Flags.isInBounds()) {
+      MO = Ctx.checkProvenance(
+          Ptr, [](const Provenance &) { return true; },
+          /*HasSideEffect=*/false);
+      if (!MO || !MO->inBounds(NewAddr))
+        return AnyValue::poison();
+    }
 
     if (!AccumulatedOffset.isPoison()) {
       AccumulatedOffset =
@@ -746,7 +753,13 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
 
     // Should not expose provenance here even if the new address doesn't point
     // to the original object.
-    return Ptr.getWithNewAddr(NewAddr);
+    auto Res = Ptr.getWithNewAddr(NewAddr);
+    if (MO) {
+      auto &Prov = Res.provenance();
+      if (Prov.isWildcard() && !Prov.getMemoryObject())
+        Res = Res.getWithNewProvenance(Prov.getWithKnownMemoryObject(*MO));
+    }
+    return Res;
   }
 
   AnyValue computePtrAdd(const AnyValue &Ptr, const APInt &Offset,
@@ -985,7 +998,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
               break;
             if (violatesDereferenceableBytesAttr(
                     WasOn, DereferenceableBytes.getLimitedValue(),
-                    Kind == Attribute::DereferenceableOrNull, AS, DL))
+                    Kind == Attribute::DereferenceableOrNull, AS, Ctx))
               reportImmediateUB() << "The pointer " << WasOn << " violates "
                                   << (Kind == Attribute::DereferenceableOrNull
                                           ? "dereferenceable_or_null("
@@ -1010,7 +1023,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       auto Ptr = Args[0];
       if (Ptr.isPoison())
         return AnyValue();
-      auto *MO = Ptr.asPointer().getMemoryObject();
+      auto *MO = Ctx.checkProvenance(Ptr.asPointer(),
+                                     [](const Provenance &) { return true; });
       assert(MO && "Memory object accessed by lifetime intrinsic should be "
                    "always valid.");
       if (IID == Intrinsic::lifetime_start) {
@@ -1693,7 +1707,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
               std::max(AttrsAtCallSite.getDereferenceableBytes(),
                        AttrsAtCallee.getDereferenceableBytes())) {
         if (violatesDereferenceableBytesAttr(V, DereferenceableBytes,
-                                             /*OrNull=*/false, AS, DL))
+                                             /*OrNull=*/false, AS, Ctx))
           reportImmediateUB()
               << "The value " << V << " violates dereferenceable("
               << DereferenceableBytes << ") attribute.";
@@ -1701,7 +1715,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
                      std::max(AttrsAtCallSite.getDereferenceableOrNullBytes(),
                               AttrsAtCallee.getDereferenceableOrNullBytes())) {
         if (violatesDereferenceableBytesAttr(V, DereferenceableOrNullBytes,
-                                             /*OrNull=*/true, AS, DL))
+                                             /*OrNull=*/true, AS, Ctx))
           reportImmediateUB() << "The value " << V
                               << " violates "
                                  "dereferenceable_or_null("
@@ -1759,7 +1773,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
               I.getMetadata(LLVMContext::MD_dereferenceable)) {
         uint64_t Bytes = ExtractFirstIntOperand(DereferenceableBytes);
         if (violatesDereferenceableBytesAttr(V, Bytes,
-                                             /*OrNull=*/false, AS, DL))
+                                             /*OrNull=*/false, AS, Ctx))
           reportImmediateUB()
               << "The value " << V << " violates !dereferenceable !{i64 "
               << Bytes << "} metadata.";
@@ -1767,7 +1781,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
                      I.getMetadata(LLVMContext::MD_dereferenceable_or_null)) {
         uint64_t Bytes = ExtractFirstIntOperand(DereferenceableOrNullBytes);
         if (violatesDereferenceableBytesAttr(V, Bytes,
-                                             /*OrNull=*/true, AS, DL))
+                                             /*OrNull=*/true, AS, Ctx))
           reportImmediateUB()
               << "The value " << V << " violates !dereferenceable_or_null!{i64 "
               << Bytes << "} metadata.";
@@ -2419,14 +2433,26 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     setResult(GEP, std::move(Res));
   }
 
+  void visitPtrToInt(PtrToIntInst &I) {
+    return visitUnOp(I, [&](const AnyValue &V) -> AnyValue {
+      if (V.isPoison())
+        return AnyValue::poison();
+      Ctx.exposeProvenance(V.asPointer().provenance());
+      return V.asPointer().address();
+    });
+  }
+
+  // TODO: Add support for ptrtoaddr, which exposes nothing.
+
   void visitIntToPtr(IntToPtrInst &I) {
     return visitUnOp(I, [&](const AnyValue &V) -> AnyValue {
       if (V.isPoison())
         return AnyValue::poison();
-      // TODO: expose provenance
+      auto Prov = Ctx.getWildcardProvenance();
       // TODO: check metadata
-      return Pointer(V.asInteger().zextOrTrunc(
-          DL.getPointerSizeInBits(I.getType()->getPointerAddressSpace())));
+      return Pointer(std::move(Prov),
+                     V.asInteger().zextOrTrunc(DL.getPointerSizeInBits(
+                         I.getType()->getPointerAddressSpace())));
     });
   }
 

diff  --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index 3eab215f933d1..c3452b8af92c0 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -29,24 +29,17 @@ Library::Library(Context &Ctx, EventHandler &Handler, const DataLayout &DL,
     : Ctx(Ctx), Handler(Handler), DL(DL), Executor(Executor) {}
 
 std::optional<std::string> Library::readStringFromMemory(const Pointer &Ptr) {
-  auto *MO = Ptr.getMemoryObject();
-  if (!MO) {
-    Executor.reportImmediateUB()
-        << "Invalid memory access via a pointer with nullary provenance.";
-    return std::nullopt;
-  }
-
   std::string Result;
   const APInt &Address = Ptr.address();
   uint64_t Offset = 0;
 
   while (true) {
-    auto ValidOffset =
-        Executor.verifyMemAccess(*MO, Address + Offset, 1, Align(1), false);
-    if (!ValidOffset)
+    auto [MO, ValidOffset] = Executor.verifyMemAccess(
+        Ptr.getWithNewAddr(Address + Offset), 1, Align(1), /*IsStore=*/false);
+    if (!MO)
       return std::nullopt;
 
-    Byte B = (*MO)[*ValidOffset];
+    Byte B = (*MO)[ValidOffset];
     if (B.ConcreteMask != 0xFF) {
       Executor.reportImmediateUB()
           << "Read uninitialized or poison memory while "
@@ -128,7 +121,10 @@ AnyValue Library::executeFree(ArrayRef<AnyValue> Args) {
   if (Ptr.isNullPtr(/*AS=*/0, DL))
     return AnyValue();
 
-  MemoryObject *Obj = Ptr.getMemoryObject();
+  MemoryObject *Obj = Ctx.checkProvenance(Ptr, [](const Provenance &) {
+    // TODO: check nofree
+    return true;
+  });
   if (!Obj) {
     Executor.reportImmediateUB()
         << "freeing a pointer with nullary provenance.";

diff  --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 04a1aa803f66c..3f1c2567cc51d 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -22,11 +22,20 @@ IntrusiveRefCntPtr<Provenance> Provenance::nullary() {
   return Instance;
 }
 
+IntrusiveRefCntPtr<Provenance>
+Provenance::getWithKnownMemoryObject(MemoryObject &KnownObj) {
+  assert(!Obj && Wildcard && "The memory object has been determined.");
+  auto Res = makeIntrusiveRefCnt<Provenance>(*this);
+  Res->Obj = &KnownObj;
+  Res->Tag = APInt();
+  return Res;
+}
+
 void Pointer::print(raw_ostream &OS) const {
   SmallString<32> AddrStr;
   Address.toStringUnsigned(AddrStr, 16);
   OS << "ptr 0x" << AddrStr << " [";
-  if (MemoryObject *Obj = getMemoryObject()) {
+  if (MemoryObject *Obj = Prov->getMemoryObject()) {
     if (Obj->isIRGlobalValue())
       OS << "@";
     OS << Obj->getName();
@@ -36,8 +45,9 @@ void Pointer::print(raw_ostream &OS) const {
     if (State != MemoryObjectState::Alive)
       OS << (State == MemoryObjectState::Dead ? " (dead)" : " (dangling)");
   } else {
-    OS << "nullary";
+    OS << (Prov->isWildcard() ? "wildcard" : "nullary");
   }
+  // TODO: print provenance
   OS << "]";
 }
 

diff  --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index e88e2da04dcc5..b5c5078103e18 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -114,15 +114,56 @@ enum class StorageKind {
 /// Tri-state boolean value.
 enum class BooleanKind { False, True, Poison };
 
+/// A set of previously exposed provenances. It is originally yielded by
+/// inttoptr, and shared by pointers derived from the result.
+///
+/// Each capability check may invalidate some provenances. If we cannot
+/// pick one, it is UB. That is, from the angelic non-determinism view,
+/// we cannot pick a provenance to make the program reach this point.
+///
+/// For efficiency, this class has 
diff erent forms in two stages:
+/// 1. Before any memory access is performed, ActiveMask is set to zero and
+/// Generation represents the global generation number of the snapshot.
+/// 2. After a memory access is performed, we can determine exactly one memory
+/// object to be accessed (address ranges are distinct). In this case,
+/// BaseAddress is set and ActiveMask is non-zero. ActiveMask represents the
+/// validity of the first N exposed provenances associated with the memory
+/// object. The bitwidth N is the number of provenances in the list with
+/// List[I].Generation <= WildcardProvenance::Generation (The generation field
+/// in the list is monotonically increasing). That is, we can only access
+/// through exposed provenances before inttoptr executes. Note that if
+/// ActiveMask becomes zero again, UB must be triggered.
+class WildcardProvenance : public RefCountedBase<WildcardProvenance> {
+  APInt ActiveMask;
+  union {
+    uint64_t Generation;
+    uint64_t BaseAddress;
+  };
+
+  friend class Context;
+
+public:
+  explicit WildcardProvenance(uint64_t Generation)
+      : ActiveMask(), Generation(Generation) {}
+};
+
 /// Components of a pointer excluding address. They are shared between pointer
 /// values, as most of operations don't change the provenance.
 /// Each node will be assigned a unique, pointer-sized tag, which is used to
 /// represent the pointer in the memory.
+/// The provenance can be either concrete or wildcard, as determined by the
+/// cases below:
+///  Obj        Wildcard      State
+///  Null       Null          Invalid
+///  Null       NonNull       Wildcard
+///  NonNull    Null          Concrete
+///  NonNull    NonNull       Wildcard (associated with a specific MO)
 class Provenance : public RefCountedBase<Provenance> {
   // TODO: store reference to the provenance of the pointer it is derived from
 
   // The underlying memory object. It can be null for invalid or dangling
-  // pointers.
+  // pointers. Besides, for pointers with wildcard provenance, it can be null
+  // until the memory object is resolved by gep inbounds.
   IntrusiveRefCntPtr<MemoryObject> Obj;
 
   // A tag is a randomly generated unique identifier to recover the provenance
@@ -130,10 +171,11 @@ class Provenance : public RefCountedBase<Provenance> {
   // type, in bits. It may produce false negatives in some corner cases. But in
   // real practice the false negative rate should be negligible.
   // A zero tag is invalid.
-  // TODO: we need a special tag for wildcard provenance, which is introduced by
-  // inttoptr.
   APInt Tag;
 
+  // Null if it is concrete.
+  IntrusiveRefCntPtr<WildcardProvenance> Wildcard;
+
   // TODO: modeling nofree
   // TODO: modeling captures
   // TODO: modeling inrange(Start, End) attribute
@@ -146,7 +188,9 @@ class Provenance : public RefCountedBase<Provenance> {
 public:
   Provenance(IntrusiveRefCntPtr<MemoryObject> Obj) : Obj(std::move(Obj)) {}
   static IntrusiveRefCntPtr<Provenance> nullary();
+  IntrusiveRefCntPtr<Provenance> getWithKnownMemoryObject(MemoryObject &Obj);
   MemoryObject *getMemoryObject() const { return Obj.get(); }
+  bool isWildcard() const { return Wildcard != nullptr; }
 };
 
 class Pointer {
@@ -166,12 +210,14 @@ class Pointer {
   Pointer getWithNewAddr(const APInt &NewAddr) const {
     return Pointer(Prov, NewAddr);
   }
+  Pointer getWithNewProvenance(IntrusiveRefCntPtr<Provenance> NewProv) const {
+    return Pointer(NewProv, Address);
+  }
   static AnyValue null(unsigned AS, const DataLayout &DL);
   bool isNullPtr(unsigned AS, const DataLayout &DL) const;
   void print(raw_ostream &OS) const;
   const APInt &address() const { return Address; }
   Provenance &provenance() const { return *Prov; }
-  MemoryObject *getMemoryObject() const { return Prov->getMemoryObject(); }
 };
 
 // Value representation for actual values of LLVM values.


        


More information about the llvm-commits mailing list