[llvm] [llubi] Add support for exposed provenance (PR #200596)
Yingwei Zheng via llvm-commits
llvm-commits at lists.llvm.org
Sat Jun 13 08:28:11 PDT 2026
https://github.com/dtcxzyw updated https://github.com/llvm/llvm-project/pull/200596
>From 6cd61b1ad5164e58a1f1984c1bb59e2d3c10384f Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sat, 30 May 2026 10:36:47 +0800
Subject: [PATCH 1/8] [llubi] Add extra padding to memory objects
---
llvm/test/tools/llubi/alloca.ll | 8 +++---
llvm/test/tools/llubi/attributes.ll | 2 +-
llvm/test/tools/llubi/gep.ll | 6 ++--
llvm/test/tools/llubi/lib_abort.ll | 2 +-
llvm/test/tools/llubi/lib_exit.ll | 2 +-
llvm/test/tools/llubi/lib_io.ll | 4 +--
llvm/test/tools/llubi/lib_printf_format.ll | 18 ++++++------
llvm/test/tools/llubi/lib_terminate.ll | 2 +-
llvm/test/tools/llubi/loadstore_be.ll | 30 ++++++++++----------
llvm/test/tools/llubi/loadstore_le.ll | 32 +++++++++++-----------
llvm/test/tools/llubi/metadata.ll | 16 +++++------
llvm/tools/llubi/lib/Context.cpp | 4 ++-
12 files changed, 64 insertions(+), 62 deletions(-)
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/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..0bdece99a95cd 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -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;
}
>From 59554ccd7e9ad2b8ace1f67098795e1d2cf66463 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sat, 30 May 2026 10:44:37 +0800
Subject: [PATCH 2/8] [llubi] Add basic inttoptr/ptrtoint support (limited
provenance set)
---
llvm/test/tools/llubi/inttoptr.ll | 8 ++++
llvm/tools/llubi/lib/Context.cpp | 58 +++++++++++++++++++++++++++
llvm/tools/llubi/lib/Context.h | 31 +++++++++++++-
llvm/tools/llubi/lib/ExecutorBase.cpp | 46 ++++++++-------------
llvm/tools/llubi/lib/ExecutorBase.h | 3 +-
llvm/tools/llubi/lib/Interpreter.cpp | 20 +++++++--
llvm/tools/llubi/lib/Library.cpp | 10 +----
llvm/tools/llubi/lib/Value.h | 24 ++++++++++-
8 files changed, 154 insertions(+), 46 deletions(-)
diff --git a/llvm/test/tools/llubi/inttoptr.ll b/llvm/test/tools/llubi/inttoptr.ll
index d29d26f69e2ad..99207a5b649b3 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 [alloc]
; 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 0bdece99a95cd..570f25a65896c 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -602,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;
@@ -614,6 +615,63 @@ 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.emplace_back(&Prov);
+}
+
+bool Context::checkProvenance(Provenance &Prov,
+ function_ref<bool(Provenance &)> Check) {
+ if (!Check(Prov))
+ return false;
+ // Early return for concrete provenances.
+ if (!Prov.Wildcard)
+ return true;
+ auto Iter = ExposedProvenances.find(Prov.Wildcard->BaseAddress);
+ // The memory object has been freed.
+ if (Iter == ExposedProvenances.end())
+ return false;
+ auto &List = Iter->second.List;
+ APInt &Mask = Prov.Wildcard->ActiveMask;
+ uint32_t Generation = Mask.getBitWidth();
+ bool Valid = false;
+ for (uint32_t I = 0; I != Generation; ++I) {
+ if (!Mask[I])
+ continue;
+ if (Check(*List[I]))
+ Valid = true;
+ else
+ Mask.clearBit(I);
+ }
+
+ return Valid;
+}
+
+IntrusiveRefCntPtr<Provenance>
+Context::getWildcardProvenance(const APInt &Address, unsigned AS) {
+ uint64_t Addr = Address.getLimitedValue();
+ auto Iter = ExposedProvenances.upper_bound(Addr);
+ if (Iter == ExposedProvenances.begin())
+ return Provenance::nullary();
+ auto &[BaseAddress, Set] = *std::prev(Iter);
+ auto &Obj = MemoryObjects.at(BaseAddress);
+ if (!Obj->inBounds(Address) || Obj->getAddressSpace() != AS)
+ return Provenance::nullary();
+
+ auto Prov = makeIntrusiveRefCnt<Provenance>(Obj);
+ uint32_t Generation = static_cast<uint32_t>(Set.List.size());
+ Prov->Wildcard =
+ makeIntrusiveRefCnt<WildcardProvenance>(BaseAddress, Generation);
+ 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..4042d19dbaf4a 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -237,8 +237,25 @@ 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/addr
+ // 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.
+ struct ExposedProvenanceSet {
+ SmallVector<IntrusiveRefCntPtr<Provenance>> List;
+ SmallPtrSet<Provenance *, 4> Set;
+ };
+ std::map<uint64_t, ExposedProvenanceSet> ExposedProvenances;
/// Get the tag for the given pointer provenance.
APInt getTag(uint32_t BitWidth, Provenance &Prov);
@@ -329,6 +346,16 @@ 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.
+ bool checkProvenance(Provenance &Prov,
+ function_ref<bool(Provenance &)> Check);
+ IntrusiveRefCntPtr<Provenance> getWildcardProvenance(const APInt &Address,
+ unsigned AS);
/// 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..409e32fa3491d 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -53,11 +53,18 @@ void ExecutorBase::reportErrorString(StringRef Msg) {
Handler.onError(Msg);
}
-std::optional<uint64_t> ExecutorBase::verifyMemAccess(const MemoryObject &MO,
- const APInt &Address,
+std::optional<uint64_t> ExecutorBase::verifyMemAccess(const Pointer &Ptr,
uint64_t AccessSize,
Align Alignment,
bool IsStore) {
+ auto &Prov = Ptr.provenance();
+ if (!Prov.getMemoryObject()) {
+ reportImmediateUB()
+ << "Invalid memory access via a pointer with nullary provenance.";
+ return std::nullopt;
+ }
+ auto &MO = *Prov.getMemoryObject();
+ 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.
@@ -107,17 +114,11 @@ 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 Offset = verifyMemAccess(
+ PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
+ /*IsStore=*/false)) {
+ auto *MO = PtrVal.getMemoryObject();
// Load from a dead stack object yields poison value.
if (MO->getState() == MemoryObjectState::Dead)
return AnyValue::getPoisonValue(Ctx, ValTy);
@@ -139,23 +140,10 @@ 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;
- }
- // TODO: pointer capability check
- if (auto Offset =
- verifyMemAccess(*MO, PtrVal.address(),
- Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
- /*IsStore=*/true))
- Ctx.store(*MO, *Offset, Val, ValTy);
+ if (auto Offset = verifyMemAccess(
+ PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
+ /*IsStore=*/true))
+ Ctx.store(*PtrVal.getMemoryObject(), *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..78f4696af8628 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -101,8 +101,7 @@ class ExecutorBase {
/// 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,
+ std::optional<uint64_t> verifyMemAccess(const Pointer &Ptr,
uint64_t AccessSize, Align Alignment,
bool IsStore);
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 81cc968535a2f..9cd4cc972e2f5 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -2417,14 +2417,28 @@ 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 only exposes the captured address
+ // capability.
+
void visitIntToPtr(IntToPtrInst &I) {
+ unsigned AS = I.getType()->getPointerAddressSpace();
return visitUnOp(I, [&](const AnyValue &V) -> AnyValue {
if (V.isPoison())
return AnyValue::poison();
- // TODO: expose provenance
+ auto Prov = Ctx.getWildcardProvenance(V.asInteger(), AS);
// 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..15e405ac5a579 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -30,19 +30,13 @@ Library::Library(Context &Ctx, EventHandler &Handler, const DataLayout &DL,
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);
+ auto ValidOffset = Executor.verifyMemAccess(
+ Ptr.getWithNewAddr(Address + Offset), 1, Align(1), false);
if (!ValidOffset)
return std::nullopt;
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index 71ee212b4299e..41f7d789e44e2 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -104,6 +104,25 @@ enum class StorageKind {
/// Tri-state boolean value.
enum class BooleanKind { False, True, Poison };
+/// A set of previously exposed provenance. It is originally yielded by
+/// inttoptr, and shared by pointers derived from the result.
+class WildcardProvenance : public RefCountedBase<WildcardProvenance> {
+ // 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.
+ // The bitwidth represents the generation of the set. We only consider
+ // the exposed provenances in the snapshot, which is captured at the
+ // point where inttoptr executes.
+ APInt ActiveMask;
+ uint64_t BaseAddress;
+
+ friend class Context;
+
+public:
+ WildcardProvenance(uint64_t BaseAddress, uint32_t Generation)
+ : ActiveMask(APInt::getAllOnes(Generation)), BaseAddress(BaseAddress) {}
+};
+
/// 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
@@ -120,10 +139,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
>From 9a0bbf898f51aadecc270ea41281f72705cf956f Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sun, 31 May 2026 01:40:01 +0800
Subject: [PATCH 3/8] [llubi] Use Miri's semantic
---
llvm/test/tools/llubi/inttoptr.ll | 2 +-
llvm/tools/llubi/lib/Context.cpp | 91 ++++++++++++++++++---------
llvm/tools/llubi/lib/Context.h | 22 +++++--
llvm/tools/llubi/lib/ExecutorBase.cpp | 69 ++++++++++----------
llvm/tools/llubi/lib/ExecutorBase.h | 18 +++---
llvm/tools/llubi/lib/Interpreter.cpp | 81 +++++++++++++++---------
llvm/tools/llubi/lib/Library.cpp | 20 +++---
llvm/tools/llubi/lib/Value.cpp | 14 ++++-
llvm/tools/llubi/lib/Value.h | 30 +++++++--
9 files changed, 228 insertions(+), 119 deletions(-)
diff --git a/llvm/test/tools/llubi/inttoptr.ll b/llvm/test/tools/llubi/inttoptr.ll
index 99207a5b649b3..53310b84af1e9 100644
--- a/llvm/test/tools/llubi/inttoptr.ll
+++ b/llvm/test/tools/llubi/inttoptr.ll
@@ -20,6 +20,6 @@ define void @main() {
; 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 [alloc]
+; CHECK-NEXT: %cast = inttoptr i64 %off to ptr => ptr 0x8 [wildcard]
; 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 570f25a65896c..1eb7160dc07e1 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -623,52 +623,85 @@ void Context::exposeProvenance(Provenance &Prov) {
return;
uint64_t Address = Obj->getAddress();
ExposedProvenanceSet &Set = ExposedProvenances[Address];
- if (Set.Set.insert(&Prov).second)
+ if (Set.Set.insert(&Prov).second) {
Set.List.emplace_back(&Prov);
+ Set.GenerationList.push_back(++ExposedProvenanceSetGeneration);
+ }
}
-bool Context::checkProvenance(Provenance &Prov,
- function_ref<bool(Provenance &)> Check) {
+MemoryObject *
+Context::checkProvenance(const Pointer &Ptr,
+ function_ref<bool(const Provenance &)> Check,
+ unsigned AS, bool HasSideEffect) {
+ auto &Prov = Ptr.provenance();
if (!Check(Prov))
- return false;
+ return nullptr;
// Early return for concrete provenances.
if (!Prov.Wildcard)
- return true;
- auto Iter = ExposedProvenances.find(Prov.Wildcard->BaseAddress);
- // The memory object has been freed.
- if (Iter == ExposedProvenances.end())
- return false;
- auto &List = Iter->second.List;
- APInt &Mask = Prov.Wildcard->ActiveMask;
- uint32_t Generation = Mask.getBitWidth();
+ return Prov.Obj.get();
+
+ MemoryObject *MO = nullptr;
+ APInt TempMask;
+ APInt *Mask = HasSideEffect ? &Prov.Wildcard->ActiveMask : &TempMask;
+ SmallVector<IntrusiveRefCntPtr<Provenance>> *List = nullptr;
+ if (Prov.Wildcard->ActiveMask.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->getAddressSpace() != AS || !Obj->inBounds(Ptr.address()))
+ return nullptr;
+ MO = Obj.get();
+ uint32_t Generation = std::distance(
+ Set.GenerationList.begin(),
+ upper_bound(Set.GenerationList, Prov.Wildcard->Generation));
+ *Mask = APInt::getAllOnes(Generation);
+ 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->getAddressSpace() != AS || !MO->inBounds(Ptr.address()))
+ return nullptr;
+ if (HasSideEffect)
+ TempMask = Prov.Wildcard->ActiveMask;
+ List = &Iter->second.List;
+ }
+ if (Prov.Obj) {
+ // We already determined the memory object via speculatable operations like
+ // gep inbounds.
+ if (Prov.Obj.get() != MO)
+ return nullptr;
+ }
+
+ uint32_t Generation = Mask->getBitWidth();
bool Valid = false;
for (uint32_t I = 0; I != Generation; ++I) {
- if (!Mask[I])
+ if (!(*Mask)[I])
continue;
- if (Check(*List[I]))
+ if (Check(*(*List)[I]))
Valid = true;
else
- Mask.clearBit(I);
+ Mask->clearBit(I);
}
- return Valid;
+ return Valid ? MO : nullptr;
}
-IntrusiveRefCntPtr<Provenance>
-Context::getWildcardProvenance(const APInt &Address, unsigned AS) {
- uint64_t Addr = Address.getLimitedValue();
- auto Iter = ExposedProvenances.upper_bound(Addr);
- if (Iter == ExposedProvenances.begin())
+IntrusiveRefCntPtr<Provenance> Context::getWildcardProvenance() {
+ // No exposed provenances.
+ if (ExposedProvenanceSetGeneration == 0)
return Provenance::nullary();
- auto &[BaseAddress, Set] = *std::prev(Iter);
- auto &Obj = MemoryObjects.at(BaseAddress);
- if (!Obj->inBounds(Address) || Obj->getAddressSpace() != AS)
- return Provenance::nullary();
-
- auto Prov = makeIntrusiveRefCnt<Provenance>(Obj);
- uint32_t Generation = static_cast<uint32_t>(Set.List.size());
+ auto Prov = makeIntrusiveRefCnt<Provenance>(nullptr);
Prov->Wildcard =
- makeIntrusiveRefCnt<WildcardProvenance>(BaseAddress, Generation);
+ makeIntrusiveRefCnt<WildcardProvenance>(ExposedProvenanceSetGeneration);
return Prov;
}
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 4042d19dbaf4a..e805f734acf08 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -251,11 +251,19 @@ class Context {
// 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 ExposedProvenanceSet {
SmallVector<IntrusiveRefCntPtr<Provenance>> List;
+ SmallVector<uint64_t> GenerationList;
+ // 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);
@@ -352,10 +360,16 @@ class Context {
/// 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.
- bool checkProvenance(Provenance &Prov,
- function_ref<bool(Provenance &)> Check);
- IntrusiveRefCntPtr<Provenance> getWildcardProvenance(const APInt &Address,
- unsigned AS);
+ /// 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,
+ unsigned AS, 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 409e32fa3491d..4c0afca7fa69c 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -53,78 +53,82 @@ void ExecutorBase::reportErrorString(StringRef Msg) {
Handler.onError(Msg);
}
-std::optional<uint64_t> ExecutorBase::verifyMemAccess(const Pointer &Ptr,
- uint64_t AccessSize,
- Align Alignment,
- bool IsStore) {
- auto &Prov = Ptr.provenance();
- if (!Prov.getMemoryObject()) {
+std::pair<MemoryObject *, uint64_t>
+ExecutorBase::verifyMemAccess(const Pointer &Ptr, uint64_t AccessSize,
+ Align Alignment, bool IsStore, unsigned AS) {
+ auto *MO = Ctx.checkProvenance(
+ Ptr,
+ [](const Provenance &) {
+ // TODO: check provenance
+ // TODO: check inrange(S, E)
+ return true;
+ },
+ AS);
+ if (!MO) {
reportImmediateUB()
<< "Invalid memory access via a pointer with nullary provenance.";
- return std::nullopt;
+ return {};
}
- auto &MO = *Prov.getMemoryObject();
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,
- bool NoUndef) {
+ bool NoUndef, unsigned AS) {
if (Ptr.isPoison()) {
reportImmediateUB() << "Invalid memory access with a poison pointer.";
return AnyValue::getPoisonValue(Ctx, ValTy);
}
auto &PtrVal = Ptr.asPointer();
- // TODO: pointer capability check
- if (auto Offset = verifyMemAccess(
+ if (auto [MO, Offset] = verifyMemAccess(
PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
- /*IsStore=*/false)) {
- auto *MO = PtrVal.getMemoryObject();
+ /*IsStore=*/false, AS);
+ 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.";
@@ -134,16 +138,17 @@ AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
}
void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
- const AnyValue &Val, Type *ValTy) {
+ const AnyValue &Val, Type *ValTy, unsigned AS) {
if (Ptr.isPoison()) {
reportImmediateUB() << "Invalid memory access with a poison pointer.";
return;
}
auto &PtrVal = Ptr.asPointer();
- if (auto Offset = verifyMemAccess(
+ if (auto [MO, Offset] = verifyMemAccess(
PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
- /*IsStore=*/true))
- Ctx.store(*PtrVal.getMemoryObject(), *Offset, Val, ValTy);
+ /*IsStore=*/true, AS);
+ MO)
+ 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 78f4696af8628..35a37f2b3c80d 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -99,16 +99,16 @@ 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 Pointer &Ptr,
- uint64_t AccessSize, Align Alignment,
- bool IsStore);
-
- AnyValue load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
- bool NoUndef);
+ /// 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, unsigned AS);
+
+ AnyValue load(const AnyValue &Ptr, Align Alignment, Type *ValTy, bool NoUndef,
+ unsigned AS);
void store(const AnyValue &Ptr, Align Alignment, const AnyValue &Val,
- Type *ValTy);
+ Type *ValTy, unsigned AS);
void requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
uint64_t ExitCode = 0);
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 9cd4cc972e2f5..0e66a25564a1f 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -120,23 +120,28 @@ 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;
+ },
+ AS);
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);
@@ -710,7 +715,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
AnyValue computePtrAdd(const Pointer &Ptr, const APInt &Offset,
- GEPNoWrapFlags Flags, AnyValue &AccumulatedOffset) {
+ GEPNoWrapFlags Flags, AnyValue &AccumulatedOffset,
+ unsigned AS) {
if (Offset.isZero())
return Ptr;
APInt IndexBits = Ptr.address().trunc(Offset.getBitWidth());
@@ -730,9 +736,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; }, AS,
+ /*HasSideEffect=*/false);
+ if (!MO || !MO->inBounds(NewAddr))
+ return AnyValue::poison();
+ }
if (!AccumulatedOffset.isPoison()) {
AccumulatedOffset =
@@ -744,31 +755,39 @@ 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,
- GEPNoWrapFlags Flags, AnyValue &AccumulatedOffset) {
+ GEPNoWrapFlags Flags, AnyValue &AccumulatedOffset,
+ unsigned AS) {
if (Ptr.isPoison())
return AnyValue::poison();
- return computePtrAdd(Ptr.asPointer(), Offset, Flags, AccumulatedOffset);
+ return computePtrAdd(Ptr.asPointer(), Offset, Flags, AccumulatedOffset, AS);
}
AnyValue computeScaledPtrAdd(const AnyValue &Ptr, const AnyValue &Index,
const APInt &Scale, GEPNoWrapFlags Flags,
- AnyValue &AccumulatedOffset) {
+ AnyValue &AccumulatedOffset, unsigned AS) {
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);
+ return computePtrAdd(Ptr, Index.asInteger(), Flags, AccumulatedOffset,
+ AS);
auto ScaledOffset =
mulNoWrap(Index.asInteger(), Scale, Flags.hasNoUnsignedSignedWrap(),
Flags.hasNoUnsignedWrap());
if (ScaledOffset.isPoison())
return AnyValue::poison();
return computePtrAdd(Ptr, ScaledOffset.asInteger(), Flags,
- AccumulatedOffset);
+ AccumulatedOffset, AS);
}
AnyValue canonicalizeIndex(const AnyValue &Idx, unsigned IndexBitWidth,
@@ -983,7 +1002,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("
@@ -1008,7 +1027,9 @@ 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; },
+ CB.getOperand(0)->getType()->getPointerAddressSpace());
assert(MO && "Memory object accessed by lifetime intrinsic should be "
"always valid.");
if (IID == Intrinsic::lifetime_start) {
@@ -1691,7 +1712,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.";
@@ -1699,7 +1720,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("
@@ -1757,7 +1778,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.";
@@ -1765,7 +1786,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.";
@@ -2347,6 +2368,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
void visitGetElementPtrInst(GetElementPtrInst &GEP) {
uint32_t IndexBitWidth =
DL.getIndexSizeInBits(GEP.getType()->getPointerAddressSpace());
+ unsigned AS = GEP.getType()->getPointerAddressSpace();
GEPNoWrapFlags Flags = GEP.getNoWrapFlags();
AnyValue Res = getValue(GEP.getPointerOperand());
AnyValue AccumulatedOffset = APInt(IndexBitWidth, 0);
@@ -2365,7 +2387,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
AccumulatedOffset.asAggregate()))
ResElem = computeScaledPtrAdd(
ResElem, canonicalizeIndex(IndexElem, IndexBitWidth, Flags),
- Scale, Flags, OffsetElem);
+ Scale, Flags, OffsetElem, AS);
} else {
AnyValue CanonicalIndex =
canonicalizeIndex(Index, IndexBitWidth, Flags);
@@ -2373,10 +2395,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
for (auto &&[ResElem, OffsetElem] :
zip(Res.asAggregate(), AccumulatedOffset.asAggregate()))
ResElem = computeScaledPtrAdd(ResElem, CanonicalIndex, Scale, Flags,
- OffsetElem);
+ OffsetElem, AS);
} else {
Res = computeScaledPtrAdd(Res, CanonicalIndex, Scale, Flags,
- AccumulatedOffset);
+ AccumulatedOffset, AS);
}
}
};
@@ -2430,11 +2452,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
// capability.
void visitIntToPtr(IntToPtrInst &I) {
- unsigned AS = I.getType()->getPointerAddressSpace();
return visitUnOp(I, [&](const AnyValue &V) -> AnyValue {
if (V.isPoison())
return AnyValue::poison();
- auto Prov = Ctx.getWildcardProvenance(V.asInteger(), AS);
+ auto Prov = Ctx.getWildcardProvenance();
// TODO: check metadata
return Pointer(std::move(Prov),
V.asInteger().zextOrTrunc(DL.getPointerSizeInBits(
@@ -2444,7 +2465,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
void visitLoadInst(LoadInst &LI) {
auto RetVal = load(getValue(LI.getPointerOperand()), LI.getAlign(),
- LI.getType(), LI.hasMetadata(LLVMContext::MD_noundef));
+ LI.getType(), LI.hasMetadata(LLVMContext::MD_noundef),
+ LI.getPointerAddressSpace());
// TODO: track volatile loads
handleMetadata(LI.getType(), RetVal, LI);
setResult(LI, std::move(RetVal));
@@ -2455,7 +2477,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
auto &Val = getValue(SI.getValueOperand());
// TODO: track volatile stores
// TODO: handle metadata
- store(Ptr, SI.getAlign(), Val, SI.getValueOperand()->getType());
+ store(Ptr, SI.getAlign(), Val, SI.getValueOperand()->getType(),
+ SI.getPointerAddressSpace());
if (!hasProgramExited() && !Handler.onInstructionExecuted(SI, AnyValue()))
setFailed();
}
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index 15e405ac5a579..4bdafe8db96ab 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -29,18 +29,18 @@ 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();
std::string Result;
const APInt &Address = Ptr.address();
uint64_t Offset = 0;
while (true) {
- auto ValidOffset = Executor.verifyMemAccess(
- Ptr.getWithNewAddr(Address + Offset), 1, Align(1), false);
- if (!ValidOffset)
+ auto [MO, ValidOffset] =
+ Executor.verifyMemAccess(Ptr.getWithNewAddr(Address + Offset), 1,
+ Align(1), /*IsStore=*/false, /*AS=*/0);
+ 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 "
@@ -122,7 +122,13 @@ 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;
+ },
+ /*AS=*/0);
if (!Obj) {
Executor.reportImmediateUB()
<< "freeing a pointer with nullary provenance.";
@@ -276,7 +282,7 @@ AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
case 'n': {
OS.flush();
Executor.store(Arg, Align(4), AnyValue(APInt(32, Output.size())),
- Type::getInt32Ty(Ctx.getContext()));
+ Type::getInt32Ty(Ctx.getContext()), /*AS=*/0);
break;
}
case 'p': {
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 41f7d789e44e2..aa2df04003d30 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -104,7 +104,7 @@ enum class StorageKind {
/// Tri-state boolean value.
enum class BooleanKind { False, True, Poison };
-/// A set of previously exposed provenance. It is originally yielded by
+/// A set of previously exposed provenances. It is originally yielded by
/// inttoptr, and shared by pointers derived from the result.
class WildcardProvenance : public RefCountedBase<WildcardProvenance> {
// Each capability check may invalidate some provenances. If we cannot
@@ -114,24 +114,38 @@ class WildcardProvenance : public RefCountedBase<WildcardProvenance> {
// the exposed provenances in the snapshot, which is captured at the
// point where inttoptr executes.
APInt ActiveMask;
- uint64_t BaseAddress;
+ union {
+ // Initially, we record a snapshot of exposed provenances. It is active when
+ // ActiveMask is zero.
+ uint64_t Generation;
+ // After a memory access is performed, the memory object can be determined.
+ // It is active when ActiveMask is non-zero.
+ uint64_t BaseAddress;
+ };
friend class Context;
public:
- WildcardProvenance(uint64_t BaseAddress, uint32_t Generation)
- : ActiveMask(APInt::getAllOnes(Generation)), BaseAddress(BaseAddress) {}
+ 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 (limited to exposed provenances associated
+/// with a specific memory object)
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
@@ -156,7 +170,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 {
@@ -176,12 +192,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.
>From 69fac30058554ae93d42450442974a685ae520f9 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Wed, 3 Jun 2026 02:35:09 +0800
Subject: [PATCH 4/8] [llubi] Fix compilation error after rebasing
---
llvm/tools/llubi/lib/Context.cpp | 2 +-
llvm/tools/llubi/lib/ExecutorBase.cpp | 8 +++++++-
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 1eb7160dc07e1..c0939de6104db 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();
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index 4c0afca7fa69c..aa3abdfb511e0 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -147,8 +147,14 @@ void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
if (auto [MO, Offset] = verifyMemAccess(
PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
/*IsStore=*/true, AS);
- MO)
+ MO) {
+ if (MO->isConstant()) {
+ reportImmediateUB() << "Try to write to a constant memory object: "
+ << PtrVal << ".";
+ return;
+ }
Ctx.store(*MO, Offset, Val, ValTy);
+ }
}
void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
>From c12b74446803a675e096fbc359725da831a1afbc Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sat, 13 Jun 2026 22:47:42 +0800
Subject: [PATCH 5/8] [llubi] Address review comments.
---
llvm/tools/llubi/lib/Context.cpp | 42 +++++++++++++----------
llvm/tools/llubi/lib/Context.h | 13 ++++----
llvm/tools/llubi/lib/ExecutorBase.cpp | 23 ++++++-------
llvm/tools/llubi/lib/ExecutorBase.h | 13 ++++----
llvm/tools/llubi/lib/Interpreter.cpp | 48 +++++++++++----------------
llvm/tools/llubi/lib/Library.cpp | 18 ++++------
llvm/tools/llubi/lib/Value.h | 37 ++++++++++++---------
7 files changed, 96 insertions(+), 98 deletions(-)
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index c0939de6104db..6a579c3ef6ab4 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -632,7 +632,7 @@ void Context::exposeProvenance(Provenance &Prov) {
MemoryObject *
Context::checkProvenance(const Pointer &Ptr,
function_ref<bool(const Provenance &)> Check,
- unsigned AS, bool HasSideEffect) {
+ bool HasSideEffect) {
auto &Prov = Ptr.provenance();
if (!Check(Prov))
return nullptr;
@@ -641,10 +641,10 @@ Context::checkProvenance(const Pointer &Ptr,
return Prov.Obj.get();
MemoryObject *MO = nullptr;
- APInt TempMask;
- APInt *Mask = HasSideEffect ? &Prov.Wildcard->ActiveMask : &TempMask;
+ APInt &Mask = Prov.Wildcard->ActiveMask;
SmallVector<IntrusiveRefCntPtr<Provenance>> *List = nullptr;
- if (Prov.Wildcard->ActiveMask.isZero()) {
+ 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);
@@ -652,27 +652,31 @@ Context::checkProvenance(const Pointer &Ptr,
return nullptr;
auto &[BaseAddress, Set] = *std::prev(Iter);
auto &Obj = MemoryObjects.at(BaseAddress);
- if (Obj->getAddressSpace() != AS || !Obj->inBounds(Ptr.address()))
+ if (!Obj->inBounds(Ptr.address()))
return nullptr;
MO = Obj.get();
- uint32_t Generation = std::distance(
+ // We only inspect the first N exposed provenances according to the global
+ // generation number of the wildcard pointer.
+ ProvenanceCount = std::distance(
Set.GenerationList.begin(),
upper_bound(Set.GenerationList, Prov.Wildcard->Generation));
- *Mask = APInt::getAllOnes(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。
+ // 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->getAddressSpace() != AS || !MO->inBounds(Ptr.address()))
+ if (!MO->inBounds(Ptr.address()))
return nullptr;
- if (HasSideEffect)
- TempMask = Prov.Wildcard->ActiveMask;
List = &Iter->second.List;
+ ProvenanceCount = Mask.getBitWidth();
}
if (Prov.Obj) {
// We already determined the memory object via speculatable operations like
@@ -681,15 +685,19 @@ Context::checkProvenance(const Pointer &Ptr,
return nullptr;
}
- uint32_t Generation = Mask->getBitWidth();
bool Valid = false;
- for (uint32_t I = 0; I != Generation; ++I) {
- if (!(*Mask)[I])
+ 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]))
+ if (Check(*(*List)[I])) {
Valid = true;
- else
- Mask->clearBit(I);
+ // Early return as we don't need to update the Mask.
+ if (!HasSideEffect)
+ break;
+ } else if (HasSideEffect)
+ Mask.clearBit(I);
}
return Valid ? MO : nullptr;
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index e805f734acf08..9be7fb84aa7df 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -245,12 +245,11 @@ class Context {
// 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/addr
- // 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.
+ // 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.
@@ -367,7 +366,7 @@ class Context {
/// must report UB when the result is nullptr.
MemoryObject *checkProvenance(const Pointer &Ptr,
function_ref<bool(const Provenance &)> Check,
- unsigned AS, bool HasSideEffect = true);
+ 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
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index aa3abdfb511e0..482930af81b5b 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -55,15 +55,12 @@ void ExecutorBase::reportErrorString(StringRef Msg) {
std::pair<MemoryObject *, uint64_t>
ExecutorBase::verifyMemAccess(const Pointer &Ptr, uint64_t AccessSize,
- Align Alignment, bool IsStore, unsigned AS) {
- auto *MO = Ctx.checkProvenance(
- Ptr,
- [](const Provenance &) {
- // TODO: check provenance
- // TODO: check inrange(S, E)
- return true;
- },
- AS);
+ 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.";
@@ -113,7 +110,7 @@ ExecutorBase::verifyMemAccess(const Pointer &Ptr, uint64_t AccessSize,
}
AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
- bool NoUndef, unsigned AS) {
+ bool NoUndef) {
if (Ptr.isPoison()) {
reportImmediateUB() << "Invalid memory access with a poison pointer.";
return AnyValue::getPoisonValue(Ctx, ValTy);
@@ -121,7 +118,7 @@ AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
auto &PtrVal = Ptr.asPointer();
if (auto [MO, Offset] = verifyMemAccess(
PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
- /*IsStore=*/false, AS);
+ /*IsStore=*/false);
MO) {
// Load from a dead stack object yields poison value.
if (MO->getState() == MemoryObjectState::Dead)
@@ -138,7 +135,7 @@ AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
}
void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
- const AnyValue &Val, Type *ValTy, unsigned AS) {
+ const AnyValue &Val, Type *ValTy) {
if (Ptr.isPoison()) {
reportImmediateUB() << "Invalid memory access with a poison pointer.";
return;
@@ -146,7 +143,7 @@ void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
auto &PtrVal = Ptr.asPointer();
if (auto [MO, Offset] = verifyMemAccess(
PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
- /*IsStore=*/true, AS);
+ /*IsStore=*/true);
MO) {
if (MO->isConstant()) {
reportImmediateUB() << "Try to write to a constant memory object: "
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index 35a37f2b3c80d..d165192b55d63 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -101,14 +101,15 @@ class ExecutorBase {
/// 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, unsigned AS);
+ 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,
- unsigned AS);
+ AnyValue load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
+ bool NoUndef);
void store(const AnyValue &Ptr, Align Alignment, const AnyValue &Val,
- Type *ValTy, unsigned AS);
+ Type *ValTy);
void requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
uint64_t ExitCode = 0);
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 0e66a25564a1f..b5a7b0d26475c 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -131,14 +131,11 @@ static bool violatesDereferenceableBytesAttr(const AnyValue &V, uint64_t Bytes,
return true;
}
- auto *MO = Ctx.checkProvenance(
- Ptr,
- [&](const Provenance &) {
- // TODO: check read_provenance
- // TODO: check nofree for attributes/metadata.
- return true;
- },
- AS);
+ auto *MO = Ctx.checkProvenance(Ptr, [&](const Provenance &) {
+ // TODO: check read_provenance
+ // TODO: check nofree for attributes/metadata.
+ return true;
+ });
if (!MO)
return true;
@@ -715,8 +712,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
AnyValue computePtrAdd(const Pointer &Ptr, const APInt &Offset,
- GEPNoWrapFlags Flags, AnyValue &AccumulatedOffset,
- unsigned AS) {
+ GEPNoWrapFlags Flags, AnyValue &AccumulatedOffset) {
if (Offset.isZero())
return Ptr;
APInt IndexBits = Ptr.address().trunc(Offset.getBitWidth());
@@ -739,7 +735,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
MemoryObject *MO = nullptr;
if (Flags.isInBounds()) {
MO = Ctx.checkProvenance(
- Ptr, [](const Provenance &) { return true; }, AS,
+ Ptr, [](const Provenance &) { return true; },
/*HasSideEffect=*/false);
if (!MO || !MO->inBounds(NewAddr))
return AnyValue::poison();
@@ -765,29 +761,27 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
AnyValue computePtrAdd(const AnyValue &Ptr, const APInt &Offset,
- GEPNoWrapFlags Flags, AnyValue &AccumulatedOffset,
- unsigned AS) {
+ GEPNoWrapFlags Flags, AnyValue &AccumulatedOffset) {
if (Ptr.isPoison())
return AnyValue::poison();
- return computePtrAdd(Ptr.asPointer(), Offset, Flags, AccumulatedOffset, AS);
+ return computePtrAdd(Ptr.asPointer(), Offset, Flags, AccumulatedOffset);
}
AnyValue computeScaledPtrAdd(const AnyValue &Ptr, const AnyValue &Index,
const APInt &Scale, GEPNoWrapFlags Flags,
- AnyValue &AccumulatedOffset, unsigned AS) {
+ 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,
- AS);
+ 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, AS);
+ AccumulatedOffset);
}
AnyValue canonicalizeIndex(const AnyValue &Idx, unsigned IndexBitWidth,
@@ -1027,9 +1021,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
auto Ptr = Args[0];
if (Ptr.isPoison())
return AnyValue();
- auto *MO = Ctx.checkProvenance(
- Ptr.asPointer(), [](const Provenance &) { return true; },
- CB.getOperand(0)->getType()->getPointerAddressSpace());
+ 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) {
@@ -2368,7 +2361,6 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
void visitGetElementPtrInst(GetElementPtrInst &GEP) {
uint32_t IndexBitWidth =
DL.getIndexSizeInBits(GEP.getType()->getPointerAddressSpace());
- unsigned AS = GEP.getType()->getPointerAddressSpace();
GEPNoWrapFlags Flags = GEP.getNoWrapFlags();
AnyValue Res = getValue(GEP.getPointerOperand());
AnyValue AccumulatedOffset = APInt(IndexBitWidth, 0);
@@ -2387,7 +2379,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
AccumulatedOffset.asAggregate()))
ResElem = computeScaledPtrAdd(
ResElem, canonicalizeIndex(IndexElem, IndexBitWidth, Flags),
- Scale, Flags, OffsetElem, AS);
+ Scale, Flags, OffsetElem);
} else {
AnyValue CanonicalIndex =
canonicalizeIndex(Index, IndexBitWidth, Flags);
@@ -2395,10 +2387,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
for (auto &&[ResElem, OffsetElem] :
zip(Res.asAggregate(), AccumulatedOffset.asAggregate()))
ResElem = computeScaledPtrAdd(ResElem, CanonicalIndex, Scale, Flags,
- OffsetElem, AS);
+ OffsetElem);
} else {
Res = computeScaledPtrAdd(Res, CanonicalIndex, Scale, Flags,
- AccumulatedOffset, AS);
+ AccumulatedOffset);
}
}
};
@@ -2465,8 +2457,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
void visitLoadInst(LoadInst &LI) {
auto RetVal = load(getValue(LI.getPointerOperand()), LI.getAlign(),
- LI.getType(), LI.hasMetadata(LLVMContext::MD_noundef),
- LI.getPointerAddressSpace());
+ LI.getType(), LI.hasMetadata(LLVMContext::MD_noundef));
// TODO: track volatile loads
handleMetadata(LI.getType(), RetVal, LI);
setResult(LI, std::move(RetVal));
@@ -2477,8 +2468,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
auto &Val = getValue(SI.getValueOperand());
// TODO: track volatile stores
// TODO: handle metadata
- store(Ptr, SI.getAlign(), Val, SI.getValueOperand()->getType(),
- SI.getPointerAddressSpace());
+ store(Ptr, SI.getAlign(), Val, SI.getValueOperand()->getType());
if (!hasProgramExited() && !Handler.onInstructionExecuted(SI, AnyValue()))
setFailed();
}
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index 4bdafe8db96ab..c3452b8af92c0 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -34,9 +34,8 @@ std::optional<std::string> Library::readStringFromMemory(const Pointer &Ptr) {
uint64_t Offset = 0;
while (true) {
- auto [MO, ValidOffset] =
- Executor.verifyMemAccess(Ptr.getWithNewAddr(Address + Offset), 1,
- Align(1), /*IsStore=*/false, /*AS=*/0);
+ auto [MO, ValidOffset] = Executor.verifyMemAccess(
+ Ptr.getWithNewAddr(Address + Offset), 1, Align(1), /*IsStore=*/false);
if (!MO)
return std::nullopt;
@@ -122,13 +121,10 @@ AnyValue Library::executeFree(ArrayRef<AnyValue> Args) {
if (Ptr.isNullPtr(/*AS=*/0, DL))
return AnyValue();
- MemoryObject *Obj = Ctx.checkProvenance(
- Ptr,
- [](const Provenance &) {
- // TODO: check nofree
- return true;
- },
- /*AS=*/0);
+ MemoryObject *Obj = Ctx.checkProvenance(Ptr, [](const Provenance &) {
+ // TODO: check nofree
+ return true;
+ });
if (!Obj) {
Executor.reportImmediateUB()
<< "freeing a pointer with nullary provenance.";
@@ -282,7 +278,7 @@ AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
case 'n': {
OS.flush();
Executor.store(Arg, Align(4), AnyValue(APInt(32, Output.size())),
- Type::getInt32Ty(Ctx.getContext()), /*AS=*/0);
+ Type::getInt32Ty(Ctx.getContext()));
break;
}
case 'p': {
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index aa2df04003d30..c8242af511498 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -106,20 +106,26 @@ 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 different 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 first N exposed provenances associated with the memory object.
+/// The bitwidth N is the first index I in the provenance list with
+/// List[I].Generation > WildcardProvenance::Generation. 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> {
- // 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.
- // The bitwidth represents the generation of the set. We only consider
- // the exposed provenances in the snapshot, which is captured at the
- // point where inttoptr executes.
APInt ActiveMask;
union {
- // Initially, we record a snapshot of exposed provenances. It is active when
- // ActiveMask is zero.
uint64_t Generation;
- // After a memory access is performed, the memory object can be determined.
- // It is active when ActiveMask is non-zero.
uint64_t BaseAddress;
};
@@ -135,11 +141,12 @@ class WildcardProvenance : public RefCountedBase<WildcardProvenance> {
/// 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 (limited to exposed provenances associated
-/// with a specific memory object)
+/// 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
>From 6073cca3046b12b530372dca46800d7cab62ca37 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sat, 13 Jun 2026 22:54:03 +0800
Subject: [PATCH 6/8] [llubi] Merge lists. NFC.
---
llvm/tools/llubi/lib/Context.cpp | 15 +++++++--------
llvm/tools/llubi/lib/Context.h | 12 ++++++++++--
2 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 6a579c3ef6ab4..7b9d0d849e5f3 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -623,10 +623,8 @@ void Context::exposeProvenance(Provenance &Prov) {
return;
uint64_t Address = Obj->getAddress();
ExposedProvenanceSet &Set = ExposedProvenances[Address];
- if (Set.Set.insert(&Prov).second) {
- Set.List.emplace_back(&Prov);
- Set.GenerationList.push_back(++ExposedProvenanceSetGeneration);
- }
+ if (Set.Set.insert(&Prov).second)
+ Set.List.push_back({&Prov, ++ExposedProvenanceSetGeneration});
}
MemoryObject *
@@ -642,7 +640,7 @@ Context::checkProvenance(const Pointer &Ptr,
MemoryObject *MO = nullptr;
APInt &Mask = Prov.Wildcard->ActiveMask;
- SmallVector<IntrusiveRefCntPtr<Provenance>> *List = nullptr;
+ SmallVector<ExposedProvenance> *List = nullptr;
uint32_t ProvenanceCount = 0;
if (Mask.isZero()) {
// The memory object hasn't been determined.
@@ -658,8 +656,9 @@ Context::checkProvenance(const Pointer &Ptr,
// We only inspect the first N exposed provenances according to the global
// generation number of the wildcard pointer.
ProvenanceCount = std::distance(
- Set.GenerationList.begin(),
- upper_bound(Set.GenerationList, Prov.Wildcard->Generation));
+ Set.List.begin(),
+ upper_bound(Set.List,
+ ExposedProvenance{nullptr, Prov.Wildcard->Generation}));
if (HasSideEffect) {
Mask = APInt::getAllOnes(ProvenanceCount);
Prov.Wildcard->BaseAddress = BaseAddress;
@@ -691,7 +690,7 @@ Context::checkProvenance(const Pointer &Ptr,
"Mask must be initialized if HasSideEffect is true.");
if (!Mask.isZero() && !Mask[I])
continue;
- if (Check(*(*List)[I])) {
+ if (Check(*(*List)[I].Prov)) {
Valid = true;
// Early return as we don't need to update the Mask.
if (!HasSideEffect)
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 9be7fb84aa7df..f15d1c8d8ac81 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -253,9 +253,17 @@ class Context {
/// 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 {
- SmallVector<IntrusiveRefCntPtr<Provenance>> List;
- SmallVector<uint64_t> GenerationList;
+ // (Provenance, Generation)
+ SmallVector<ExposedProvenance> List;
// FIXME: Implement a partial order comparator for provenance instead of
// deduplicating by pointers.
SmallPtrSet<Provenance *, 4> Set;
>From f1af251abfa5f5f87f77032278f84565a46c84c5 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sat, 13 Jun 2026 23:16:56 +0800
Subject: [PATCH 7/8] [llubi] Add more negative tests. NFC.
---
llvm/test/tools/llubi/inttoptr_generation.ll | 34 +++++++++++++++++
llvm/test/tools/llubi/inttoptr_multiobj.ll | 40 ++++++++++++++++++++
2 files changed, 74 insertions(+)
create mode 100644 llvm/test/tools/llubi/inttoptr_generation.ll
create mode 100644 llvm/test/tools/llubi/inttoptr_multiobj.ll
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_multiobj.ll b/llvm/test/tools/llubi/inttoptr_multiobj.ll
new file mode 100644
index 0000000000000..78dde07cc64b0
--- /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
+ %ptrdiff = sub i64 %addr2, %addr1
+
+ %p = inttoptr i64 %addr1 to ptr
+
+ %gep1 = getelementptr i8, ptr %p, i64 %ptrdiff
+ %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: %ptrdiff = 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 %ptrdiff => 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.
>From 5adb4ff36a679cd99c2479f98f129a67f9bda69d Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sat, 13 Jun 2026 23:27:47 +0800
Subject: [PATCH 8/8] [llubi] Add wildcard provenance + gep tests. NFC.
---
llvm/test/tools/llubi/inttoptr_gep.ll | 38 +++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
create mode 100644 llvm/test/tools/llubi/inttoptr_gep.ll
diff --git a/llvm/test/tools/llubi/inttoptr_gep.ll b/llvm/test/tools/llubi/inttoptr_gep.ll
new file mode 100644
index 0000000000000..6ebc19ec172c6
--- /dev/null
+++ b/llvm/test/tools/llubi/inttoptr_gep.ll
@@ -0,0 +1,38 @@
+; 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
+ %ptrdiff1 = sub i64 %addr2, %addr1
+ %ptrdiff2 = sub i64 %addr1, %addr2
+
+ %p = inttoptr i64 %addr1 to ptr
+ %gep1 = getelementptr i8, ptr %p, i64 %ptrdiff1
+ %gep2 = getelementptr inbounds i8, ptr %gep1, i64 2
+ %gep3 = getelementptr i8, ptr %gep2, i64 %ptrdiff2
+
+ ; 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: %ptrdiff1 = sub i64 %addr2, %addr1 => i64 8
+; CHECK-NEXT: %ptrdiff2 = sub i64 %addr1, %addr2 => i64 -8
+; CHECK-NEXT: %p = inttoptr i64 %addr1 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT: %gep1 = getelementptr i8, ptr %p, i64 %ptrdiff1 => 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 %ptrdiff2 => ptr 0xA [alloc2 + -6]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i16 0, ptr %gep3, align 2 at @main <stdin>:21
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
More information about the llvm-commits
mailing list