[llvm] [llubi] Poison object contents in `llvm.lifetime.end` (PR #206036)
Zhige Chen via llvm-commits
llvm-commits at lists.llvm.org
Fri Jun 26 04:28:21 PDT 2026
https://github.com/nofe1248 created https://github.com/llvm/llvm-project/pull/206036
Make `@llvm.lifetime.end` poison the object content. This removes the need of special-casing for dead objects in `ExecutorBase::load()`, etc.
See also [#204932 (comment)](https://github.com/llvm/llvm-project/pull/204932#discussion_r3465364425).
>From f76fec04ca3d30f8a334a67b11da3abce98b7349 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Fri, 26 Jun 2026 19:23:54 +0800
Subject: [PATCH] [llubi] Poison object contents in `llvm.lifetime.end`
---
llvm/test/tools/llubi/intr_memory.ll | 19 +++++++++++++++++++
llvm/tools/llubi/lib/Context.h | 7 ++++---
llvm/tools/llubi/lib/ExecutorBase.cpp | 4 ----
llvm/tools/llubi/lib/Interpreter.cpp | 9 +++------
4 files changed, 26 insertions(+), 13 deletions(-)
diff --git a/llvm/test/tools/llubi/intr_memory.ll b/llvm/test/tools/llubi/intr_memory.ll
index 5ab3a1d101fa1..3f51188602659 100644
--- a/llvm/test/tools/llubi/intr_memory.ll
+++ b/llvm/test/tools/llubi/intr_memory.ll
@@ -51,6 +51,16 @@ define void @main() {
%prov_copy = load ptr, ptr %prov_dst, align 8
store i8 0, ptr %prov_copy, align 1
+ %dead_src = alloca [4 x i8], align 1
+ %dead_dst = alloca [4 x i8], align 1
+ call void @llvm.lifetime.start.p0(ptr %dead_src)
+ call void @llvm.memset.p0.i8(ptr %dead_src, i8 51, i8 4, i1 false)
+ call void @llvm.lifetime.end.p0(ptr %dead_src)
+ call void @llvm.memcpy.p0.p0.i8(ptr %dead_dst, ptr %dead_src, i8 4, i1 false)
+ %dead_copy = load i32, ptr %dead_dst, align 1
+ call void @llvm.lifetime.start.p0(ptr %dead_src)
+ %restarted = load i32, ptr %dead_src, align 1
+
call void @llvm.memset.p0.i16(ptr poison, i8 0, i16 0, i1 false)
call void @llvm.memcpy.p0.p0.i8(ptr poison, ptr poison, i8 0, i1 false)
@@ -100,6 +110,15 @@ define void @main() {
; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i8(ptr %prov_dst, ptr %prov_src, i8 8, i1 false)
; CHECK-NEXT: %prov_copy = load ptr, ptr %prov_dst, align 8 => ptr 0x41 [prov_ptr]
; CHECK-NEXT: store i8 0, ptr %prov_copy, align 1
+; CHECK-NEXT: %dead_src = alloca [4 x i8], align 1 => ptr 0x43 [dead_src (dead)]
+; CHECK-NEXT: %dead_dst = alloca [4 x i8], align 1 => ptr 0x48 [dead_dst]
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %dead_src)
+; CHECK-NEXT: call void @llvm.memset.p0.i8(ptr %dead_src, i8 51, i8 4, i1 false)
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %dead_src)
+; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i8(ptr %dead_dst, ptr %dead_src, i8 4, i1 false)
+; CHECK-NEXT: %dead_copy = load i32, ptr %dead_dst, align 1 => poison
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %dead_src)
+; CHECK-NEXT: %restarted = load i32, ptr %dead_src, align 1 => i32 -1393641077
; CHECK-NEXT: call void @llvm.memset.p0.i16(ptr poison, i8 0, i16 0, i1 false)
; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i8(ptr poison, ptr poison, i8 0, i1 false)
; CHECK-NEXT: ret void
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 5f8b79a6b183c..7c1e8e82c7e74 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -43,9 +43,10 @@ enum class MemoryObjectState {
// -> Dead (after the end of lifetime of an alloca)
// -> Freed (after free is called on a heap object)
Alive,
- // This memory object is out of lifetime. It is OK to perform
- // operations that do not access its content, e.g., getelementptr.
- // Otherwise, an immediate UB occurs.
+ // This memory object is out of lifetime. Its contents are poison. Loads and
+ // memory transfers from it are allowed and propagate poison, stores to it
+ // cause immediate UB, and non-accessing operations such as getelementptr are
+ // allowed.
// Valid transition:
// -> Alive (after the start of lifetime of an alloca)
Dead,
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index da49a934123c9..0cbb38073f652 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -126,10 +126,6 @@ AnyValue ExecutorBase::load(const AnyValue &Ptr, Align Alignment, Type *ValTy,
PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
/*IsStore=*/false);
MO) {
- // Load from a dead stack object yields poison value.
- if (MO->getState() == MemoryObjectState::Dead)
- return AnyValue::getPoisonValue(Ctx, ValTy);
-
bool ContainsUndefinedBits = false;
AnyValue Res = Ctx.load(*MO, Offset, ValTy,
NoUndef ? &ContainsUndefinedBits : nullptr);
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index fe3c627b358ba..fd39b809d8f25 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -763,12 +763,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
MutableArrayRef<Byte> DstBytes = DstMO->getBytes().slice(DstOffset, Len);
- if (SrcMO->getState() == MemoryObjectState::Dead) {
- fill(DstBytes, Byte::poison());
- } else {
- ArrayRef<Byte> SrcBytes = SrcMO->getBytes().slice(SrcOffset, Len);
- std::memmove(DstBytes.data(), SrcBytes.data(), Len * sizeof(Byte));
- }
+ ArrayRef<Byte> SrcBytes = SrcMO->getBytes().slice(SrcOffset, Len);
+ std::memmove(DstBytes.data(), SrcBytes.data(), Len * sizeof(Byte));
return AnyValue();
}
@@ -1018,6 +1014,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
MO->setState(MemoryObjectState::Alive);
fill(MO->getBytes(), Byte::undef());
} else {
+ fill(MO->getBytes(), Byte::poison());
MO->setState(MemoryObjectState::Dead);
}
return AnyValue();
More information about the llvm-commits
mailing list