[llvm] [llubi] Add support for captures (PR #201170)
Yingwei Zheng via llvm-commits
llvm-commits at lists.llvm.org
Sat Aug 1 10:04:41 PDT 2026
https://github.com/dtcxzyw updated https://github.com/llvm/llvm-project/pull/201170
>From feccfca3455eeca9ef2b601c4ebf996de40dc909 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 4 Jun 2026 01:31:10 +0800
Subject: [PATCH 1/4] [llubi] Add support for captures
---
llvm/test/tools/llubi/captures.ll | 120 ++++++++++++++++++
...dereferenceable_capture_read_provenance.ll | 24 ++++
.../llubi/load_capture_read_provenance.ll | 24 ++++
.../tools/llubi/store_capture_provenance.ll | 24 ++++
.../tools/llubi/volatile_capture_address.ll | 24 ++++
llvm/test/tools/llubi/wildcard_provenance.ll | 38 ++++++
...dcard_provenance_angelic_nondeterminism.ll | 31 +++++
.../llubi/wildcard_provenance_gep_inbounds.ll | 29 +++++
llvm/tools/llubi/lib/ExecutorBase.cpp | 33 +++--
llvm/tools/llubi/lib/ExecutorBase.h | 20 +--
llvm/tools/llubi/lib/Interpreter.cpp | 75 ++++++++++-
llvm/tools/llubi/lib/Library.cpp | 6 +-
llvm/tools/llubi/lib/Value.cpp | 4 +-
llvm/tools/llubi/lib/Value.h | 18 ++-
14 files changed, 438 insertions(+), 32 deletions(-)
create mode 100644 llvm/test/tools/llubi/captures.ll
create mode 100644 llvm/test/tools/llubi/dereferenceable_capture_read_provenance.ll
create mode 100644 llvm/test/tools/llubi/load_capture_read_provenance.ll
create mode 100644 llvm/test/tools/llubi/store_capture_provenance.ll
create mode 100644 llvm/test/tools/llubi/volatile_capture_address.ll
create mode 100644 llvm/test/tools/llubi/wildcard_provenance.ll
create mode 100644 llvm/test/tools/llubi/wildcard_provenance_angelic_nondeterminism.ll
create mode 100644 llvm/test/tools/llubi/wildcard_provenance_gep_inbounds.ll
diff --git a/llvm/test/tools/llubi/captures.ll b/llvm/test/tools/llubi/captures.ll
new file mode 100644
index 0000000000000..4ac808c4248aa
--- /dev/null
+++ b/llvm/test/tools/llubi/captures.ll
@@ -0,0 +1,120 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @write(ptr %p) {
+ store i8 1, ptr %p
+ ret void
+}
+
+define ptr @address_only(ptr captures(address) %p) {
+ ret ptr %p
+}
+
+define ptr @address_is_null_only(ptr captures(address_is_null) %p) {
+ ret ptr %p
+}
+
+define ptr @provenance_only(ptr captures(provenance) %p) {
+ ret ptr %p
+}
+
+define ptr @read_provenance_only(ptr captures(read_provenance) %p) {
+ ret ptr %p
+}
+
+ at g = global ptr null
+
+define ptr @ret_capture_read_provenance_other_capture_address(ptr captures(address, ret: read_provenance) %p) {
+ store ptr %p, ptr @g
+ %p2 = load ptr, ptr @g
+ ret ptr %p2
+}
+
+define void @main() {
+ %p = alloca i8
+ call void @write(ptr captures(none) %p)
+ %p_poison = call ptr @address_only(ptr poison)
+ %p_address_only = call ptr @address_only(ptr %p)
+ %p_address_is_null_only = call ptr @address_is_null_only(ptr %p)
+ %cmp1 = icmp eq ptr %p_address_only, %p_address_only
+ %cmp2 = icmp eq ptr %p_address_is_null_only, %p_address_is_null_only
+ %cmp3 = icmp eq ptr %p_address_is_null_only, null
+ %cmp4 = icmp eq ptr null, %p_address_is_null_only
+ %cmp5 = icmp eq ptr null, null
+
+ %p_read_provenance = call ptr @read_provenance_only(ptr %p)
+ %p_provenance = call ptr @provenance_only(ptr %p)
+
+ %p_read_provenance_2 = call ptr @read_provenance_only(ptr %p_provenance)
+ %p_none = call ptr @provenance_only(ptr %p_address_only)
+
+ %p_read_provenance2 = call ptr @ret_capture_read_provenance_other_capture_address(ptr %p)
+ %p_address_only2 = load ptr, ptr @g
+
+ call void @llvm.assume(i1 true) ["dereferenceable"(ptr %p_read_provenance, i32 1)]
+ %val = load i8, ptr %p_read_provenance
+ store i8 0, ptr %p_provenance
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p = alloca i8, align 1 => ptr 0x11 [p]
+; CHECK-NEXT: Entering function: write
+; CHECK-NEXT: ptr %p = ptr 0x11 [p]
+; CHECK-NEXT: store i8 1, ptr %p, align 1
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: write
+; CHECK-NEXT: call void @write(ptr captures(none) %p)
+; CHECK-NEXT: Entering function: address_only
+; CHECK-NEXT: ptr %p = poison
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: address_only
+; CHECK-NEXT: %p_poison = call ptr @address_only(ptr poison) => poison
+; CHECK-NEXT: Entering function: address_only
+; CHECK-NEXT: ptr %p = ptr 0x11 [p]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: address_only
+; CHECK-NEXT: %p_address_only = call ptr @address_only(ptr %p) => ptr 0x11 [p address]
+; CHECK-NEXT: Entering function: address_is_null_only
+; CHECK-NEXT: ptr %p = ptr 0x11 [p]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: address_is_null_only
+; CHECK-NEXT: %p_address_is_null_only = call ptr @address_is_null_only(ptr %p) => ptr 0x11 [p address_is_null]
+; CHECK-NEXT: %cmp1 = icmp eq ptr %p_address_only, %p_address_only => T
+; CHECK-NEXT: %cmp2 = icmp eq ptr %p_address_is_null_only, %p_address_is_null_only => poison
+; CHECK-NEXT: %cmp3 = icmp eq ptr %p_address_is_null_only, null => F
+; CHECK-NEXT: %cmp4 = icmp eq ptr null, %p_address_is_null_only => F
+; CHECK-NEXT: %cmp5 = icmp eq ptr null, null => T
+; CHECK-NEXT: Entering function: read_provenance_only
+; CHECK-NEXT: ptr %p = ptr 0x11 [p]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: read_provenance_only
+; CHECK-NEXT: %p_read_provenance = call ptr @read_provenance_only(ptr %p) => ptr 0x11 [p read_provenance]
+; CHECK-NEXT: Entering function: provenance_only
+; CHECK-NEXT: ptr %p = ptr 0x11 [p]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: provenance_only
+; CHECK-NEXT: %p_provenance = call ptr @provenance_only(ptr %p) => ptr 0x11 [p provenance]
+; CHECK-NEXT: Entering function: read_provenance_only
+; CHECK-NEXT: ptr %p = ptr 0x11 [p provenance]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: read_provenance_only
+; CHECK-NEXT: %p_read_provenance_2 = call ptr @read_provenance_only(ptr %p_provenance) => ptr 0x11 [p read_provenance]
+; CHECK-NEXT: Entering function: provenance_only
+; CHECK-NEXT: ptr %p = ptr 0x11 [p address]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: provenance_only
+; CHECK-NEXT: %p_none = call ptr @provenance_only(ptr %p_address_only) => ptr 0x11 [p none]
+; CHECK-NEXT: Entering function: ret_capture_read_provenance_other_capture_address
+; CHECK-NEXT: ptr %p = ptr 0x11 [p]
+; CHECK-NEXT: store ptr %p, ptr @g, align 8
+; CHECK-NEXT: %p2 = load ptr, ptr @g, align 8 => ptr 0x11 [p]
+; CHECK-NEXT: ret ptr %p2
+; CHECK-NEXT: Exiting function: ret_capture_read_provenance_other_capture_address
+; CHECK-NEXT: %p_read_provenance2 = call ptr @ret_capture_read_provenance_other_capture_address(ptr %p) => ptr 0x11 [p read_provenance]
+; CHECK-NEXT: %p_address_only2 = load ptr, ptr @g, align 8 => ptr 0x11 [p address]
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %p_read_provenance, i32 1) ]
+; CHECK-NEXT: %val = load i8, ptr %p_read_provenance, align 1 => i8 1
+; CHECK-NEXT: store i8 0, ptr %p_provenance, align 1
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/dereferenceable_capture_read_provenance.ll b/llvm/test/tools/llubi/dereferenceable_capture_read_provenance.ll
new file mode 100644
index 0000000000000..809756499b8c8
--- /dev/null
+++ b/llvm/test/tools/llubi/dereferenceable_capture_read_provenance.ll
@@ -0,0 +1,24 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define ptr @address_only(ptr captures(address) %p) {
+ ret ptr %p
+}
+
+ at g = global i8 0
+
+define void @main() {
+ %g_address_only = call ptr @address_only(ptr @g)
+ call void @llvm.assume(i1 true) ["dereferenceable"(ptr %g_address_only, i32 1)]
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Entering function: address_only
+; CHECK-NEXT: ptr %p = ptr 0x8 [@g]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: address_only
+; CHECK-NEXT: %g_address_only = call ptr @address_only(ptr @g) => ptr 0x8 [@g address]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %g_address_only, i32 1) ] at @main <stdin>:12
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0x8 [@g address] violates dereferenceable(1) assumption.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/load_capture_read_provenance.ll b/llvm/test/tools/llubi/load_capture_read_provenance.ll
new file mode 100644
index 0000000000000..ae4cf63a2dbb1
--- /dev/null
+++ b/llvm/test/tools/llubi/load_capture_read_provenance.ll
@@ -0,0 +1,24 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define ptr @address_only(ptr captures(address) %p) {
+ ret ptr %p
+}
+
+ at g = global i8 0
+
+define void @main() {
+ %g_address_only = call ptr @address_only(ptr @g)
+ %res = load i8, ptr %g_address_only
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Entering function: address_only
+; CHECK-NEXT: ptr %p = ptr 0x8 [@g]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: address_only
+; CHECK-NEXT: %g_address_only = call ptr @address_only(ptr @g) => ptr 0x8 [@g address]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %res = load i8, ptr %g_address_only, align 1 at @main <stdin>:12
+; 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/store_capture_provenance.ll b/llvm/test/tools/llubi/store_capture_provenance.ll
new file mode 100644
index 0000000000000..a5fd778d7a200
--- /dev/null
+++ b/llvm/test/tools/llubi/store_capture_provenance.ll
@@ -0,0 +1,24 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define ptr @address_read_provenance(ptr captures(address, read_provenance) %p) {
+ ret ptr %p
+}
+
+ at g = global i8 0
+
+define void @main() {
+ %g_address_read_provenance = call ptr @address_read_provenance(ptr @g)
+ store i8 1, ptr %g_address_read_provenance
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Entering function: address_read_provenance
+; CHECK-NEXT: ptr %p = ptr 0x8 [@g]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: address_read_provenance
+; CHECK-NEXT: %g_address_read_provenance = call ptr @address_read_provenance(ptr @g) => ptr 0x8 [@g address, read_provenance]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i8 1, ptr %g_address_read_provenance, align 1 at @main <stdin>:12
+; 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/volatile_capture_address.ll b/llvm/test/tools/llubi/volatile_capture_address.ll
new file mode 100644
index 0000000000000..28a84e1b893b8
--- /dev/null
+++ b/llvm/test/tools/llubi/volatile_capture_address.ll
@@ -0,0 +1,24 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define ptr @provenance_only(ptr captures(provenance) %p) {
+ ret ptr %p
+}
+
+ at g = global i8 0
+
+define void @main() {
+ %g_provenance_only = call ptr @provenance_only(ptr @g)
+ %res = load volatile i8, ptr %g_provenance_only
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Entering function: provenance_only
+; CHECK-NEXT: ptr %p = ptr 0x8 [@g]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: provenance_only
+; CHECK-NEXT: %g_provenance_only = call ptr @provenance_only(ptr @g) => ptr 0x8 [@g provenance]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %res = load volatile i8, ptr %g_provenance_only, align 1 at @main <stdin>:12
+; 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/wildcard_provenance.ll b/llvm/test/tools/llubi/wildcard_provenance.ll
new file mode 100644
index 0000000000000..806c67bb1edd6
--- /dev/null
+++ b/llvm/test/tools/llubi/wildcard_provenance.ll
@@ -0,0 +1,38 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+target datalayout = "e-p:64:64:64"
+
+define ptr @identity(ptr captures(provenance) %p) {
+ ret ptr %p
+}
+
+define void @main() {
+ %p = alloca i32
+ %p_read_provenance_only = call ptr @identity(ptr captures(read_provenance) %p)
+ %p_provenance_only = call ptr @identity(ptr captures(provenance) %p)
+ %addr1 = ptrtoint ptr %p_read_provenance_only to i64
+ %addr2 = ptrtoint ptr %p_provenance_only to i64
+ %p_wildcard = inttoptr i64 %addr1 to ptr
+ store i32 0, ptr %p_wildcard
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p = alloca i32, align 4 => ptr 0x8 [p]
+; CHECK-NEXT: Entering function: identity
+; CHECK-NEXT: ptr %p = ptr 0x8 [p]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: identity
+; CHECK-NEXT: %p_read_provenance_only = call ptr @identity(ptr captures(read_provenance) %p) => ptr 0x8 [p read_provenance]
+; CHECK-NEXT: Entering function: identity
+; CHECK-NEXT: ptr %p = ptr 0x8 [p]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: identity
+; CHECK-NEXT: %p_provenance_only = call ptr @identity(ptr captures(provenance) %p) => ptr 0x8 [p provenance]
+; CHECK-NEXT: %addr1 = ptrtoint ptr %p_read_provenance_only to i64 => i64 8
+; CHECK-NEXT: %addr2 = ptrtoint ptr %p_provenance_only to i64 => i64 8
+; CHECK-NEXT: %p_wildcard = inttoptr i64 %addr1 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT: store i32 0, ptr %p_wildcard, align 4
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/wildcard_provenance_angelic_nondeterminism.ll b/llvm/test/tools/llubi/wildcard_provenance_angelic_nondeterminism.ll
new file mode 100644
index 0000000000000..70c7747b2c2cd
--- /dev/null
+++ b/llvm/test/tools/llubi/wildcard_provenance_angelic_nondeterminism.ll
@@ -0,0 +1,31 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+target datalayout = "e-p:64:64:64"
+
+define void @main() {
+ %p1 = alloca i32
+ %p2 = alloca i32
+ %addr1 = ptrtoint ptr %p1 to i64
+ %addr2 = ptrtoint ptr %p2 to i64
+ %diff = sub i64 %addr2, %addr1
+ %p = inttoptr i64 %addr1 to ptr
+ %p_off = getelementptr i8, ptr %p, i64 %diff
+ store i32 0, ptr %p_off
+ store i32 0, ptr %p
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p1 = alloca i32, align 4 => ptr 0x8 [p1]
+; CHECK-NEXT: %p2 = alloca i32, align 4 => ptr 0x10 [p2]
+; CHECK-NEXT: %addr1 = ptrtoint ptr %p1 to i64 => i64 8
+; CHECK-NEXT: %addr2 = ptrtoint ptr %p2 to i64 => i64 16
+; CHECK-NEXT: %diff = sub i64 %addr2, %addr1 => i64 8
+; CHECK-NEXT: %p = inttoptr i64 %addr1 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT: %p_off = getelementptr i8, ptr %p, i64 %diff => ptr 0x10 [wildcard]
+; CHECK-NEXT: store i32 0, ptr %p_off, align 4
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i32 0, ptr %p, align 4 at @main <stdin>:15
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/wildcard_provenance_gep_inbounds.ll b/llvm/test/tools/llubi/wildcard_provenance_gep_inbounds.ll
new file mode 100644
index 0000000000000..9718b4743e46c
--- /dev/null
+++ b/llvm/test/tools/llubi/wildcard_provenance_gep_inbounds.ll
@@ -0,0 +1,29 @@
+; 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() {
+ %p1 = alloca i32
+ %p2 = alloca i32
+ %addr1 = ptrtoint ptr %p1 to i64
+ %addr2 = ptrtoint ptr %p2 to i64
+ %diff = sub i64 %addr2, %addr1
+ %p = inttoptr i64 %addr1 to ptr
+ %p_off = getelementptr inbounds i8, ptr %p, i64 %diff
+ store i32 0, ptr %p_off
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p1 = alloca i32, align 4 => ptr 0x8 [p1]
+; CHECK-NEXT: %p2 = alloca i32, align 4 => ptr 0x10 [p2]
+; CHECK-NEXT: %addr1 = ptrtoint ptr %p1 to i64 => i64 8
+; CHECK-NEXT: %addr2 = ptrtoint ptr %p2 to i64 => i64 16
+; CHECK-NEXT: %diff = sub i64 %addr2, %addr1 => i64 8
+; CHECK-NEXT: %p = inttoptr i64 %addr1 to ptr => ptr 0x8 [wildcard]
+; CHECK-NEXT: %p_off = getelementptr inbounds i8, ptr %p, i64 %diff => poison
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i32 0, ptr %p_off, align 4 at @main <stdin>:14
+; CHECK-NEXT: Immediate UB detected: Invalid memory access with a poison pointer.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index 0cbb38073f652..392abb2511e88 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -15,9 +15,11 @@
namespace llvm::ubi {
Frame::Frame(Function &F, CallBase *CallSite, Frame *LastFrame,
ArrayRef<AnyValue> Args, AnyValue &RetVal,
- const TargetLibraryInfoImpl &TLIImpl)
+ const TargetLibraryInfoImpl &TLIImpl,
+ CapturedProvenanceList CapturedProvenances)
: Func(F), LastFrame(LastFrame), CallSite(CallSite), Args(Args),
- RetVal(RetVal), TLI(TLIImpl, &F) {
+ RetVal(RetVal), TLI(TLIImpl, &F),
+ CapturedProvenances(std::move(CapturedProvenances)) {
assert((Args.size() == F.arg_size() ||
(F.isVarArg() && Args.size() >= F.arg_size())) &&
"Expected enough arguments to call the function.");
@@ -55,12 +57,19 @@ void ExecutorBase::reportErrorString(StringRef Msg) {
std::pair<MemoryObject *, uint64_t>
ExecutorBase::verifyMemAccess(const Pointer &Ptr, uint64_t AccessSize,
- Align Alignment, bool IsStore) {
- auto *MO = Ctx.checkProvenance(Ptr, [](const Provenance &) {
- // TODO: check provenance
- // TODO: check inrange(S, E)
- return true;
- });
+ Align Alignment, bool IsStore, bool IsVolatile) {
+ auto *MO =
+ Ctx.checkProvenance(Ptr, [IsStore, IsVolatile](const Provenance &Prov) {
+ CaptureComponents CC = Prov.capability();
+ if (IsVolatile &&
+ (CC & CaptureComponents::Address) != CaptureComponents::Address)
+ return false;
+ if (IsStore ? !capturesFullProvenance(CC) : !capturesAnyProvenance(CC))
+ return false;
+
+ // TODO: check inrange(S, E)
+ return true;
+ });
if (!MO) {
reportImmediateUB()
<< "Invalid memory access via a pointer with nullary provenance.";
@@ -116,7 +125,7 @@ ExecutorBase::verifyMemAccess(const Pointer &Ptr, uint64_t AccessSize,
}
AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
- bool NoUndef) {
+ bool NoUndef, bool IsVolatile) {
if (Ptr.isPoison()) {
reportImmediateUB() << "Invalid memory access with a poison pointer.";
return AnyValue::getPoisonValue(Ctx, ValTy);
@@ -124,7 +133,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);
+ /*IsStore=*/false, IsVolatile);
MO) {
bool ContainsUndefinedBits = false;
AnyValue Res = Ctx.load(*MO, Offset, ValTy,
@@ -137,7 +146,7 @@ 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, bool IsVolatile) {
if (Ptr.isPoison()) {
reportImmediateUB() << "Invalid memory access with a poison pointer.";
return;
@@ -145,7 +154,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);
+ /*IsStore=*/true, IsVolatile);
MO)
Ctx.store(*MO, Offset, Val, ValTy);
}
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index ff8025d189615..b556b35180182 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -60,6 +60,10 @@ struct Frame {
SmallVector<IntrusiveRefCntPtr<MemoryObject>> Allocas;
// Values of arguments and executed instructions in this function.
DenseMap<Value *, AnyValue> ValueMap;
+ // The provenances to be updated after the function returns or unwinds.
+ using CapturedProvenanceList =
+ SmallVector<std::pair<IntrusiveRefCntPtr<Provenance>, CaptureInfo>>;
+ CapturedProvenanceList CapturedProvenances;
// Reserved for in-flight subroutines.
Function *ResolvedCallee = nullptr;
@@ -71,7 +75,8 @@ struct Frame {
Frame(Function &F, CallBase *CallSite, Frame *LastFrame,
ArrayRef<AnyValue> Args, AnyValue &RetVal,
- const TargetLibraryInfoImpl &TLIImpl);
+ const TargetLibraryInfoImpl &TLIImpl,
+ CapturedProvenanceList CapturedProvenance);
};
enum class DiagnosticKind {
@@ -104,15 +109,14 @@ 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);
+ std::pair<MemoryObject *, uint64_t>
+ verifyMemAccess(const Pointer &Ptr, uint64_t AccessSize, Align Alignment,
+ bool IsStore, bool IsVolatile);
- AnyValue load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
- bool NoUndef);
+ AnyValue load(const AnyValue &Ptr, Align Alignment, Type *ValTy, bool NoUndef,
+ bool IsVolatile);
void store(const AnyValue &Ptr, Align Alignment, const AnyValue &Val,
- Type *ValTy);
+ Type *ValTy, bool IsVolatile);
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 aa2d903bfb5f1..3d113ef67aea2 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -107,8 +107,10 @@ static bool violatesDereferenceableBytesAttr(const AnyValue &V, uint64_t Bytes,
return true;
}
- auto *MO = Ctx.checkProvenance(Ptr, [&](const Provenance &) {
- // TODO: check read_provenance
+ auto *MO = Ctx.checkProvenance(Ptr, [&](const Provenance &Prov) {
+ CaptureComponents CC = Prov.capability();
+ if (!capturesAnyProvenance(CC))
+ return false;
// TODO: check nofree for attributes/metadata.
return true;
});
@@ -924,7 +926,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
: ExecutorBase(C, H), DL(Ctx.getDataLayout()),
Lib(Ctx, Handler, DL, static_cast<ExecutorBase &>(*this)) {
CallStack.emplace_back(F, /*CallSite=*/nullptr, /*LastFrame=*/nullptr, Args,
- RetVal, Ctx.getTLIImpl());
+ RetVal, Ctx.getTLIImpl(),
+ Frame::CapturedProvenanceList{});
}
void visitReturnInst(ReturnInst &RI) {
@@ -2092,6 +2095,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
// Handle parameter attributes (Attributes from resolved callee should be
// applied if available).
+ Frame::CapturedProvenanceList Captures;
for (auto [I, Arg] : enumerate(CB.args())) {
Type *ArgTy = Arg->getType();
AnyValue &ArgVal = CalleeArgs[I];
@@ -2151,6 +2155,17 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
}
handleAttributes(ArgTy, ArgVal, AttrsAtCallSite, AttrsAtCallee);
+
+ // Handle captures
+ if (ArgVal.isPointer() && !CB.isByValArgument(I)) {
+ CaptureInfo CI =
+ AttrsAtCallSite.getCaptureInfo() & AttrsAtCallee.getCaptureInfo();
+ if (CI != CaptureInfo::all()) {
+ auto NewProv = ArgVal.asPointer().provenance().clone();
+ Captures.emplace_back(NewProv, CI);
+ ArgVal = ArgVal.asPointer().getWithNewProvenance(std::move(NewProv));
+ }
+ }
}
CurrentFrame->ResolvedCallee = Callee;
@@ -2174,7 +2189,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
AnyValue &RetVal = CurrentFrame->CalleeRetVal;
CurrentFrame->State = FrameState::Pending;
CallStack.emplace_back(*Callee, &CB, CurrentFrame, Args, RetVal,
- Ctx.getTLIImpl());
+ Ctx.getTLIImpl(), std::move(Captures));
}
}
@@ -2541,6 +2556,33 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
if (LHS.isPoison() || RHS.isPoison())
return AnyValue::poison();
+ if (LHS.isPointer()) {
+ auto &LHSPtr = LHS.asPointer();
+ auto &RHSPtr = RHS.asPointer();
+ unsigned AS = I.getOperand(0)->getType()->getPointerAddressSpace();
+ // Check provenance
+ bool LHSIsNull = LHSPtr.isNullPtr(AS, DL);
+ bool RHSIsNull = RHSPtr.isNullPtr(AS, DL);
+
+ auto IsValidCompare = [&](const Pointer &Ptr,
+ bool OtherIsNull) -> bool {
+ return Ctx.checkProvenance(
+ Ptr,
+ [&](const Provenance &Prov) {
+ CaptureComponents CC = Prov.capability();
+ CaptureComponents Mask = OtherIsNull
+ ? CaptureComponents::AddressIsNull
+ : CaptureComponents::Address;
+ return (CC & Mask) == Mask;
+ },
+ /*HasSideEffect=*/false);
+ };
+
+ if (!LHSIsNull && !IsValidCompare(LHSPtr, RHSIsNull))
+ return AnyValue::poison();
+ if (!RHSIsNull && !IsValidCompare(RHSPtr, LHSIsNull))
+ return AnyValue::poison();
+ }
const APInt &LHSVal =
LHS.isPointer() ? LHS.asPointer().address() : LHS.asInteger();
const APInt &RHSVal =
@@ -2705,8 +2747,9 @@ 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));
+ auto RetVal =
+ load(getValue(LI.getPointerOperand()), LI.getAlign(), LI.getType(),
+ LI.hasMetadata(LLVMContext::MD_noundef), LI.isVolatile());
// TODO: track volatile loads
handleMetadata(LI.getType(), RetVal, LI);
setResult(LI, std::move(RetVal));
@@ -2717,7 +2760,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.isVolatile());
if (!hasProgramExited() && !Handler.onInstructionExecuted(SI, AnyValue()))
setFailed();
}
@@ -2860,6 +2904,23 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
if (Top.State == FrameState::Exit) {
assert((Top.Func.getReturnType()->isVoidTy() || !Top.RetVal.isNone()) &&
"Expected return value to be set on function exit.");
+ // Apply captures attributes
+ for (auto &[Prov, CI] : Top.CapturedProvenances) {
+ if (Top.RetVal.isPointer()) {
+ auto &PtrVal = Top.RetVal.asPointer();
+ if (&PtrVal.provenance() == Prov.get()) {
+ CaptureComponents Capability = PtrVal.provenance().capability();
+ if ((Capability & CI.getRetComponents()) !=
+ (Capability & CI.getOtherComponents())) {
+ // We have to create a copy of provenance.
+ auto NewProv = Prov->clone();
+ NewProv->captureCapability(CI.getRetComponents());
+ Top.RetVal = PtrVal.getWithNewProvenance(std::move(NewProv));
+ }
+ }
+ }
+ Prov->captureCapability(CI.getOtherComponents());
+ }
Handler.onFunctionExit(Top.Func, Top.RetVal);
// Free stack objects allocated in this frame.
for (auto &Obj : Top.Allocas)
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index c3452b8af92c0..d224b2e02041b 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -35,7 +35,8 @@ std::optional<std::string> Library::readStringFromMemory(const Pointer &Ptr) {
while (true) {
auto [MO, ValidOffset] = Executor.verifyMemAccess(
- Ptr.getWithNewAddr(Address + Offset), 1, Align(1), /*IsStore=*/false);
+ Ptr.getWithNewAddr(Address + Offset), 1, Align(1), /*IsStore=*/false,
+ /*IsVolatile=*/false);
if (!MO)
return std::nullopt;
@@ -278,7 +279,8 @@ 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()),
+ /*IsVolatile=*/false);
break;
}
case 'p': {
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 0993441abf22d..8a9a46a24759c 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -47,7 +47,9 @@ void Pointer::print(raw_ostream &OS) const {
} else {
OS << (Prov->isWildcard() ? "wildcard" : "nullary");
}
- // TODO: print provenance
+ CaptureComponents CC = Prov->capability();
+ if (CC != CaptureComponents::All)
+ OS << ' ' << CC;
OS << "]";
}
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index b0f90aab2f1b5..bcbe332f92220 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -14,6 +14,7 @@
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Type.h"
+#include "llvm/Support/ModRef.h"
#include "llvm/Support/raw_ostream.h"
namespace llvm::ubi {
@@ -199,8 +200,9 @@ class Provenance : public RefCountedBase<Provenance> {
// Null if it is concrete.
IntrusiveRefCntPtr<WildcardProvenance> Wildcard;
+ CaptureComponents Capability;
+
// TODO: modeling nofree
- // TODO: modeling captures
// TODO: modeling inrange(Start, End) attribute
const APInt &getTag() const { return Tag; }
@@ -209,10 +211,22 @@ class Provenance : public RefCountedBase<Provenance> {
friend class Context;
public:
- Provenance(IntrusiveRefCntPtr<MemoryObject> Obj) : Obj(std::move(Obj)) {}
+ Provenance(IntrusiveRefCntPtr<MemoryObject> Obj,
+ CaptureComponents Capability = CaptureComponents::All)
+ : Obj(std::move(Obj)), Capability(Capability) {}
static IntrusiveRefCntPtr<Provenance> nullary();
+ IntrusiveRefCntPtr<Provenance> clone() const {
+ IntrusiveRefCntPtr<Provenance> Res =
+ makeIntrusiveRefCnt<Provenance>(Obj, Capability);
+ Res->Wildcard = Wildcard;
+ return Res;
+ }
+ void captureCapability(CaptureComponents CapturedMask) {
+ Capability &= CapturedMask;
+ }
IntrusiveRefCntPtr<Provenance> getWithKnownMemoryObject(MemoryObject &Obj);
MemoryObject *getMemoryObject() const { return Obj.get(); }
+ CaptureComponents capability() const { return Capability; }
bool isWildcard() const { return Wildcard != nullptr; }
};
>From 01d29fa9fc1cf0865076752132d434ca07ed4b16 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 30 Jul 2026 21:38:28 +0800
Subject: [PATCH 2/4] [llubi] Remove address capturing support
---
llvm/test/tools/llubi/captures.ll | 22 ++++--------
...dereferenceable_capture_read_provenance.ll | 4 +--
.../llubi/load_capture_read_provenance.ll | 2 +-
.../tools/llubi/store_capture_provenance.ll | 2 +-
.../tools/llubi/volatile_capture_address.ll | 24 -------------
llvm/test/tools/llubi/wildcard_provenance.ll | 2 +-
llvm/tools/llubi/lib/ExecutorBase.cpp | 30 +++++++---------
llvm/tools/llubi/lib/ExecutorBase.h | 13 +++----
llvm/tools/llubi/lib/Interpreter.cpp | 35 ++-----------------
llvm/tools/llubi/lib/Library.cpp | 6 ++--
llvm/tools/llubi/lib/Value.cpp | 2 +-
llvm/tools/llubi/lib/Value.h | 4 ++-
12 files changed, 40 insertions(+), 106 deletions(-)
delete mode 100644 llvm/test/tools/llubi/volatile_capture_address.ll
diff --git a/llvm/test/tools/llubi/captures.ll b/llvm/test/tools/llubi/captures.ll
index 4ac808c4248aa..c1ea5e46ccdb3 100644
--- a/llvm/test/tools/llubi/captures.ll
+++ b/llvm/test/tools/llubi/captures.ll
@@ -36,11 +36,6 @@ define void @main() {
%p_poison = call ptr @address_only(ptr poison)
%p_address_only = call ptr @address_only(ptr %p)
%p_address_is_null_only = call ptr @address_is_null_only(ptr %p)
- %cmp1 = icmp eq ptr %p_address_only, %p_address_only
- %cmp2 = icmp eq ptr %p_address_is_null_only, %p_address_is_null_only
- %cmp3 = icmp eq ptr %p_address_is_null_only, null
- %cmp4 = icmp eq ptr null, %p_address_is_null_only
- %cmp5 = icmp eq ptr null, null
%p_read_provenance = call ptr @read_provenance_only(ptr %p)
%p_provenance = call ptr @provenance_only(ptr %p)
@@ -74,17 +69,12 @@ define void @main() {
; CHECK-NEXT: ptr %p = ptr 0x11 [p]
; CHECK-NEXT: ret ptr %p
; CHECK-NEXT: Exiting function: address_only
-; CHECK-NEXT: %p_address_only = call ptr @address_only(ptr %p) => ptr 0x11 [p address]
+; CHECK-NEXT: %p_address_only = call ptr @address_only(ptr %p) => ptr 0x11 [p none]
; CHECK-NEXT: Entering function: address_is_null_only
; CHECK-NEXT: ptr %p = ptr 0x11 [p]
; CHECK-NEXT: ret ptr %p
; CHECK-NEXT: Exiting function: address_is_null_only
-; CHECK-NEXT: %p_address_is_null_only = call ptr @address_is_null_only(ptr %p) => ptr 0x11 [p address_is_null]
-; CHECK-NEXT: %cmp1 = icmp eq ptr %p_address_only, %p_address_only => T
-; CHECK-NEXT: %cmp2 = icmp eq ptr %p_address_is_null_only, %p_address_is_null_only => poison
-; CHECK-NEXT: %cmp3 = icmp eq ptr %p_address_is_null_only, null => F
-; CHECK-NEXT: %cmp4 = icmp eq ptr null, %p_address_is_null_only => F
-; CHECK-NEXT: %cmp5 = icmp eq ptr null, null => T
+; CHECK-NEXT: %p_address_is_null_only = call ptr @address_is_null_only(ptr %p) => ptr 0x11 [p none]
; CHECK-NEXT: Entering function: read_provenance_only
; CHECK-NEXT: ptr %p = ptr 0x11 [p]
; CHECK-NEXT: ret ptr %p
@@ -94,14 +84,14 @@ define void @main() {
; CHECK-NEXT: ptr %p = ptr 0x11 [p]
; CHECK-NEXT: ret ptr %p
; CHECK-NEXT: Exiting function: provenance_only
-; CHECK-NEXT: %p_provenance = call ptr @provenance_only(ptr %p) => ptr 0x11 [p provenance]
+; CHECK-NEXT: %p_provenance = call ptr @provenance_only(ptr %p) => ptr 0x11 [p]
; CHECK-NEXT: Entering function: read_provenance_only
-; CHECK-NEXT: ptr %p = ptr 0x11 [p provenance]
+; CHECK-NEXT: ptr %p = ptr 0x11 [p]
; CHECK-NEXT: ret ptr %p
; CHECK-NEXT: Exiting function: read_provenance_only
; CHECK-NEXT: %p_read_provenance_2 = call ptr @read_provenance_only(ptr %p_provenance) => ptr 0x11 [p read_provenance]
; CHECK-NEXT: Entering function: provenance_only
-; CHECK-NEXT: ptr %p = ptr 0x11 [p address]
+; CHECK-NEXT: ptr %p = ptr 0x11 [p none]
; CHECK-NEXT: ret ptr %p
; CHECK-NEXT: Exiting function: provenance_only
; CHECK-NEXT: %p_none = call ptr @provenance_only(ptr %p_address_only) => ptr 0x11 [p none]
@@ -112,7 +102,7 @@ define void @main() {
; CHECK-NEXT: ret ptr %p2
; CHECK-NEXT: Exiting function: ret_capture_read_provenance_other_capture_address
; CHECK-NEXT: %p_read_provenance2 = call ptr @ret_capture_read_provenance_other_capture_address(ptr %p) => ptr 0x11 [p read_provenance]
-; CHECK-NEXT: %p_address_only2 = load ptr, ptr @g, align 8 => ptr 0x11 [p address]
+; CHECK-NEXT: %p_address_only2 = load ptr, ptr @g, align 8 => ptr 0x11 [p none]
; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %p_read_provenance, i32 1) ]
; CHECK-NEXT: %val = load i8, ptr %p_read_provenance, align 1 => i8 1
; CHECK-NEXT: store i8 0, ptr %p_provenance, align 1
diff --git a/llvm/test/tools/llubi/dereferenceable_capture_read_provenance.ll b/llvm/test/tools/llubi/dereferenceable_capture_read_provenance.ll
index 809756499b8c8..f859d86fe0a93 100644
--- a/llvm/test/tools/llubi/dereferenceable_capture_read_provenance.ll
+++ b/llvm/test/tools/llubi/dereferenceable_capture_read_provenance.ll
@@ -17,8 +17,8 @@ define void @main() {
; CHECK-NEXT: ptr %p = ptr 0x8 [@g]
; CHECK-NEXT: ret ptr %p
; CHECK-NEXT: Exiting function: address_only
-; CHECK-NEXT: %g_address_only = call ptr @address_only(ptr @g) => ptr 0x8 [@g address]
+; CHECK-NEXT: %g_address_only = call ptr @address_only(ptr @g) => ptr 0x8 [@g none]
; CHECK-NEXT: Stacktrace:
; CHECK-NEXT: #0 call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %g_address_only, i32 1) ] at @main <stdin>:12
-; CHECK-NEXT: Immediate UB detected: The pointer ptr 0x8 [@g address] violates dereferenceable(1) assumption.
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0x8 [@g none] violates dereferenceable(1) assumption.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/load_capture_read_provenance.ll b/llvm/test/tools/llubi/load_capture_read_provenance.ll
index ae4cf63a2dbb1..a06f6c0098d32 100644
--- a/llvm/test/tools/llubi/load_capture_read_provenance.ll
+++ b/llvm/test/tools/llubi/load_capture_read_provenance.ll
@@ -17,7 +17,7 @@ define void @main() {
; CHECK-NEXT: ptr %p = ptr 0x8 [@g]
; CHECK-NEXT: ret ptr %p
; CHECK-NEXT: Exiting function: address_only
-; CHECK-NEXT: %g_address_only = call ptr @address_only(ptr @g) => ptr 0x8 [@g address]
+; CHECK-NEXT: %g_address_only = call ptr @address_only(ptr @g) => ptr 0x8 [@g none]
; CHECK-NEXT: Stacktrace:
; CHECK-NEXT: #0 %res = load i8, ptr %g_address_only, align 1 at @main <stdin>:12
; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
diff --git a/llvm/test/tools/llubi/store_capture_provenance.ll b/llvm/test/tools/llubi/store_capture_provenance.ll
index a5fd778d7a200..920742c9fe5ff 100644
--- a/llvm/test/tools/llubi/store_capture_provenance.ll
+++ b/llvm/test/tools/llubi/store_capture_provenance.ll
@@ -17,7 +17,7 @@ define void @main() {
; CHECK-NEXT: ptr %p = ptr 0x8 [@g]
; CHECK-NEXT: ret ptr %p
; CHECK-NEXT: Exiting function: address_read_provenance
-; CHECK-NEXT: %g_address_read_provenance = call ptr @address_read_provenance(ptr @g) => ptr 0x8 [@g address, read_provenance]
+; CHECK-NEXT: %g_address_read_provenance = call ptr @address_read_provenance(ptr @g) => ptr 0x8 [@g read_provenance]
; CHECK-NEXT: Stacktrace:
; CHECK-NEXT: #0 store i8 1, ptr %g_address_read_provenance, align 1 at @main <stdin>:12
; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
diff --git a/llvm/test/tools/llubi/volatile_capture_address.ll b/llvm/test/tools/llubi/volatile_capture_address.ll
deleted file mode 100644
index 28a84e1b893b8..0000000000000
--- a/llvm/test/tools/llubi/volatile_capture_address.ll
+++ /dev/null
@@ -1,24 +0,0 @@
-; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
-; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
-
-define ptr @provenance_only(ptr captures(provenance) %p) {
- ret ptr %p
-}
-
- at g = global i8 0
-
-define void @main() {
- %g_provenance_only = call ptr @provenance_only(ptr @g)
- %res = load volatile i8, ptr %g_provenance_only
- ret void
-}
-; CHECK: Entering function: main
-; CHECK-NEXT: Entering function: provenance_only
-; CHECK-NEXT: ptr %p = ptr 0x8 [@g]
-; CHECK-NEXT: ret ptr %p
-; CHECK-NEXT: Exiting function: provenance_only
-; CHECK-NEXT: %g_provenance_only = call ptr @provenance_only(ptr @g) => ptr 0x8 [@g provenance]
-; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0 %res = load volatile i8, ptr %g_provenance_only, align 1 at @main <stdin>:12
-; 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/wildcard_provenance.ll b/llvm/test/tools/llubi/wildcard_provenance.ll
index 806c67bb1edd6..dbeec0cd59d40 100644
--- a/llvm/test/tools/llubi/wildcard_provenance.ll
+++ b/llvm/test/tools/llubi/wildcard_provenance.ll
@@ -29,7 +29,7 @@ define void @main() {
; CHECK-NEXT: ptr %p = ptr 0x8 [p]
; CHECK-NEXT: ret ptr %p
; CHECK-NEXT: Exiting function: identity
-; CHECK-NEXT: %p_provenance_only = call ptr @identity(ptr captures(provenance) %p) => ptr 0x8 [p provenance]
+; CHECK-NEXT: %p_provenance_only = call ptr @identity(ptr captures(provenance) %p) => ptr 0x8 [p]
; CHECK-NEXT: %addr1 = ptrtoint ptr %p_read_provenance_only to i64 => i64 8
; CHECK-NEXT: %addr2 = ptrtoint ptr %p_provenance_only to i64 => i64 8
; CHECK-NEXT: %p_wildcard = inttoptr i64 %addr1 to ptr => ptr 0x8 [wildcard]
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index 392abb2511e88..0ba34f0f11621 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -57,19 +57,15 @@ void ExecutorBase::reportErrorString(StringRef Msg) {
std::pair<MemoryObject *, uint64_t>
ExecutorBase::verifyMemAccess(const Pointer &Ptr, uint64_t AccessSize,
- Align Alignment, bool IsStore, bool IsVolatile) {
- auto *MO =
- Ctx.checkProvenance(Ptr, [IsStore, IsVolatile](const Provenance &Prov) {
- CaptureComponents CC = Prov.capability();
- if (IsVolatile &&
- (CC & CaptureComponents::Address) != CaptureComponents::Address)
- return false;
- if (IsStore ? !capturesFullProvenance(CC) : !capturesAnyProvenance(CC))
- return false;
-
- // TODO: check inrange(S, E)
- return true;
- });
+ Align Alignment, bool IsStore) {
+ auto *MO = Ctx.checkProvenance(Ptr, [IsStore](const Provenance &Prov) {
+ CaptureComponents CC = Prov.capability();
+ if (IsStore ? !capturesFullProvenance(CC) : !capturesAnyProvenance(CC))
+ return false;
+
+ // TODO: check inrange(S, E)
+ return true;
+ });
if (!MO) {
reportImmediateUB()
<< "Invalid memory access via a pointer with nullary provenance.";
@@ -125,7 +121,7 @@ ExecutorBase::verifyMemAccess(const Pointer &Ptr, uint64_t AccessSize,
}
AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
- bool NoUndef, bool IsVolatile) {
+ bool NoUndef) {
if (Ptr.isPoison()) {
reportImmediateUB() << "Invalid memory access with a poison pointer.";
return AnyValue::getPoisonValue(Ctx, ValTy);
@@ -133,7 +129,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, IsVolatile);
+ /*IsStore=*/false);
MO) {
bool ContainsUndefinedBits = false;
AnyValue Res = Ctx.load(*MO, Offset, ValTy,
@@ -146,7 +142,7 @@ AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
}
void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
- const AnyValue &Val, Type *ValTy, bool IsVolatile) {
+ const AnyValue &Val, Type *ValTy) {
if (Ptr.isPoison()) {
reportImmediateUB() << "Invalid memory access with a poison pointer.";
return;
@@ -154,7 +150,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, IsVolatile);
+ /*IsStore=*/true);
MO)
Ctx.store(*MO, Offset, Val, ValTy);
}
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index b556b35180182..07591bb4bfc1f 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -109,14 +109,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, bool IsVolatile);
+ 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,
- bool IsVolatile);
+ AnyValue load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
+ bool NoUndef);
void store(const AnyValue &Ptr, Align Alignment, const AnyValue &Val,
- Type *ValTy, bool IsVolatile);
+ 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 3d113ef67aea2..07adb88a66071 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -2556,33 +2556,6 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
if (LHS.isPoison() || RHS.isPoison())
return AnyValue::poison();
- if (LHS.isPointer()) {
- auto &LHSPtr = LHS.asPointer();
- auto &RHSPtr = RHS.asPointer();
- unsigned AS = I.getOperand(0)->getType()->getPointerAddressSpace();
- // Check provenance
- bool LHSIsNull = LHSPtr.isNullPtr(AS, DL);
- bool RHSIsNull = RHSPtr.isNullPtr(AS, DL);
-
- auto IsValidCompare = [&](const Pointer &Ptr,
- bool OtherIsNull) -> bool {
- return Ctx.checkProvenance(
- Ptr,
- [&](const Provenance &Prov) {
- CaptureComponents CC = Prov.capability();
- CaptureComponents Mask = OtherIsNull
- ? CaptureComponents::AddressIsNull
- : CaptureComponents::Address;
- return (CC & Mask) == Mask;
- },
- /*HasSideEffect=*/false);
- };
-
- if (!LHSIsNull && !IsValidCompare(LHSPtr, RHSIsNull))
- return AnyValue::poison();
- if (!RHSIsNull && !IsValidCompare(RHSPtr, LHSIsNull))
- return AnyValue::poison();
- }
const APInt &LHSVal =
LHS.isPointer() ? LHS.asPointer().address() : LHS.asInteger();
const APInt &RHSVal =
@@ -2747,9 +2720,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.isVolatile());
+ auto RetVal = load(getValue(LI.getPointerOperand()), LI.getAlign(),
+ LI.getType(), LI.hasMetadata(LLVMContext::MD_noundef));
// TODO: track volatile loads
handleMetadata(LI.getType(), RetVal, LI);
setResult(LI, std::move(RetVal));
@@ -2760,8 +2732,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.isVolatile());
+ 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 d224b2e02041b..c3452b8af92c0 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -35,8 +35,7 @@ std::optional<std::string> Library::readStringFromMemory(const Pointer &Ptr) {
while (true) {
auto [MO, ValidOffset] = Executor.verifyMemAccess(
- Ptr.getWithNewAddr(Address + Offset), 1, Align(1), /*IsStore=*/false,
- /*IsVolatile=*/false);
+ Ptr.getWithNewAddr(Address + Offset), 1, Align(1), /*IsStore=*/false);
if (!MO)
return std::nullopt;
@@ -279,8 +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()),
- /*IsVolatile=*/false);
+ Type::getInt32Ty(Ctx.getContext()));
break;
}
case 'p': {
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index 8a9a46a24759c..0ea36d9536bfe 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -48,7 +48,7 @@ void Pointer::print(raw_ostream &OS) const {
OS << (Prov->isWildcard() ? "wildcard" : "nullary");
}
CaptureComponents CC = Prov->capability();
- if (CC != CaptureComponents::All)
+ if (CC != CaptureComponents::Provenance)
OS << ' ' << CC;
OS << "]";
}
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index bcbe332f92220..79fdca177ab29 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -200,6 +200,8 @@ class Provenance : public RefCountedBase<Provenance> {
// Null if it is concrete.
IntrusiveRefCntPtr<WildcardProvenance> Wildcard;
+ // Controls whether we can read/write to the memory object via this
+ // provenance. Address and AddressIsNull is not supported.
CaptureComponents Capability;
// TODO: modeling nofree
@@ -212,7 +214,7 @@ class Provenance : public RefCountedBase<Provenance> {
public:
Provenance(IntrusiveRefCntPtr<MemoryObject> Obj,
- CaptureComponents Capability = CaptureComponents::All)
+ CaptureComponents Capability = CaptureComponents::Provenance)
: Obj(std::move(Obj)), Capability(Capability) {}
static IntrusiveRefCntPtr<Provenance> nullary();
IntrusiveRefCntPtr<Provenance> clone() const {
>From 1b08cc8776551043aedd7250a6009a2cb60827ab Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 30 Jul 2026 22:28:16 +0800
Subject: [PATCH 3/4] [llubi] Add more tests.
---
llvm/test/tools/llubi/captures_mixed1.ll | 44 ++++++++++++++++
llvm/test/tools/llubi/captures_mixed2.ll | 45 +++++++++++++++++
llvm/test/tools/llubi/captures_mixed3.ll | 45 +++++++++++++++++
llvm/test/tools/llubi/captures_mixed4.ll | 51 +++++++++++++++++++
llvm/test/tools/llubi/captures_mixed5.ll | 50 +++++++++++++++++++
llvm/test/tools/llubi/captures_mixed6.ll | 61 +++++++++++++++++++++++
llvm/test/tools/llubi/captures_nested1.ll | 39 +++++++++++++++
llvm/test/tools/llubi/captures_nested2.ll | 45 +++++++++++++++++
8 files changed, 380 insertions(+)
create mode 100644 llvm/test/tools/llubi/captures_mixed1.ll
create mode 100644 llvm/test/tools/llubi/captures_mixed2.ll
create mode 100644 llvm/test/tools/llubi/captures_mixed3.ll
create mode 100644 llvm/test/tools/llubi/captures_mixed4.ll
create mode 100644 llvm/test/tools/llubi/captures_mixed5.ll
create mode 100644 llvm/test/tools/llubi/captures_mixed6.ll
create mode 100644 llvm/test/tools/llubi/captures_nested1.ll
create mode 100644 llvm/test/tools/llubi/captures_nested2.ll
diff --git a/llvm/test/tools/llubi/captures_mixed1.ll b/llvm/test/tools/llubi/captures_mixed1.ll
new file mode 100644
index 0000000000000..48a95325beab2
--- /dev/null
+++ b/llvm/test/tools/llubi/captures_mixed1.ll
@@ -0,0 +1,44 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+target datalayout = "e-p:64:64:64"
+
+define ptr @mix_pointer(ptr %pa, ptr %pb) {
+ %ba = bitcast ptr %pa to <2 x b32>
+ %bb = bitcast ptr %pb to <2 x b32>
+ %bc = shufflevector <2 x b32> %ba, <2 x b32> %bb, <2 x i32> <i32 0, i32 3>
+ %pc = bitcast <2 x b32> %bc to ptr
+ ret ptr %pc
+}
+
+define ptr @identity(ptr %a) {
+ ret ptr %a
+}
+
+define void @main() {
+ %p1 = alloca i32
+ %p2 = call ptr @identity(ptr %p1)
+ %mixed = call ptr @mix_pointer(ptr %p1, ptr %p2)
+ store i32 0, ptr %mixed
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p1 = alloca i32, align 4 => ptr 0x8 [p1]
+; CHECK-NEXT: Entering function: identity
+; CHECK-NEXT: ptr %a = ptr 0x8 [p1]
+; CHECK-NEXT: ret ptr %a
+; CHECK-NEXT: Exiting function: identity
+; CHECK-NEXT: %p2 = call ptr @identity(ptr %p1) => ptr 0x8 [p1]
+; CHECK-NEXT: Entering function: mix_pointer
+; CHECK-NEXT: ptr %pa = ptr 0x8 [p1]
+; CHECK-NEXT: ptr %pb = ptr 0x8 [p1]
+; CHECK-NEXT: %ba = bitcast ptr %pa to <2 x b32> => { b32 00001000(00111110) 00000000(11011100) 00000000(01000001) 00000000(11001011) , b32 00000000(11000101) 00000000(00110111) 00000000(11101000) 00000000(00101000) }
+; CHECK-NEXT: %bb = bitcast ptr %pb to <2 x b32> => { b32 00001000(00111110) 00000000(11011100) 00000000(01000001) 00000000(11001011) , b32 00000000(11000101) 00000000(00110111) 00000000(11101000) 00000000(00101000) }
+; CHECK-NEXT: %bc = shufflevector <2 x b32> %ba, <2 x b32> %bb, <2 x i32> <i32 0, i32 3> => { b32 00001000(00111110) 00000000(11011100) 00000000(01000001) 00000000(11001011) , b32 00000000(11000101) 00000000(00110111) 00000000(11101000) 00000000(00101000) }
+; CHECK-NEXT: %pc = bitcast <2 x b32> %bc to ptr => ptr 0x8 [p1]
+; CHECK-NEXT: ret ptr %pc
+; CHECK-NEXT: Exiting function: mix_pointer
+; CHECK-NEXT: %mixed = call ptr @mix_pointer(ptr %p1, ptr %p2) => ptr 0x8 [p1]
+; CHECK-NEXT: store i32 0, ptr %mixed, align 4
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/captures_mixed2.ll b/llvm/test/tools/llubi/captures_mixed2.ll
new file mode 100644
index 0000000000000..fc8bf1c903ad1
--- /dev/null
+++ b/llvm/test/tools/llubi/captures_mixed2.ll
@@ -0,0 +1,45 @@
+; 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 ptr @mix_pointer(ptr %pa, ptr %pb) {
+ %ba = bitcast ptr %pa to <2 x b32>
+ %bb = bitcast ptr %pb to <2 x b32>
+ %bc = shufflevector <2 x b32> %ba, <2 x b32> %bb, <2 x i32> <i32 0, i32 3>
+ %pc = bitcast <2 x b32> %bc to ptr
+ ret ptr %pc
+}
+
+define ptr @identity(ptr captures(ret: address, provenance) %a) {
+ ret ptr %a
+}
+
+define void @main() {
+ %p1 = alloca i32
+ %p2 = call ptr @identity(ptr %p1)
+ %mixed = call ptr @mix_pointer(ptr %p1, ptr %p2)
+ store i32 0, ptr %mixed
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p1 = alloca i32, align 4 => ptr 0x8 [p1]
+; CHECK-NEXT: Entering function: identity
+; CHECK-NEXT: ptr %a = ptr 0x8 [p1]
+; CHECK-NEXT: ret ptr %a
+; CHECK-NEXT: Exiting function: identity
+; CHECK-NEXT: %p2 = call ptr @identity(ptr %p1) => ptr 0x8 [p1]
+; CHECK-NEXT: Entering function: mix_pointer
+; CHECK-NEXT: ptr %pa = ptr 0x8 [p1]
+; CHECK-NEXT: ptr %pb = ptr 0x8 [p1]
+; CHECK-NEXT: %ba = bitcast ptr %pa to <2 x b32> => { b32 00001000(00111110) 00000000(11011100) 00000000(01000001) 00000000(11001011) , b32 00000000(11000101) 00000000(00110111) 00000000(11101000) 00000000(00101000) }
+; CHECK-NEXT: %bb = bitcast ptr %pb to <2 x b32> => { b32 00001000(10001011) 00000000(11111001) 00000000(01000000) 00000000(00111110) , b32 00000000(01111100) 00000000(00111010) 00000000(11111101) 00000000(11111101) }
+; CHECK-NEXT: %bc = shufflevector <2 x b32> %ba, <2 x b32> %bb, <2 x i32> <i32 0, i32 3> => { b32 00001000(00111110) 00000000(11011100) 00000000(01000001) 00000000(11001011) , b32 00000000(01111100) 00000000(00111010) 00000000(11111101) 00000000(11111101) }
+; CHECK-NEXT: %pc = bitcast <2 x b32> %bc to ptr => ptr 0x8 [nullary]
+; CHECK-NEXT: ret ptr %pc
+; CHECK-NEXT: Exiting function: mix_pointer
+; CHECK-NEXT: %mixed = call ptr @mix_pointer(ptr %p1, ptr %p2) => ptr 0x8 [nullary]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i32 0, ptr %mixed, align 4 at @main <stdin>:22
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/captures_mixed3.ll b/llvm/test/tools/llubi/captures_mixed3.ll
new file mode 100644
index 0000000000000..03526d926a577
--- /dev/null
+++ b/llvm/test/tools/llubi/captures_mixed3.ll
@@ -0,0 +1,45 @@
+; 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 ptr @mix_pointer(ptr %pa, ptr %pb) {
+ %ba = bitcast ptr %pa to <2 x b32>
+ %bb = bitcast ptr %pb to <2 x b32>
+ %bc = shufflevector <2 x b32> %ba, <2 x b32> %bb, <2 x i32> <i32 0, i32 3>
+ %pc = bitcast <2 x b32> %bc to ptr
+ ret ptr %pc
+}
+
+define ptr @identity(ptr captures(read_provenance) %a) {
+ ret ptr %a
+}
+
+define void @main() {
+ %p1 = alloca i32
+ %p2 = call ptr @identity(ptr %p1)
+ %mixed = call ptr @mix_pointer(ptr %p1, ptr %p2)
+ store i32 0, ptr %mixed
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p1 = alloca i32, align 4 => ptr 0x8 [p1]
+; CHECK-NEXT: Entering function: identity
+; CHECK-NEXT: ptr %a = ptr 0x8 [p1]
+; CHECK-NEXT: ret ptr %a
+; CHECK-NEXT: Exiting function: identity
+; CHECK-NEXT: %p2 = call ptr @identity(ptr %p1) => ptr 0x8 [p1 read_provenance]
+; CHECK-NEXT: Entering function: mix_pointer
+; CHECK-NEXT: ptr %pa = ptr 0x8 [p1]
+; CHECK-NEXT: ptr %pb = ptr 0x8 [p1 read_provenance]
+; CHECK-NEXT: %ba = bitcast ptr %pa to <2 x b32> => { b32 00001000(00111110) 00000000(11011100) 00000000(01000001) 00000000(11001011) , b32 00000000(11000101) 00000000(00110111) 00000000(11101000) 00000000(00101000) }
+; CHECK-NEXT: %bb = bitcast ptr %pb to <2 x b32> => { b32 00001000(10001011) 00000000(11111001) 00000000(01000000) 00000000(00111110) , b32 00000000(01111100) 00000000(00111010) 00000000(11111101) 00000000(11111101) }
+; CHECK-NEXT: %bc = shufflevector <2 x b32> %ba, <2 x b32> %bb, <2 x i32> <i32 0, i32 3> => { b32 00001000(00111110) 00000000(11011100) 00000000(01000001) 00000000(11001011) , b32 00000000(01111100) 00000000(00111010) 00000000(11111101) 00000000(11111101) }
+; CHECK-NEXT: %pc = bitcast <2 x b32> %bc to ptr => ptr 0x8 [nullary]
+; CHECK-NEXT: ret ptr %pc
+; CHECK-NEXT: Exiting function: mix_pointer
+; CHECK-NEXT: %mixed = call ptr @mix_pointer(ptr %p1, ptr %p2) => ptr 0x8 [nullary]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i32 0, ptr %mixed, align 4 at @main <stdin>:22
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/captures_mixed4.ll b/llvm/test/tools/llubi/captures_mixed4.ll
new file mode 100644
index 0000000000000..da2e331b46218
--- /dev/null
+++ b/llvm/test/tools/llubi/captures_mixed4.ll
@@ -0,0 +1,51 @@
+; 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 ptr @mix_pointer(ptr %pa, ptr %pb) {
+ %ba = bitcast ptr %pa to <2 x b32>
+ %bb = bitcast ptr %pb to <2 x b32>
+ %bc = shufflevector <2 x b32> %ba, <2 x b32> %bb, <2 x i32> <i32 0, i32 3>
+ %pc = bitcast <2 x b32> %bc to ptr
+ ret ptr %pc
+}
+
+ at g = global ptr null
+
+define void @captured_by_mem(ptr captures(provenance) %a) {
+ store ptr %a, ptr @g
+ ret void
+}
+
+define void @main() {
+ %p1 = alloca i32
+ call void @captured_by_mem(ptr %p1)
+ %p2 = load ptr, ptr @g
+ %mixed = call ptr @mix_pointer(ptr %p1, ptr %p2)
+ store i32 0, ptr %mixed
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p1 = alloca i32, align 4 => ptr 0x14 [p1]
+; CHECK-NEXT: Entering function: captured_by_mem
+; CHECK-NEXT: ptr %a = ptr 0x14 [p1]
+; CHECK-NEXT: store ptr %a, ptr @g, align 8
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: captured_by_mem
+; CHECK-NEXT: call void @captured_by_mem(ptr %p1)
+; CHECK-NEXT: %p2 = load ptr, ptr @g, align 8 => ptr 0x14 [p1]
+; CHECK-NEXT: Entering function: mix_pointer
+; CHECK-NEXT: ptr %pa = ptr 0x14 [p1]
+; CHECK-NEXT: ptr %pb = ptr 0x14 [p1]
+; CHECK-NEXT: %ba = bitcast ptr %pa to <2 x b32> => { b32 00010100(10001011) 00000000(11111001) 00000000(01000000) 00000000(00111110) , b32 00000000(01111100) 00000000(00111010) 00000000(11111101) 00000000(11111101) }
+; CHECK-NEXT: %bb = bitcast ptr %pb to <2 x b32> => { b32 00010100(00111110) 00000000(11011100) 00000000(01000001) 00000000(11001011) , b32 00000000(11000101) 00000000(00110111) 00000000(11101000) 00000000(00101000) }
+; CHECK-NEXT: %bc = shufflevector <2 x b32> %ba, <2 x b32> %bb, <2 x i32> <i32 0, i32 3> => { b32 00010100(10001011) 00000000(11111001) 00000000(01000000) 00000000(00111110) , b32 00000000(11000101) 00000000(00110111) 00000000(11101000) 00000000(00101000) }
+; CHECK-NEXT: %pc = bitcast <2 x b32> %bc to ptr => ptr 0x14 [nullary]
+; CHECK-NEXT: ret ptr %pc
+; CHECK-NEXT: Exiting function: mix_pointer
+; CHECK-NEXT: %mixed = call ptr @mix_pointer(ptr %p1, ptr %p2) => ptr 0x14 [nullary]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i32 0, ptr %mixed, align 4 at @main <stdin>:26
+; 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/captures_mixed5.ll b/llvm/test/tools/llubi/captures_mixed5.ll
new file mode 100644
index 0000000000000..e73c1af9bea96
--- /dev/null
+++ b/llvm/test/tools/llubi/captures_mixed5.ll
@@ -0,0 +1,50 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+target datalayout = "e-p:64:64:64"
+
+define ptr @mix_pointer(ptr %pa, ptr %pb) {
+ %ba = bitcast ptr %pa to <2 x b32>
+ %bb = bitcast ptr %pb to <2 x b32>
+ %bc = shufflevector <2 x b32> %ba, <2 x b32> %bb, <2 x i32> <i32 0, i32 3>
+ %pc = bitcast <2 x b32> %bc to ptr
+ ret ptr %pc
+}
+
+ at g = global ptr null
+
+define void @captured_by_mem(ptr captures(address, provenance) %a) {
+ store ptr %a, ptr @g
+ ret void
+}
+
+define void @main() {
+ %p1 = alloca i32
+ call void @captured_by_mem(ptr %p1)
+ %p2 = load ptr, ptr @g
+ %mixed = call ptr @mix_pointer(ptr %p1, ptr %p2)
+ store i32 0, ptr %mixed
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p1 = alloca i32, align 4 => ptr 0x14 [p1]
+; CHECK-NEXT: Entering function: captured_by_mem
+; CHECK-NEXT: ptr %a = ptr 0x14 [p1]
+; CHECK-NEXT: store ptr %a, ptr @g, align 8
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: captured_by_mem
+; CHECK-NEXT: call void @captured_by_mem(ptr %p1)
+; CHECK-NEXT: %p2 = load ptr, ptr @g, align 8 => ptr 0x14 [p1]
+; CHECK-NEXT: Entering function: mix_pointer
+; CHECK-NEXT: ptr %pa = ptr 0x14 [p1]
+; CHECK-NEXT: ptr %pb = ptr 0x14 [p1]
+; CHECK-NEXT: %ba = bitcast ptr %pa to <2 x b32> => { b32 00010100(00111110) 00000000(11011100) 00000000(01000001) 00000000(11001011) , b32 00000000(11000101) 00000000(00110111) 00000000(11101000) 00000000(00101000) }
+; CHECK-NEXT: %bb = bitcast ptr %pb to <2 x b32> => { b32 00010100(00111110) 00000000(11011100) 00000000(01000001) 00000000(11001011) , b32 00000000(11000101) 00000000(00110111) 00000000(11101000) 00000000(00101000) }
+; CHECK-NEXT: %bc = shufflevector <2 x b32> %ba, <2 x b32> %bb, <2 x i32> <i32 0, i32 3> => { b32 00010100(00111110) 00000000(11011100) 00000000(01000001) 00000000(11001011) , b32 00000000(11000101) 00000000(00110111) 00000000(11101000) 00000000(00101000) }
+; CHECK-NEXT: %pc = bitcast <2 x b32> %bc to ptr => ptr 0x14 [p1]
+; CHECK-NEXT: ret ptr %pc
+; CHECK-NEXT: Exiting function: mix_pointer
+; CHECK-NEXT: %mixed = call ptr @mix_pointer(ptr %p1, ptr %p2) => ptr 0x14 [p1]
+; CHECK-NEXT: store i32 0, ptr %mixed, align 4
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/captures_mixed6.ll b/llvm/test/tools/llubi/captures_mixed6.ll
new file mode 100644
index 0000000000000..8665f98fddb2d
--- /dev/null
+++ b/llvm/test/tools/llubi/captures_mixed6.ll
@@ -0,0 +1,61 @@
+; 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 ptr @mix_pointer(ptr %pa, ptr %pb) {
+ %ba = bitcast ptr %pa to <2 x b32>
+ %bb = bitcast ptr %pb to <2 x b32>
+ %bc = shufflevector <2 x b32> %ba, <2 x b32> %bb, <2 x i32> <i32 0, i32 3>
+ %pc = bitcast <2 x b32> %bc to ptr
+ ret ptr %pc
+}
+
+ at g = global ptr null
+
+define ptr @capture_read_provenance(ptr captures(read_provenance) %a) {
+ ret ptr %a
+}
+
+define void @captured_by_mem(ptr captures(read_provenance) %a) {
+ store ptr %a, ptr @g
+ ret void
+}
+
+define void @main() {
+ %p1 = alloca i32
+ %p2 = call ptr @capture_read_provenance(ptr %p1)
+ call void @captured_by_mem(ptr %p2)
+ %p3 = load ptr, ptr @g
+ %mixed = call ptr @mix_pointer(ptr %p2, ptr %p3)
+ %v = load i32, ptr %mixed
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p1 = alloca i32, align 4 => ptr 0x14 [p1]
+; CHECK-NEXT: Entering function: capture_read_provenance
+; CHECK-NEXT: ptr %a = ptr 0x14 [p1]
+; CHECK-NEXT: ret ptr %a
+; CHECK-NEXT: Exiting function: capture_read_provenance
+; CHECK-NEXT: %p2 = call ptr @capture_read_provenance(ptr %p1) => ptr 0x14 [p1 read_provenance]
+; CHECK-NEXT: Entering function: captured_by_mem
+; CHECK-NEXT: ptr %a = ptr 0x14 [p1 read_provenance]
+; CHECK-NEXT: store ptr %a, ptr @g, align 8
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: captured_by_mem
+; CHECK-NEXT: call void @captured_by_mem(ptr %p2)
+; CHECK-NEXT: %p3 = load ptr, ptr @g, align 8 => ptr 0x14 [p1 read_provenance]
+; CHECK-NEXT: Entering function: mix_pointer
+; CHECK-NEXT: ptr %pa = ptr 0x14 [p1 read_provenance]
+; CHECK-NEXT: ptr %pb = ptr 0x14 [p1 read_provenance]
+; CHECK-NEXT: %ba = bitcast ptr %pa to <2 x b32> => { b32 00010100(10001011) 00000000(11111001) 00000000(01000000) 00000000(00111110) , b32 00000000(01111100) 00000000(00111010) 00000000(11111101) 00000000(11111101) }
+; CHECK-NEXT: %bb = bitcast ptr %pb to <2 x b32> => { b32 00010100(00111110) 00000000(11011100) 00000000(01000001) 00000000(11001011) , b32 00000000(11000101) 00000000(00110111) 00000000(11101000) 00000000(00101000) }
+; CHECK-NEXT: %bc = shufflevector <2 x b32> %ba, <2 x b32> %bb, <2 x i32> <i32 0, i32 3> => { b32 00010100(10001011) 00000000(11111001) 00000000(01000000) 00000000(00111110) , b32 00000000(11000101) 00000000(00110111) 00000000(11101000) 00000000(00101000) }
+; CHECK-NEXT: %pc = bitcast <2 x b32> %bc to ptr => ptr 0x14 [nullary]
+; CHECK-NEXT: ret ptr %pc
+; CHECK-NEXT: Exiting function: mix_pointer
+; CHECK-NEXT: %mixed = call ptr @mix_pointer(ptr %p2, ptr %p3) => ptr 0x14 [nullary]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %v = load i32, ptr %mixed, align 4 at @main <stdin>:31
+; 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/captures_nested1.ll b/llvm/test/tools/llubi/captures_nested1.ll
new file mode 100644
index 0000000000000..fa7a60ceb58ab
--- /dev/null
+++ b/llvm/test/tools/llubi/captures_nested1.ll
@@ -0,0 +1,39 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+define ptr @g(ptr captures(read_provenance) %a) {
+ store i32 0, ptr %a
+ ret ptr %a
+}
+
+define ptr @f(ptr captures(none) %a) {
+ store i32 0, ptr %a
+ %res = call ptr @g(ptr %a)
+ %v = load i32, ptr %res
+ ret ptr %res
+}
+
+define void @main() {
+ %p1 = alloca i32
+ %p2 = call ptr @f(ptr %p1)
+ %v = load i32, ptr %p2
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p1 = alloca i32, align 4 => ptr 0x8 [p1]
+; CHECK-NEXT: Entering function: f
+; CHECK-NEXT: ptr %a = ptr 0x8 [p1]
+; CHECK-NEXT: store i32 0, ptr %a, align 4
+; CHECK-NEXT: Entering function: g
+; CHECK-NEXT: ptr %a = ptr 0x8 [p1]
+; CHECK-NEXT: store i32 0, ptr %a, align 4
+; CHECK-NEXT: ret ptr %a
+; CHECK-NEXT: Exiting function: g
+; CHECK-NEXT: %res = call ptr @g(ptr %a) => ptr 0x8 [p1 read_provenance]
+; CHECK-NEXT: %v = load i32, ptr %res, align 4 => i32 0
+; CHECK-NEXT: ret ptr %res
+; CHECK-NEXT: Exiting function: f
+; CHECK-NEXT: %p2 = call ptr @f(ptr %p1) => ptr 0x8 [p1 read_provenance]
+; CHECK-NEXT: %v = load i32, ptr %p2, align 4 => i32 0
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/captures_nested2.ll b/llvm/test/tools/llubi/captures_nested2.ll
new file mode 100644
index 0000000000000..0316f4cc018b3
--- /dev/null
+++ b/llvm/test/tools/llubi/captures_nested2.ll
@@ -0,0 +1,45 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+ at mem = global ptr null
+
+define void @g(ptr captures(read_provenance) %a) {
+ store i32 0, ptr %a
+ store ptr %a, ptr @mem
+ ret void
+}
+
+define ptr @f(ptr captures(none) %a) {
+ store i32 0, ptr %a
+ call void @g(ptr %a)
+ %res = load ptr, ptr @mem
+ %v = load i32, ptr %res
+ ret ptr %res
+}
+
+define void @main() {
+ %p1 = alloca i32
+ %p2 = call ptr @f(ptr %p1)
+ %v = load i32, ptr %p2
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p1 = alloca i32, align 4 => ptr 0x14 [p1]
+; CHECK-NEXT: Entering function: f
+; CHECK-NEXT: ptr %a = ptr 0x14 [p1]
+; CHECK-NEXT: store i32 0, ptr %a, align 4
+; CHECK-NEXT: Entering function: g
+; CHECK-NEXT: ptr %a = ptr 0x14 [p1]
+; CHECK-NEXT: store i32 0, ptr %a, align 4
+; CHECK-NEXT: store ptr %a, ptr @mem, align 8
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: g
+; CHECK-NEXT: call void @g(ptr %a)
+; CHECK-NEXT: %res = load ptr, ptr @mem, align 8 => ptr 0x14 [p1 read_provenance]
+; CHECK-NEXT: %v = load i32, ptr %res, align 4 => i32 0
+; CHECK-NEXT: ret ptr %res
+; CHECK-NEXT: Exiting function: f
+; CHECK-NEXT: %p2 = call ptr @f(ptr %p1) => ptr 0x14 [p1 read_provenance]
+; CHECK-NEXT: %v = load i32, ptr %p2, align 4 => i32 0
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
>From 2f66a08c12ae2f93c762626bed5e0e1c303f2372 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sun, 2 Aug 2026 01:04:20 +0800
Subject: [PATCH 4/4] [llubi] Add more tests and comments.
---
llvm/test/tools/llubi/captures_mixed1.ll | 2 ++
llvm/test/tools/llubi/captures_mixed2.ll | 5 ++-
llvm/test/tools/llubi/captures_mixed3.ll | 4 ++-
llvm/test/tools/llubi/captures_mixed4.ll | 4 ++-
llvm/test/tools/llubi/captures_mixed5.ll | 2 ++
llvm/test/tools/llubi/captures_mixed6.ll | 4 ++-
llvm/test/tools/llubi/captures_nested1.ll | 3 ++
llvm/test/tools/llubi/captures_nested2.ll | 4 +++
.../tools/llubi/retonly_capture_provenance.ll | 34 +++++++++++++++++++
.../tools/llubi/store_capture_provenance.ll | 3 +-
10 files changed, 60 insertions(+), 5 deletions(-)
create mode 100644 llvm/test/tools/llubi/retonly_capture_provenance.ll
diff --git a/llvm/test/tools/llubi/captures_mixed1.ll b/llvm/test/tools/llubi/captures_mixed1.ll
index 48a95325beab2..1a318721b86fe 100644
--- a/llvm/test/tools/llubi/captures_mixed1.ll
+++ b/llvm/test/tools/llubi/captures_mixed1.ll
@@ -11,6 +11,8 @@ define ptr @mix_pointer(ptr %pa, ptr %pb) {
ret ptr %pc
}
+; Ensure it is identical to `captures(address, provenance)`.
+
define ptr @identity(ptr %a) {
ret ptr %a
}
diff --git a/llvm/test/tools/llubi/captures_mixed2.ll b/llvm/test/tools/llubi/captures_mixed2.ll
index fc8bf1c903ad1..0f3d263af0550 100644
--- a/llvm/test/tools/llubi/captures_mixed2.ll
+++ b/llvm/test/tools/llubi/captures_mixed2.ll
@@ -11,6 +11,9 @@ define ptr @mix_pointer(ptr %pa, ptr %pb) {
ret ptr %pc
}
+; All components are captured through return value only. The returned pointer
+; should be identical to the input.
+
define ptr @identity(ptr captures(ret: address, provenance) %a) {
ret ptr %a
}
@@ -40,6 +43,6 @@ define void @main() {
; CHECK-NEXT: Exiting function: mix_pointer
; CHECK-NEXT: %mixed = call ptr @mix_pointer(ptr %p1, ptr %p2) => ptr 0x8 [nullary]
; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0 store i32 0, ptr %mixed, align 4 at @main <stdin>:22
+; CHECK-NEXT: #0 store i32 0, ptr %mixed, align 4 at @main <stdin>:25
; 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/captures_mixed3.ll b/llvm/test/tools/llubi/captures_mixed3.ll
index 03526d926a577..7845e55942b6d 100644
--- a/llvm/test/tools/llubi/captures_mixed3.ll
+++ b/llvm/test/tools/llubi/captures_mixed3.ll
@@ -11,6 +11,8 @@ define ptr @mix_pointer(ptr %pa, ptr %pb) {
ret ptr %pc
}
+; The returned pointer only captures read_provenance. So it cannot be mixed with the input.
+
define ptr @identity(ptr captures(read_provenance) %a) {
ret ptr %a
}
@@ -40,6 +42,6 @@ define void @main() {
; CHECK-NEXT: Exiting function: mix_pointer
; CHECK-NEXT: %mixed = call ptr @mix_pointer(ptr %p1, ptr %p2) => ptr 0x8 [nullary]
; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0 store i32 0, ptr %mixed, align 4 at @main <stdin>:22
+; CHECK-NEXT: #0 store i32 0, ptr %mixed, align 4 at @main <stdin>:24
; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/captures_mixed4.ll b/llvm/test/tools/llubi/captures_mixed4.ll
index da2e331b46218..f0a5dd03e639f 100644
--- a/llvm/test/tools/llubi/captures_mixed4.ll
+++ b/llvm/test/tools/llubi/captures_mixed4.ll
@@ -11,6 +11,8 @@ define ptr @mix_pointer(ptr %pa, ptr %pb) {
ret ptr %pc
}
+; Only provenance is captured through the memory. So the stored pointer cannot be mixed with the input.
+
@g = global ptr null
define void @captured_by_mem(ptr captures(provenance) %a) {
@@ -46,6 +48,6 @@ define void @main() {
; CHECK-NEXT: Exiting function: mix_pointer
; CHECK-NEXT: %mixed = call ptr @mix_pointer(ptr %p1, ptr %p2) => ptr 0x14 [nullary]
; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0 store i32 0, ptr %mixed, align 4 at @main <stdin>:26
+; CHECK-NEXT: #0 store i32 0, ptr %mixed, align 4 at @main <stdin>:28
; 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/captures_mixed5.ll b/llvm/test/tools/llubi/captures_mixed5.ll
index e73c1af9bea96..043927006d4b3 100644
--- a/llvm/test/tools/llubi/captures_mixed5.ll
+++ b/llvm/test/tools/llubi/captures_mixed5.ll
@@ -11,6 +11,8 @@ define ptr @mix_pointer(ptr %pa, ptr %pb) {
ret ptr %pc
}
+; All components are captured through the memory. The stored pointer should be identical to the input.
+
@g = global ptr null
define void @captured_by_mem(ptr captures(address, provenance) %a) {
diff --git a/llvm/test/tools/llubi/captures_mixed6.ll b/llvm/test/tools/llubi/captures_mixed6.ll
index 8665f98fddb2d..45a0884843827 100644
--- a/llvm/test/tools/llubi/captures_mixed6.ll
+++ b/llvm/test/tools/llubi/captures_mixed6.ll
@@ -17,6 +17,8 @@ define ptr @capture_read_provenance(ptr captures(read_provenance) %a) {
ret ptr %a
}
+; The only one component (read_provenance) is captured through the memory. The stored pointer should be identical to the input.
+
define void @captured_by_mem(ptr captures(read_provenance) %a) {
store ptr %a, ptr @g
ret void
@@ -56,6 +58,6 @@ define void @main() {
; CHECK-NEXT: Exiting function: mix_pointer
; CHECK-NEXT: %mixed = call ptr @mix_pointer(ptr %p2, ptr %p3) => ptr 0x14 [nullary]
; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0 %v = load i32, ptr %mixed, align 4 at @main <stdin>:31
+; CHECK-NEXT: #0 %v = load i32, ptr %mixed, align 4 at @main <stdin>:33
; 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/captures_nested1.ll b/llvm/test/tools/llubi/captures_nested1.ll
index fa7a60ceb58ab..618eb05dadd71 100644
--- a/llvm/test/tools/llubi/captures_nested1.ll
+++ b/llvm/test/tools/llubi/captures_nested1.ll
@@ -7,8 +7,10 @@ define ptr @g(ptr captures(read_provenance) %a) {
}
define ptr @f(ptr captures(none) %a) {
+ ; %a has full provenance here.
store i32 0, ptr %a
%res = call ptr @g(ptr %a)
+ ; %res has only read_provenance.
%v = load i32, ptr %res
ret ptr %res
}
@@ -16,6 +18,7 @@ define ptr @f(ptr captures(none) %a) {
define void @main() {
%p1 = alloca i32
%p2 = call ptr @f(ptr %p1)
+ ; %p2 has nullary provenance.
%v = load i32, ptr %p2
ret void
}
diff --git a/llvm/test/tools/llubi/captures_nested2.ll b/llvm/test/tools/llubi/captures_nested2.ll
index 0316f4cc018b3..6a82f93d7242d 100644
--- a/llvm/test/tools/llubi/captures_nested2.ll
+++ b/llvm/test/tools/llubi/captures_nested2.ll
@@ -4,14 +4,17 @@
@mem = global ptr null
define void @g(ptr captures(read_provenance) %a) {
+ ; %a has full provenance here.
store i32 0, ptr %a
store ptr %a, ptr @mem
ret void
}
define ptr @f(ptr captures(none) %a) {
+ ; %a has full provenance here.
store i32 0, ptr %a
call void @g(ptr %a)
+ ; %res has only read_provenance.
%res = load ptr, ptr @mem
%v = load i32, ptr %res
ret ptr %res
@@ -20,6 +23,7 @@ define ptr @f(ptr captures(none) %a) {
define void @main() {
%p1 = alloca i32
%p2 = call ptr @f(ptr %p1)
+ ; %p2 has nullary provenance.
%v = load i32, ptr %p2
ret void
}
diff --git a/llvm/test/tools/llubi/retonly_capture_provenance.ll b/llvm/test/tools/llubi/retonly_capture_provenance.ll
new file mode 100644
index 0000000000000..4c3fca5d0901b
--- /dev/null
+++ b/llvm/test/tools/llubi/retonly_capture_provenance.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
+
+ at g = global ptr null
+
+define ptr @retonly_capture_provenance(ptr captures(ret: address, provenance) %p) {
+ store ptr %p, ptr @g
+ ret ptr %p
+}
+
+define void @main() {
+ %p = alloca i32
+ %p1 = call ptr @retonly_capture_provenance(ptr %p)
+ ; Provenance is captured via the returned pointer.
+ store i32 1, ptr %p1
+ ; Provenance is not captured in other ways.
+ %p2 = load ptr, ptr @g
+ store i32 1, ptr %p2
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p = alloca i32, align 4 => ptr 0x14 [p]
+; CHECK-NEXT: Entering function: retonly_capture_provenance
+; CHECK-NEXT: ptr %p = ptr 0x14 [p]
+; CHECK-NEXT: store ptr %p, ptr @g, align 8
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: retonly_capture_provenance
+; CHECK-NEXT: %p1 = call ptr @retonly_capture_provenance(ptr %p) => ptr 0x14 [p]
+; CHECK-NEXT: store i32 1, ptr %p1, align 4
+; CHECK-NEXT: %p2 = load ptr, ptr @g, align 8 => ptr 0x14 [p none]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i32 1, ptr %p2, align 4 at @main <stdin>:18
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/store_capture_provenance.ll b/llvm/test/tools/llubi/store_capture_provenance.ll
index 920742c9fe5ff..f217ab4137718 100644
--- a/llvm/test/tools/llubi/store_capture_provenance.ll
+++ b/llvm/test/tools/llubi/store_capture_provenance.ll
@@ -9,6 +9,7 @@ define ptr @address_read_provenance(ptr captures(address, read_provenance) %p) {
define void @main() {
%g_address_read_provenance = call ptr @address_read_provenance(ptr @g)
+ ; Only read_provenance is captured. It is UB to store via the pointer.
store i8 1, ptr %g_address_read_provenance
ret void
}
@@ -19,6 +20,6 @@ define void @main() {
; CHECK-NEXT: Exiting function: address_read_provenance
; CHECK-NEXT: %g_address_read_provenance = call ptr @address_read_provenance(ptr @g) => ptr 0x8 [@g read_provenance]
; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0 store i8 1, ptr %g_address_read_provenance, align 1 at @main <stdin>:12
+; CHECK-NEXT: #0 store i8 1, ptr %g_address_read_provenance, align 1 at @main <stdin>:13
; 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