[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