[llvm] [llubi] Add support for load/store/lifetime markers (PR #182532)
Yingwei Zheng via llvm-commits
llvm-commits at lists.llvm.org
Fri Mar 6 09:56:03 PST 2026
https://github.com/dtcxzyw updated https://github.com/llvm/llvm-project/pull/182532
>From ac3c7c26851441920c3822403efa0a57b97d294a Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Fri, 20 Feb 2026 02:46:23 +0800
Subject: [PATCH 1/6] [llubi] Add support for load/store
---
llvm/docs/CommandGuide/llubi.rst | 12 +
llvm/test/tools/llubi/loadstore_be.ll | 153 +++++++++
llvm/test/tools/llubi/loadstore_le.ll | 155 +++++++++
llvm/test/tools/llubi/loadstore_misaligned.ll | 14 +
llvm/test/tools/llubi/loadstore_null.ll | 10 +
llvm/test/tools/llubi/loadstore_oob1.ll | 14 +
llvm/test/tools/llubi/loadstore_poison.ll | 10 +
llvm/test/tools/llubi/loadstore_uaf.ll | 21 ++
llvm/test/tools/llubi/store_dead.ll | 18 +
llvm/tools/llubi/lib/Context.cpp | 309 ++++++++++++++++--
llvm/tools/llubi/lib/Context.h | 36 +-
llvm/tools/llubi/lib/Interpreter.cpp | 127 ++++++-
llvm/tools/llubi/lib/Value.h | 63 +++-
llvm/tools/llubi/llubi.cpp | 23 +-
14 files changed, 904 insertions(+), 61 deletions(-)
create mode 100644 llvm/test/tools/llubi/loadstore_be.ll
create mode 100644 llvm/test/tools/llubi/loadstore_le.ll
create mode 100644 llvm/test/tools/llubi/loadstore_misaligned.ll
create mode 100644 llvm/test/tools/llubi/loadstore_null.ll
create mode 100644 llvm/test/tools/llubi/loadstore_oob1.ll
create mode 100644 llvm/test/tools/llubi/loadstore_poison.ll
create mode 100644 llvm/test/tools/llubi/loadstore_uaf.ll
create mode 100644 llvm/test/tools/llubi/store_dead.ll
diff --git a/llvm/docs/CommandGuide/llubi.rst b/llvm/docs/CommandGuide/llubi.rst
index f652af83d810a..55528373cffef 100644
--- a/llvm/docs/CommandGuide/llubi.rst
+++ b/llvm/docs/CommandGuide/llubi.rst
@@ -70,6 +70,18 @@ INTERPRETER OPTIONS
Set the value of `llvm.vscale` to N. The default value is 4.
+.. option:: -seed=N
+
+ Set the seed for random number generator to N. By default, the seed is 0.
+
+.. option:: -undef-behavior=mode
+
+ Set the behavior for undefined values (e.g., load from uninitialized memory or freeze a poison value).
+ The options for `mode` are:
+
+ * `nondet`: Each load from the same uninitialized byte yields a freshly random value. This is the default behavior.
+ * `zero`: Uninitialized values are treated as zero.
+
EXIT STATUS
-----------
diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
new file mode 100644
index 0000000000000..0b78536cd808f
--- /dev/null
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -0,0 +1,153 @@
+; 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-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64"
+
+%struct = type { [2 x i16], i64 }
+%struct.packed = type <{ [2 x i16], i64 }>
+%struct.vscale = type { <vscale x 1 x i32>, <vscale x 1 x i32> }
+
+define void @main() {
+ %alloc = alloca i32
+ store i32 u0x00010203, ptr %alloc
+ %val1 = load i32, ptr %alloc
+ %val2 = load i32, ptr %alloc, align 2
+ %gep = getelementptr i8, ptr %alloc, i64 1
+ %val3 = load i8, ptr %gep
+ %val4 = load <4 x i8>, ptr %alloc
+
+ store i16 u0x0405, ptr %gep, align 1
+ %val5 = load <4 x i8>, ptr %alloc
+ store <2 x i16> <i16 u0x0607, i16 u0x0809>, ptr %alloc
+ %val6 = load <4 x i8>, ptr %alloc
+ %val7 = load <8 x i4>, ptr %alloc
+ store <3 x i3> <i3 1, i3 2, i3 3>, ptr %alloc
+ ; padding bits are undefined.
+ %val8 = load <16 x i1>, ptr %alloc
+ %val9 = load <16 x i1>, ptr %alloc
+ store <8 x i3> <i3 0, i3 1, i3 2, i3 3, i3 4, i3 5, i3 6, i3 7>, ptr %alloc
+ %val_bitcast = load <3 x i8>, ptr %alloc
+
+ ; For non-byte-sized types, the padding bits must be zero.
+ store i25 -1, ptr %alloc
+ %val10 = load <4 x i8>, ptr %alloc
+ store i8 -1, ptr %alloc
+ ; If the padding bits were not zero, load yields poison value.
+ %val11 = load i25, ptr %alloc
+
+ call void @llvm.lifetime.start.p0(ptr poison)
+ call void @llvm.lifetime.end.p0(ptr poison)
+ %alloc_lifetime = alloca i32
+ ; Load of an dead object yields poison value.
+ %val12 = load i32, ptr %alloc_lifetime
+ call void @llvm.lifetime.start.p0(ptr %alloc_lifetime)
+ ; After lifetime.start, the object is alive but uninitialized.
+ %val13 = load i32, ptr %alloc_lifetime
+ %val14 = load i32, ptr %alloc_lifetime
+ store i32 77, ptr %alloc_lifetime
+ %val15 = load i32, ptr %alloc_lifetime
+ call void @llvm.lifetime.end.p0(ptr %alloc_lifetime)
+ ; Load of an dead object yields poison value.
+ %val16 = load i32, ptr %alloc_lifetime
+
+ store i32 u0xFFF80000, ptr %alloc
+ %val17 = load float, ptr %alloc
+
+ %alloc_vscale = alloca <vscale x 2 x i32>
+ %insert = insertelement <vscale x 1 x i32> poison, i32 1, i32 0
+ %ones = shufflevector <vscale x 1 x i32> %insert, <vscale x 1 x i32> poison, <vscale x 1 x i32> zeroinitializer
+ %twos = add <vscale x 1 x i32> %ones, %ones
+ store <vscale x 1 x i32> %ones, ptr %alloc_vscale
+ %gep3 = getelementptr <vscale x 1 x i32>, ptr %alloc_vscale, i64 1
+ store <vscale x 1 x i32> %twos, ptr %gep3
+ %val18 = load <vscale x 2 x i32>, ptr %alloc_vscale
+
+ %alloc_struct = alloca %struct
+ store %struct { [2 x i16] [i16 1, i16 2], i64 3 }, ptr %alloc_struct
+ %val19 = load %struct, ptr %alloc_struct
+ ; Padding bytes of struct are undefined.
+ %val20 = load i64, ptr %alloc_struct
+ %val21 = load i64, ptr %alloc_struct
+
+ %alloc_struct_packed = alloca %struct.packed
+ store %struct.packed <{ [2 x i16] [i16 1, i16 2], i64 3 }>, ptr %alloc_struct_packed
+ %val22 = load %struct.packed, ptr %alloc_struct_packed
+ ; No padding bytes.
+ %val23 = load i64, ptr %alloc_struct_packed
+ %val24 = load i64, ptr %alloc_struct_packed
+
+ %alloc_struct_vscale = alloca %struct.vscale
+ store %struct.vscale zeroinitializer, ptr %alloc_struct_vscale
+ %gep4 = getelementptr <vscale x 1 x i32>, ptr %alloc_struct_vscale, i32 1
+ store <vscale x 1 x i32> %ones, ptr %gep4
+ %val25 = load %struct.vscale, ptr %alloc_struct_vscale
+
+ %alloc_array = alloca [2 x i32]
+ store [2 x i32] [i32 1, i32 2], ptr %alloc_array
+ %val26 = load [2 x i32], ptr %alloc_array
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: store i32 66051, ptr %alloc, align 4
+; CHECK-NEXT: %val1 = load i32, ptr %alloc, align 4 => i32 66051
+; CHECK-NEXT: %val2 = load i32, ptr %alloc, align 2 => i32 66051
+; CHECK-NEXT: %gep = getelementptr i8, ptr %alloc, i64 1 => ptr 0x9 [alloc + 1]
+; CHECK-NEXT: %val3 = load i8, ptr %gep, align 1 => i8 1
+; CHECK-NEXT: %val4 = load <4 x i8>, ptr %alloc, align 4 => { i8 3, i8 2, i8 1, i8 0 }
+; CHECK-NEXT: store i16 1029, ptr %gep, align 1
+; CHECK-NEXT: %val5 = load <4 x i8>, ptr %alloc, align 4 => { i8 3, i8 5, i8 4, i8 0 }
+; CHECK-NEXT: store <2 x i16> <i16 1543, i16 2057>, ptr %alloc, align 4
+; CHECK-NEXT: %val6 = load <4 x i8>, ptr %alloc, align 4 => { i8 7, i8 6, i8 9, i8 8 }
+; CHECK-NEXT: %val7 = load <8 x i4>, ptr %alloc, align 4 => { i4 0, i4 7, i4 0, i4 6, i4 0, i4 -7, i4 0, i4 -8 }
+; CHECK-NEXT: store <3 x i3> <i3 1, i3 2, i3 3>, ptr %alloc, align 2
+; CHECK-NEXT: %val8 = load <16 x i1>, ptr %alloc, align 2 => { T, F, F, F, F, T, F, F, F, T, F, T, F, F, T, T }
+; CHECK-NEXT: %val9 = load <16 x i1>, ptr %alloc, align 2 => { F, F, T, F, F, T, F, F, F, T, F, T, F, F, T, T }
+; CHECK-NEXT: store <8 x i3> <i3 0, i3 1, i3 2, i3 3, i3 -4, i3 -3, i3 -2, i3 -1>, ptr %alloc, align 4
+; CHECK-NEXT: %val_bitcast = load <3 x i8>, ptr %alloc, align 4 => { i8 5, i8 57, i8 119 }
+; CHECK-NEXT: store i25 -1, ptr %alloc, align 4
+; CHECK-NEXT: %val10 = load <4 x i8>, ptr %alloc, align 4 => { i8 -1, i8 -1, i8 -1, i8 1 }
+; CHECK-NEXT: store i8 -1, ptr %alloc, align 1
+; 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]
+; 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 -1295355583
+; CHECK-NEXT: %val14 = load i32, ptr %alloc_lifetime, align 4 => i32 -1809495666
+; CHECK-NEXT: store i32 77, ptr %alloc_lifetime, align 4
+; CHECK-NEXT: %val15 = load i32, ptr %alloc_lifetime, align 4 => i32 77
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %alloc_lifetime)
+; 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 => NaN
+; CHECK-NEXT: %alloc_vscale = alloca <vscale x 2 x i32>, align 8 => ptr 0x10 [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: 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 2, i32 2, i32 2, i32 2, i32 1, i32 1, i32 1, i32 1 }
+; CHECK-NEXT: %alloc_struct = alloca %struct, align 8 => ptr 0x30 [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 281483653031312
+; CHECK-NEXT: %val21 = load i64, ptr %alloc_struct, align 8 => i64 281487549378445
+; CHECK-NEXT: %alloc_struct_packed = alloca %struct.packed, align 8 => ptr 0x40 [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: 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: 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: 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: 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
new file mode 100644
index 0000000000000..62caea6aceacc
--- /dev/null
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -0,0 +1,155 @@
+; 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-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64"
+
+%struct = type { [2 x i16], i64 }
+%struct.packed = type <{ [2 x i16], i64 }>
+%struct.vscale = type { <vscale x 1 x i32>, <vscale x 1 x i32> }
+
+define void @main() {
+ %alloc = alloca i32
+ store i32 u0x00010203, ptr %alloc
+ %val1 = load i32, ptr %alloc
+ %val2 = load i32, ptr %alloc, align 2
+ %gep = getelementptr i8, ptr %alloc, i64 1
+ %val3 = load i8, ptr %gep
+ %val4 = load <4 x i8>, ptr %alloc
+
+ store i16 u0x0405, ptr %gep, align 1
+ %val5 = load <4 x i8>, ptr %alloc
+ store <2 x i16> <i16 u0x0607, i16 u0x0809>, ptr %alloc
+ %val6 = load <4 x i8>, ptr %alloc
+ %val7 = load <8 x i4>, ptr %alloc
+ store <3 x i3> <i3 1, i3 2, i3 3>, ptr %alloc
+ ; padding bits are undefined.
+ %val8 = load <16 x i1>, ptr %alloc
+ %val9 = load <16 x i1>, ptr %alloc
+ store <8 x i3> <i3 0, i3 1, i3 2, i3 3, i3 4, i3 5, i3 6, i3 7>, ptr %alloc
+ %val_bitcast = load <3 x i8>, ptr %alloc
+
+ ; For non-byte-sized types, the padding bits must be zero.
+ store i25 -1, ptr %alloc
+ %val10 = load <4 x i8>, ptr %alloc
+ %gep2 = getelementptr i8, ptr %alloc, i64 3
+ store i8 -1, ptr %gep2
+ ; If the padding bits were not zero, load yields poison value.
+ %val11 = load i25, ptr %alloc
+
+ call void @llvm.lifetime.start.p0(ptr poison)
+ call void @llvm.lifetime.end.p0(ptr poison)
+ %alloc_lifetime = alloca i32
+ ; Load of an dead object yields poison value.
+ %val12 = load i32, ptr %alloc_lifetime
+ call void @llvm.lifetime.start.p0(ptr %alloc_lifetime)
+ ; After lifetime.start, the object is alive but uninitialized.
+ %val13 = load i32, ptr %alloc_lifetime
+ %val14 = load i32, ptr %alloc_lifetime
+ store i32 77, ptr %alloc_lifetime
+ %val15 = load i32, ptr %alloc_lifetime
+ call void @llvm.lifetime.end.p0(ptr %alloc_lifetime)
+ ; Load of an dead object yields poison value.
+ %val16 = load i32, ptr %alloc_lifetime
+
+ store i32 u0xFFF80000, ptr %alloc
+ %val17 = load float, ptr %alloc
+
+ %alloc_vscale = alloca <vscale x 2 x i32>
+ %insert = insertelement <vscale x 1 x i32> poison, i32 1, i32 0
+ %ones = shufflevector <vscale x 1 x i32> %insert, <vscale x 1 x i32> poison, <vscale x 1 x i32> zeroinitializer
+ %twos = add <vscale x 1 x i32> %ones, %ones
+ store <vscale x 1 x i32> %ones, ptr %alloc_vscale
+ %gep3 = getelementptr <vscale x 1 x i32>, ptr %alloc_vscale, i64 1
+ store <vscale x 1 x i32> %twos, ptr %gep3
+ %val18 = load <vscale x 2 x i32>, ptr %alloc_vscale
+
+ %alloc_struct = alloca %struct
+ store %struct { [2 x i16] [i16 1, i16 2], i64 3 }, ptr %alloc_struct
+ %val19 = load %struct, ptr %alloc_struct
+ ; Padding bytes of struct are undefined.
+ %val20 = load i64, ptr %alloc_struct
+ %val21 = load i64, ptr %alloc_struct
+
+ %alloc_struct_packed = alloca %struct.packed
+ store %struct.packed <{ [2 x i16] [i16 1, i16 2], i64 3 }>, ptr %alloc_struct_packed
+ %val22 = load %struct.packed, ptr %alloc_struct_packed
+ ; No padding bytes.
+ %val23 = load i64, ptr %alloc_struct_packed
+ %val24 = load i64, ptr %alloc_struct_packed
+
+ %alloc_struct_vscale = alloca %struct.vscale
+ store %struct.vscale zeroinitializer, ptr %alloc_struct_vscale
+ %gep4 = getelementptr <vscale x 1 x i32>, ptr %alloc_struct_vscale, i32 1
+ store <vscale x 1 x i32> %ones, ptr %gep4
+ %val25 = load %struct.vscale, ptr %alloc_struct_vscale
+
+ %alloc_array = alloca [2 x i32]
+ store [2 x i32] [i32 1, i32 2], ptr %alloc_array
+ %val26 = load [2 x i32], ptr %alloc_array
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: store i32 66051, ptr %alloc, align 4
+; CHECK-NEXT: %val1 = load i32, ptr %alloc, align 4 => i32 66051
+; CHECK-NEXT: %val2 = load i32, ptr %alloc, align 2 => i32 66051
+; CHECK-NEXT: %gep = getelementptr i8, ptr %alloc, i64 1 => ptr 0x9 [alloc + 1]
+; CHECK-NEXT: %val3 = load i8, ptr %gep, align 1 => i8 2
+; CHECK-NEXT: %val4 = load <4 x i8>, ptr %alloc, align 4 => { i8 3, i8 2, i8 1, i8 0 }
+; CHECK-NEXT: store i16 1029, ptr %gep, align 1
+; CHECK-NEXT: %val5 = load <4 x i8>, ptr %alloc, align 4 => { i8 3, i8 5, i8 4, i8 0 }
+; CHECK-NEXT: store <2 x i16> <i16 1543, i16 2057>, ptr %alloc, align 4
+; CHECK-NEXT: %val6 = load <4 x i8>, ptr %alloc, align 4 => { i8 7, i8 6, i8 9, i8 8 }
+; CHECK-NEXT: %val7 = load <8 x i4>, ptr %alloc, align 4 => { i4 7, i4 0, i4 6, i4 0, i4 -7, i4 0, i4 -8, i4 0 }
+; CHECK-NEXT: store <3 x i3> <i3 1, i3 2, i3 3>, ptr %alloc, align 2
+; CHECK-NEXT: %val8 = load <16 x i1>, ptr %alloc, align 2 => { T, F, F, F, T, F, T, T, F, F, T, F, F, F, F, T }
+; CHECK-NEXT: %val9 = load <16 x i1>, ptr %alloc, align 2 => { T, F, F, F, T, F, T, T, F, F, T, F, F, T, F, F }
+; CHECK-NEXT: store <8 x i3> <i3 0, i3 1, i3 2, i3 3, i3 -4, i3 -3, i3 -2, i3 -1>, ptr %alloc, align 4
+; CHECK-NEXT: %val_bitcast = load <3 x i8>, ptr %alloc, align 4 => { i8 -120, i8 -58, i8 -6 }
+; CHECK-NEXT: store i25 -1, ptr %alloc, align 4
+; CHECK-NEXT: %val10 = load <4 x i8>, ptr %alloc, align 4 => { i8 -1, i8 -1, i8 -1, i8 1 }
+; CHECK-NEXT: %gep2 = getelementptr i8, ptr %alloc, i64 3 => ptr 0xB [alloc + 3]
+; CHECK-NEXT: store i8 -1, ptr %gep2, align 1
+; 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]
+; 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 -1295355583
+; CHECK-NEXT: %val14 = load i32, ptr %alloc_lifetime, align 4 => i32 -1809495666
+; CHECK-NEXT: store i32 77, ptr %alloc_lifetime, align 4
+; CHECK-NEXT: %val15 = load i32, ptr %alloc_lifetime, align 4 => i32 77
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %alloc_lifetime)
+; 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 => NaN
+; CHECK-NEXT: %alloc_vscale = alloca <vscale x 2 x i32>, align 8 => ptr 0x10 [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: 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: 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 371025319710294017
+; CHECK-NEXT: %val21 = load i64, ptr %alloc_struct, align 8 => i64 -1341035243900895231
+; CHECK-NEXT: %alloc_struct_packed = alloca %struct.packed, align 8 => ptr 0x40 [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: 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: 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: 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: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/loadstore_misaligned.ll b/llvm/test/tools/llubi/loadstore_misaligned.ll
new file mode 100644
index 0000000000000..cc80639d773bd
--- /dev/null
+++ b/llvm/test/tools/llubi/loadstore_misaligned.ll
@@ -0,0 +1,14 @@
+; RUN: sed 's/OP1/store i32 0/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+; RUN: sed 's/OP1/%res = load i32/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @main() {
+ %alloc = alloca [2 x i32], align 8
+ %gep = getelementptr inbounds [2 x i32], ptr %alloc, i64 0, i64 1
+ OP1, ptr %gep, align 8
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca [2 x i32], align 8 => ptr 0x8 [alloc]
+; CHECK-NEXT: %gep = getelementptr inbounds [2 x i32], ptr %alloc, i64 0, i64 1 => ptr 0xC [alloc + 4]
+; CHECK-NEXT: Immediate UB detected: Misaligned memory access.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/loadstore_null.ll b/llvm/test/tools/llubi/loadstore_null.ll
new file mode 100644
index 0000000000000..7a243784e6bd6
--- /dev/null
+++ b/llvm/test/tools/llubi/loadstore_null.ll
@@ -0,0 +1,10 @@
+; RUN: sed 's/OP1/store i32 0/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+; RUN: sed 's/OP1/%res = load i32/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @main() {
+ OP1, ptr null
+ ret void
+}
+; CHECK: Entering function: main
+; 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/loadstore_oob1.ll b/llvm/test/tools/llubi/loadstore_oob1.ll
new file mode 100644
index 0000000000000..0618faa96bfde
--- /dev/null
+++ b/llvm/test/tools/llubi/loadstore_oob1.ll
@@ -0,0 +1,14 @@
+; RUN: sed 's/OP1/store i32 0/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+; RUN: sed 's/OP1/%res = load i32/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @main() {
+ %alloc = alloca [2 x i32]
+ %gep = getelementptr inbounds [2 x i32], ptr %alloc, i64 0, i64 2
+ OP1, ptr %gep
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca [2 x i32], align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: %gep = getelementptr inbounds [2 x i32], ptr %alloc, i64 0, i64 2 => ptr 0x10 [alloc + 8]
+; CHECK-NEXT: Immediate UB detected: Memory access is out of bounds.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/loadstore_poison.ll b/llvm/test/tools/llubi/loadstore_poison.ll
new file mode 100644
index 0000000000000..44c72aa803709
--- /dev/null
+++ b/llvm/test/tools/llubi/loadstore_poison.ll
@@ -0,0 +1,10 @@
+; RUN: sed 's/OP1/store i32 0/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+; RUN: sed 's/OP1/%res = load i32/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @main() {
+ OP1, ptr poison
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Invalid memory access with a poison pointer.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/loadstore_uaf.ll b/llvm/test/tools/llubi/loadstore_uaf.ll
new file mode 100644
index 0000000000000..21b86552275b6
--- /dev/null
+++ b/llvm/test/tools/llubi/loadstore_uaf.ll
@@ -0,0 +1,21 @@
+; RUN: sed 's/OP1/store i32 0/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+; RUN: sed 's/OP1/%res = load i32/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define ptr @stack_object() {
+ %alloc = alloca i32
+ ret ptr %alloc
+}
+
+define void @main() {
+ %alloc = call ptr @stack_object()
+ OP1, ptr %alloc
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Entering function: stack_object
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: ret ptr %alloc
+; CHECK-NEXT: Exiting function: stack_object
+; CHECK-NEXT: %alloc = call ptr @stack_object() => ptr 0x8 [dangling]
+; CHECK-NEXT: Immediate UB detected: Try to access a dead memory object.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/store_dead.ll b/llvm/test/tools/llubi/store_dead.ll
new file mode 100644
index 0000000000000..8bd15beefb8d3
--- /dev/null
+++ b/llvm/test/tools/llubi/store_dead.ll
@@ -0,0 +1,18 @@
+; 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 void @main() {
+ %alloc = alloca i32
+ call void @llvm.lifetime.start.p0(ptr %alloc)
+ store i32 0, ptr %alloc
+ call void @llvm.lifetime.end.p0(ptr %alloc)
+ store i32 0, ptr %alloc
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %alloc)
+; CHECK-NEXT: store i32 0, ptr %alloc, align 4
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %alloc)
+; CHECK-NEXT: Immediate UB detected: Try to access a dead memory object.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index adedfbcc3886b..1e7b0dd723dc2 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -103,6 +103,267 @@ const AnyValue &Context::getConstantValue(Constant *C) {
return ConstCache.emplace(C, getConstantValueImpl(C)).first->second;
}
+AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
+ uint32_t &OffsetInBits, bool CheckPaddingBits) {
+ if (Ty->isIntegerTy() || Ty->isFloatingPointTy() || Ty->isPointerTy()) {
+ uint32_t NumBits = DL.getTypeSizeInBits(Ty).getFixedValue();
+ uint32_t NewOffsetInBits = OffsetInBits + NumBits;
+ if (CheckPaddingBits)
+ NewOffsetInBits = alignTo(NewOffsetInBits, 8);
+ bool NeedsPadding = NewOffsetInBits != OffsetInBits + NumBits;
+ uint32_t NumBitsToExtract = NewOffsetInBits - OffsetInBits;
+ SmallVector<uint64_t> BitsData(alignTo(NumBitsToExtract, 8));
+ for (uint32_t I = 0; I < NumBitsToExtract; I += 8) {
+ uint32_t NumBitsInByte = std::min(8U, NumBitsToExtract - I);
+ uint32_t BitsStart =
+ OffsetInBits +
+ (DL.isLittleEndian() ? I : (NumBitsToExtract - NumBitsInByte - I));
+ uint32_t BitsEnd = BitsStart + NumBitsInByte - 1;
+ Byte LogicalByte;
+ if (((BitsStart ^ BitsEnd) & ~7) == 0)
+ LogicalByte = Bytes[BitsStart / 8].lshr(BitsStart % 8);
+ else
+ LogicalByte =
+ Bytes[BitsStart / 8].fshr(Bytes[BitsEnd / 8], BitsStart % 8);
+
+ uint32_t Mask = (1U << NumBitsInByte) - 1;
+ // If any of the bits in the byte is poison, the whole value is poison.
+ if (~LogicalByte.ConcreteMask & ~LogicalByte.Value & Mask)
+ return AnyValue::poison();
+ uint8_t RandomBits = 0;
+ if (UndefBehavior == UndefValueBehavior::NonDeterministic &&
+ (~LogicalByte.ConcreteMask & Mask)) {
+ // This byte contains undef bits.
+ std::uniform_int_distribution<uint8_t> Distrib;
+ RandomBits = Distrib(Rng);
+ }
+ uint8_t ActualBits = ((LogicalByte.Value & LogicalByte.ConcreteMask) |
+ (RandomBits & ~LogicalByte.ConcreteMask)) &
+ Mask;
+ BitsData[I / 64] |= static_cast<APInt::WordType>(ActualBits) << (I % 64);
+ }
+ OffsetInBits = NewOffsetInBits;
+
+ APInt Bits(NumBitsToExtract, BitsData);
+
+ // Padding bits for non-byte-sized scalar types must be zero.
+ if (NeedsPadding) {
+ if (!Bits.isIntN(NumBits))
+ return AnyValue::poison();
+ Bits = Bits.trunc(NumBits);
+ }
+
+ if (Ty->isIntegerTy())
+ return Bits;
+ if (Ty->isFloatingPointTy())
+ return APFloat(Ty->getFltSemantics(), Bits);
+ assert(Ty->isPointerTy() && "Expect a pointer type");
+ // TODO: recover provenance
+ return Pointer(Bits);
+ }
+
+ assert(OffsetInBits % 8 == 0 && "Missing padding bits.");
+ if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
+ Type *ElemTy = VecTy->getElementType();
+ std::vector<AnyValue> ValVec;
+ uint32_t NumElements = getEVL(VecTy->getElementCount());
+ ValVec.reserve(NumElements);
+ for (uint32_t I = 0; I != NumElements; ++I)
+ ValVec.push_back(
+ fromBytes(Bytes, ElemTy, OffsetInBits, /*CheckPaddingBits=*/false));
+ if (DL.isBigEndian())
+ std::reverse(ValVec.begin(), ValVec.end());
+ return AnyValue(std::move(ValVec));
+ }
+ if (auto *ArrTy = dyn_cast<ArrayType>(Ty)) {
+ Type *ElemTy = ArrTy->getElementType();
+ std::vector<AnyValue> ValVec;
+ uint32_t NumElements = ArrTy->getNumElements();
+ ValVec.reserve(NumElements);
+ for (uint32_t I = 0; I != NumElements; ++I)
+ ValVec.push_back(
+ fromBytes(Bytes, ElemTy, OffsetInBits, /*CheckPaddingBits=*/true));
+ return AnyValue(std::move(ValVec));
+ }
+ if (auto *StructTy = dyn_cast<StructType>(Ty)) {
+ auto *Layout = DL.getStructLayout(StructTy);
+ uint32_t BaseOffsetInBits = OffsetInBits;
+ std::vector<AnyValue> ValVec;
+ uint32_t NumElements = StructTy->getNumElements();
+ ValVec.reserve(NumElements);
+ for (uint32_t I = 0; I != NumElements; ++I) {
+ Type *ElemTy = StructTy->getElementType(I);
+ TypeSize ElemOffset = Layout->getElementOffset(I);
+ OffsetInBits =
+ BaseOffsetInBits + (ElemOffset.isScalable()
+ ? ElemOffset.getKnownMinValue() * VScale
+ : ElemOffset.getFixedValue()) *
+ 8;
+ ValVec.push_back(
+ fromBytes(Bytes, ElemTy, OffsetInBits, /*CheckPaddingBits=*/true));
+ }
+ OffsetInBits =
+ BaseOffsetInBits +
+ static_cast<uint32_t>(getEffectiveTypeStoreSize(StructTy)) * 8;
+ return AnyValue(std::move(ValVec));
+ }
+ llvm_unreachable("Unsupported first class type.");
+}
+
+void Context::toBytes(const AnyValue &Val, Type *Ty, uint32_t &OffsetInBits,
+ MutableArrayRef<Byte> Bytes, bool PaddingBits) {
+ if (Val.isPoison() || Ty->isIntegerTy() || Ty->isFloatingPointTy() ||
+ Ty->isPointerTy()) {
+ uint32_t NumBits = DL.getTypeSizeInBits(Ty).getFixedValue();
+ uint32_t NewOffsetInBits = OffsetInBits + NumBits;
+ if (PaddingBits)
+ NewOffsetInBits = alignTo(NewOffsetInBits, 8);
+ bool NeedsPadding = NewOffsetInBits != OffsetInBits + NumBits;
+ auto WriteBits = [&](const APInt &Bits) {
+ for (uint32_t I = 0, E = Bits.getBitWidth(); I < E; I += 8) {
+ uint32_t NumBitsInByte = std::min(8U, E - I);
+ uint32_t BitsStart =
+ OffsetInBits + (DL.isLittleEndian() ? I : (E - NumBitsInByte - I));
+ uint32_t BitsEnd = BitsStart + NumBitsInByte - 1;
+ uint8_t BitsVal =
+ static_cast<uint8_t>(Bits.extractBitsAsZExtValue(NumBitsInByte, I));
+
+ Bytes[BitsStart / 8].writeBits(
+ static_cast<uint8_t>(((1U << NumBitsInByte) - 1)
+ << (BitsStart % 8)),
+ static_cast<uint8_t>(BitsVal << (BitsStart % 8)));
+ // Crosses the byte boundary.
+ if (((BitsStart ^ BitsEnd) & ~7) != 0)
+ Bytes[BitsEnd / 8].writeBits(
+ static_cast<uint8_t>((1U << (BitsEnd % 8 + 1)) - 1),
+ static_cast<uint8_t>(BitsVal >> (8 - (BitsStart % 8))));
+ }
+ };
+ if (Val.isPoison()) {
+ for (uint32_t I = 0, E = NewOffsetInBits - OffsetInBits; I < E;) {
+ uint32_t NumBitsInByte = std::min(8 - (OffsetInBits + I) % 8, E - I);
+ assert(((OffsetInBits ^ (OffsetInBits + NumBitsInByte - 1)) & ~7) ==
+ 0 &&
+ "Across byte boundary.");
+ Bytes[(OffsetInBits + I) / 8].poisonBits(static_cast<uint8_t>(
+ ((1U << NumBitsInByte) - 1) << ((OffsetInBits + I) % 8)));
+ I += NumBitsInByte;
+ }
+ } else if (Ty->isIntegerTy()) {
+ auto &Bits = Val.asInteger();
+ WriteBits(NeedsPadding ? Bits.zext(NewOffsetInBits - OffsetInBits)
+ : Bits);
+ } else if (Ty->isFloatingPointTy()) {
+ auto Bits = Val.asFloat().bitcastToAPInt();
+ WriteBits(NeedsPadding ? Bits.zext(NewOffsetInBits - OffsetInBits)
+ : Bits);
+ } else if (Ty->isPointerTy()) {
+ auto &Bits = Val.asPointer().address();
+ WriteBits(NeedsPadding ? Bits.zext(NewOffsetInBits - OffsetInBits)
+ : Bits);
+ // TODO: save metadata of the pointer.
+ } else {
+ llvm_unreachable("Unsupported scalar type.");
+ }
+ OffsetInBits = NewOffsetInBits;
+ return;
+ }
+
+ assert(OffsetInBits % 8 == 0 && "Missing padding bits.");
+ if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
+ Type *ElemTy = VecTy->getElementType();
+ auto &ValVec = Val.asAggregate();
+ uint32_t NewOffsetInBits =
+ alignTo(OffsetInBits + DL.getTypeSizeInBits(ElemTy).getFixedValue() *
+ ValVec.size(),
+ 8);
+ if (DL.isLittleEndian()) {
+ for (const auto &SubVal : ValVec)
+ toBytes(SubVal, ElemTy, OffsetInBits, Bytes,
+ /*PaddingBits=*/false);
+ } else {
+ for (const auto &SubVal : reverse(ValVec))
+ toBytes(SubVal, ElemTy, OffsetInBits, Bytes,
+ /*PaddingBits=*/false);
+ }
+ if (NewOffsetInBits != OffsetInBits) {
+ assert(OffsetInBits % 8 != 0 && NewOffsetInBits - OffsetInBits < 8 &&
+ "Unexpected offset.");
+ // Fill remaining bits with undef.
+ Bytes[OffsetInBits / 8].undefBits(
+ static_cast<uint8_t>(~0U << (OffsetInBits % 8)));
+ }
+ OffsetInBits = NewOffsetInBits;
+ return;
+ }
+ if (auto *ArrTy = dyn_cast<ArrayType>(Ty)) {
+ Type *ElemTy = ArrTy->getElementType();
+ for (const auto &SubVal : Val.asAggregate())
+ toBytes(SubVal, ElemTy, OffsetInBits, Bytes, /*PaddingBits=*/true);
+ return;
+ }
+ if (auto *StructTy = dyn_cast<StructType>(Ty)) {
+ auto *Layout = DL.getStructLayout(StructTy);
+ uint32_t BaseOffsetInBits = OffsetInBits;
+ auto FillUndefBytes = [&](uint32_t NewOffsetInBits) {
+ if (OffsetInBits == NewOffsetInBits)
+ return;
+ // Fill padding bits due to alignment requirement.
+ assert(NewOffsetInBits > OffsetInBits &&
+ "Unexpected negative padding bits!");
+ fill(Bytes.slice(OffsetInBits / 8, (NewOffsetInBits - OffsetInBits) / 8),
+ Byte::undef());
+ OffsetInBits = NewOffsetInBits;
+ };
+ for (uint32_t I = 0, E = Val.asAggregate().size(); I != E; ++I) {
+ Type *ElemTy = StructTy->getElementType(I);
+ TypeSize ElemOffset = Layout->getElementOffset(I);
+ uint32_t NewOffsetInBits =
+ BaseOffsetInBits + (ElemOffset.isScalable()
+ ? ElemOffset.getKnownMinValue() * VScale
+ : ElemOffset.getFixedValue()) *
+ 8;
+ FillUndefBytes(NewOffsetInBits);
+ toBytes(Val.asAggregate()[I], ElemTy, OffsetInBits, Bytes,
+ /*PaddingBits=*/true);
+ }
+ uint32_t NewOffsetInBits =
+ BaseOffsetInBits + getEffectiveTypeStoreSize(StructTy) * 8;
+ FillUndefBytes(NewOffsetInBits);
+ return;
+ }
+
+ llvm_unreachable("Unsupported first class type.");
+}
+
+AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty) {
+ uint32_t OffsetInBits = 0;
+ return fromBytes(Bytes, Ty, OffsetInBits, /*CheckPaddingBits=*/true);
+}
+
+void Context::toBytes(const AnyValue &Val, Type *Ty,
+ MutableArrayRef<Byte> Bytes) {
+ uint32_t OffsetInBits = 0;
+ toBytes(Val, Ty, OffsetInBits, Bytes, /*PaddingBits=*/true);
+}
+
+AnyValue Context::load(MemoryObject &MO, uint64_t Offset, Type *ValTy) {
+ return fromBytes(
+ MO.getBytes().slice(Offset, getEffectiveTypeStoreSize(ValTy)), ValTy);
+}
+
+void Context::store(MemoryObject &MO, uint64_t Offset, const AnyValue &Val,
+ Type *ValTy) {
+ toBytes(Val, ValTy,
+ MO.getBytes().slice(Offset, getEffectiveTypeStoreSize(ValTy)));
+}
+
+void Context::storeRawBytes(MemoryObject &MO, uint64_t Offset, const void *Data,
+ uint64_t Size) {
+ for (uint64_t I = 0; I != Size; ++I)
+ MO[Offset + I] = Byte::concrete(static_cast<const uint8_t *>(Data)[I]);
+}
+
MemoryObject::~MemoryObject() = default;
MemoryObject::MemoryObject(uint64_t Addr, uint64_t Size, StringRef Name,
unsigned AS, MemInitKind InitKind)
@@ -111,13 +372,13 @@ MemoryObject::MemoryObject(uint64_t Addr, uint64_t Size, StringRef Name,
: MemoryObjectState::Dead) {
switch (InitKind) {
case MemInitKind::Zeroed:
- Bytes.resize(Size, Byte{0, ByteKind::Concrete});
+ Bytes.resize(Size, Byte::concrete(0));
break;
case MemInitKind::Uninitialized:
- Bytes.resize(Size, Byte{0, ByteKind::Undef});
+ Bytes.resize(Size, Byte::undef());
break;
case MemInitKind::Poisoned:
- Bytes.resize(Size, Byte{0, ByteKind::Poison});
+ Bytes.resize(Size, Byte::poison());
break;
}
}
@@ -175,38 +436,22 @@ BasicBlock *Context::getTargetBlock(const Pointer &Ptr) {
return It->second.first;
}
-void MemoryObject::markAsFreed() {
- State = MemoryObjectState::Freed;
- Bytes.clear();
+uint64_t Context::getEffectiveTypeAllocSize(Type *Ty) {
+ TypeSize Size = DL.getTypeAllocSize(Ty);
+ if (Size.isScalable())
+ return Size.getKnownMinValue() * VScale;
+ return Size.getFixedValue();
}
-
-void MemoryObject::writeRawBytes(uint64_t Offset, const void *Data,
- uint64_t Length) {
- assert(SaturatingAdd(Offset, Length) <= Size && "Write out of bounds");
- const uint8_t *ByteData = static_cast<const uint8_t *>(Data);
- for (uint64_t I = 0; I < Length; ++I)
- Bytes[Offset + I].set(ByteData[I]);
+uint64_t Context::getEffectiveTypeStoreSize(Type *Ty) {
+ TypeSize Size = DL.getTypeStoreSize(Ty);
+ if (Size.isScalable())
+ return Size.getKnownMinValue() * VScale;
+ return Size.getFixedValue();
}
-void MemoryObject::writeInteger(uint64_t Offset, const APInt &Int,
- const DataLayout &DL) {
- uint64_t BitWidth = Int.getBitWidth();
- uint64_t IntSize = divideCeil(BitWidth, 8);
- assert(SaturatingAdd(Offset, IntSize) <= Size && "Write out of bounds");
- for (uint64_t I = 0; I < IntSize; ++I) {
- uint64_t ByteIndex = DL.isLittleEndian() ? I : (IntSize - 1 - I);
- uint64_t Bits = std::min(BitWidth - ByteIndex * 8, uint64_t(8));
- Bytes[Offset + I].set(Int.extractBitsAsZExtValue(Bits, ByteIndex * 8));
- }
-}
-void MemoryObject::writeFloat(uint64_t Offset, const APFloat &Float,
- const DataLayout &DL) {
- writeInteger(Offset, Float.bitcastToAPInt(), DL);
-}
-void MemoryObject::writePointer(uint64_t Offset, const Pointer &Ptr,
- const DataLayout &DL) {
- writeInteger(Offset, Ptr.address(), DL);
- // TODO: provenance
+void MemoryObject::markAsFreed() {
+ State = MemoryObjectState::Freed;
+ Bytes.clear();
}
} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 25ba940323ebf..625b214391c8d 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -14,6 +14,7 @@
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/IR/Module.h"
#include <map>
+#include <random>
namespace llvm::ubi {
@@ -41,6 +42,11 @@ enum class MemoryObjectState {
Freed,
};
+enum class UndefValueBehavior {
+ NonDeterministic, // Each use of the undef value can yield different results.
+ Zero, // All uses of the undef value yield zero.
+};
+
class MemoryObject : public RefCountedBase<MemoryObject> {
uint64_t Address;
uint64_t Size;
@@ -65,6 +71,7 @@ class MemoryObject : public RefCountedBase<MemoryObject> {
StringRef getName() const { return Name; }
unsigned getAddressSpace() const { return AS; }
MemoryObjectState getState() const { return State; }
+ void setState(MemoryObjectState S) { State = S; }
bool isConstant() const { return IsConstant; }
void setIsConstant(bool C) { IsConstant = C; }
@@ -76,10 +83,8 @@ class MemoryObject : public RefCountedBase<MemoryObject> {
assert(Offset < Size && "Offset out of bounds");
return Bytes[Offset];
}
- void writeRawBytes(uint64_t Offset, const void *Data, uint64_t Length);
- void writeInteger(uint64_t Offset, const APInt &Int, const DataLayout &DL);
- void writeFloat(uint64_t Offset, const APFloat &Float, const DataLayout &DL);
- void writePointer(uint64_t Offset, const Pointer &Ptr, const DataLayout &DL);
+ ArrayRef<Byte> getBytes() const { return Bytes; }
+ MutableArrayRef<Byte> getBytes() { return Bytes; }
void markAsFreed();
};
@@ -126,6 +131,9 @@ class Context {
uint32_t VScale = 4;
uint32_t MaxSteps = 0;
uint32_t MaxStackDepth = 256;
+ UndefValueBehavior UndefBehavior = UndefValueBehavior::NonDeterministic;
+
+ std::mt19937_64 Rng;
// Memory
uint64_t UsedMem = 0;
@@ -141,6 +149,10 @@ class Context {
// precisely after we make ptrtoint have the implicit side-effect of exposing
// the provenance.
std::map<uint64_t, IntrusiveRefCntPtr<MemoryObject>> MemoryObjects;
+ AnyValue fromBytes(ArrayRef<Byte> Bytes, Type *Ty, uint32_t &OffsetInBits,
+ bool CheckPaddingBits);
+ void toBytes(const AnyValue &Val, Type *Ty, uint32_t &OffsetInBits,
+ MutableArrayRef<Byte> Bytes, bool PaddingBits);
// Constants
// Use std::map to avoid iterator/reference invalidation.
@@ -171,6 +183,8 @@ class Context {
uint32_t getVScale() const { return VScale; }
uint32_t getMaxSteps() const { return MaxSteps; }
uint32_t getMaxStackDepth() const { return MaxStackDepth; }
+ void setUndefValueBehavior(UndefValueBehavior UB) { UndefBehavior = UB; }
+ void reseed(uint32_t Seed) { Rng.seed(Seed); }
LLVMContext &getContext() const { return Ctx; }
const DataLayout &getDataLayout() const { return DL; }
@@ -180,6 +194,8 @@ class Context {
return VScale * EC.getKnownMinValue();
return EC.getFixedValue();
}
+ uint64_t getEffectiveTypeAllocSize(Type *Ty);
+ uint64_t getEffectiveTypeStoreSize(Type *Ty);
const AnyValue &getConstantValue(Constant *C);
IntrusiveRefCntPtr<MemoryObject> allocate(uint64_t Size, uint64_t Align,
@@ -189,6 +205,18 @@ 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);
+ /// Convert byte sequence to an value of the given type. Uninitialized bits
+ /// are flushed according to the options.
+ AnyValue fromBytes(ArrayRef<Byte> Bytes, Type *Ty);
+ /// Convert a value to byte sequence. Padding bits are set to zero.
+ void toBytes(const AnyValue &Val, Type *Ty, MutableArrayRef<Byte> Bytes);
+ /// Direct memory load without checks.
+ AnyValue load(MemoryObject &MO, uint64_t Offset, Type *ValTy);
+ /// Direct memory store without checks.
+ void store(MemoryObject &MO, uint64_t Offset, const AnyValue &Val,
+ Type *ValTy);
+ void storeRawBytes(MemoryObject &MO, uint64_t Offset, const void *Data,
+ uint64_t Size);
Function *getTargetFunction(const Pointer &Ptr);
BasicBlock *getTargetBlock(const Pointer &Ptr);
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 7961f7551cf48..59b5667c6d63c 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -16,10 +16,13 @@
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/InstVisitor.h"
#include "llvm/IR/Operator.h"
+#include "llvm/IR/PatternMatch.h"
#include "llvm/Support/Allocator.h"
namespace llvm::ubi {
+using namespace PatternMatch;
+
enum class FrameState {
// It is about to enter the function.
// Valid transition:
@@ -248,6 +251,84 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
return false;
}
+ /// Check if the upcoming memory access is valid. Returns the offset relative
+ /// to the underlying object if it is valid.
+ std::optional<uint64_t> verifyMemAccess(const MemoryObject &MO,
+ const APInt &Address,
+ uint64_t AccessSize,
+ uint64_t Alignment, bool IsStore) {
+ // 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) {
+ reportImmediateUB("Try to access a dead memory object.");
+ return std::nullopt;
+ }
+
+ assert(isPowerOf2_64(Alignment) && "Alignment should be a power of 2.");
+ if (Address.countr_zero() < Log2_64(Alignment)) {
+ reportImmediateUB("Misaligned memory access.");
+ return std::nullopt;
+ }
+
+ if (AccessSize > MO.getSize() || Address.ult(MO.getAddress())) {
+ reportImmediateUB("Memory access is out of bounds.");
+ return std::nullopt;
+ }
+
+ APInt Offset = Address - MO.getAddress();
+
+ if (Offset.ugt(MO.getSize() - AccessSize)) {
+ reportImmediateUB("Memory access is out of bounds.");
+ return std::nullopt;
+ }
+
+ return Offset.getZExtValue();
+ }
+
+ AnyValue load(const AnyValue &Ptr, uint64_t Align, Type *ValTy) {
+ if (Ptr.isPoison()) {
+ reportImmediateUB("Invalid memory access with a poison pointer.");
+ 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), Align,
+ /*IsStore=*/false)) {
+ // Load from a dead stack object yields poison value.
+ if (MO->getState() == MemoryObjectState::Dead)
+ return AnyValue::getPoisonValue(Ctx, ValTy);
+
+ return Ctx.load(*MO, *Offset, ValTy);
+ }
+ return AnyValue::getPoisonValue(Ctx, ValTy);
+ }
+
+ void store(const AnyValue &Ptr, uint64_t Align, const AnyValue &Val,
+ Type *ValTy) {
+ if (Ptr.isPoison()) {
+ reportImmediateUB("Invalid memory access with a poison pointer.");
+ return;
+ }
+ auto &PtrVal = Ptr.asPointer();
+ auto *MO = PtrVal.getMemoryObject();
+ if (!MO) {
+ reportImmediateUB("Invalid memory access via a pointer with nullary provenance.");
+ return;
+ }
+ // TODO: pointer capability check
+ if (auto Offset = verifyMemAccess(
+ *MO, PtrVal.address(), Ctx.getEffectiveTypeStoreSize(ValTy), Align,
+ /*IsStore=*/true))
+ Ctx.store(*MO, *Offset, Val, ValTy);
+ }
+
AnyValue computePtrAdd(const Pointer &Ptr, const APInt &Offset,
GEPNoWrapFlags Flags, AnyValue &AccumulatedOffset) {
if (Offset.isZero())
@@ -438,6 +519,23 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
}
// TODO: handle llvm.assume with operand bundles
return AnyValue();
+ case Intrinsic::lifetime_start:
+ case Intrinsic::lifetime_end: {
+ auto *Ptr = CB.getArgOperand(0);
+ if (isa<PoisonValue>(Ptr))
+ return AnyValue();
+ auto *MO = getValue(Ptr).asPointer().getMemoryObject();
+ assert(MO && "Memory object accessed by lifetime intrinsic should be "
+ "always valid.");
+ if (IID == Intrinsic::lifetime_start) {
+ MO->setState(MemoryObjectState::Alive);
+ fill(MO->getBytes(), Byte::undef());
+ } else {
+ MO->setState(MemoryObjectState::Dead);
+ fill(MO->getBytes(), Byte::poison());
+ }
+ return AnyValue();
+ }
default:
Handler.onUnrecognizedInstruction(CB);
Status = false;
@@ -799,8 +897,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
}
void visitAllocaInst(AllocaInst &AI) {
- uint64_t AllocSize =
- DL.getTypeAllocSize(AI.getAllocatedType()).getFixedValue();
+ uint64_t AllocSize = Ctx.getEffectiveTypeAllocSize(AI.getAllocatedType());
if (AI.isArrayAllocation()) {
auto &Size = getValue(AI.getArraySize());
if (Size.isPoison()) {
@@ -821,10 +918,14 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
return;
}
}
- // FIXME: If it is used by llvm.lifetime.start, it should be initially dead.
+ // If it is used by llvm.lifetime.start, it should be initially dead.
+ bool IsInitiallyDead = any_of(AI.users(), [](User *U) {
+ return match(U, m_Intrinsic<Intrinsic::lifetime_start>());
+ });
auto Obj = Ctx.allocate(AllocSize, AI.getPointerAlignment(DL).value(),
AI.getName(), AI.getAddressSpace(),
- MemInitKind::Uninitialized);
+ IsInitiallyDead ? MemInitKind::Poisoned
+ : MemInitKind::Uninitialized);
if (!Obj) {
reportError("Insufficient stack space.");
return;
@@ -920,6 +1021,24 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
});
}
+ void visitLoadInst(LoadInst &LI) {
+ auto RetVal = load(getValue(LI.getPointerOperand()), LI.getAlign().value(),
+ LI.getType());
+ // TODO: track volatile loads
+ // TODO: handle metadata
+ setResult(LI, std::move(RetVal));
+ }
+
+ void visitStoreInst(StoreInst &SI) {
+ auto &Ptr = getValue(SI.getPointerOperand());
+ auto &Val = getValue(SI.getValueOperand());
+ // TODO: track volatile stores
+ // TODO: handle metadata
+ store(Ptr, SI.getAlign().value(), Val, SI.getValueOperand()->getType());
+ if (Status)
+ Status &= Handler.onInstructionExecuted(SI, AnyValue());
+ }
+
void visitInstruction(Instruction &I) {
Handler.onUnrecognizedInstruction(I);
Status = false;
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index 6add6e7f06304..b6ce06af19f54 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -21,26 +21,55 @@ class MemoryObject;
class Context;
class AnyValue;
-enum class ByteKind : uint8_t {
- // A concrete byte with a known value.
- Concrete,
- // A uninitialized byte. Each load from an uninitialized byte yields
- // a nondeterministic value.
- Undef,
- // A poisoned byte. It occurs when the program stores a poison value to
- // memory,
- // or when a memory object is dead.
- Poison,
-};
-
+/// Representation of a byte in memory.
+/// How to interpret the byte per bit:
+/// - If the concrete mask bit is 0, the bit is either undef or poison. The
+/// value bit indicates whether it is undef.
+/// - If the concrete mask bit is 1, the bit is a concrete value. The value bit
+/// stores the concrete bit value.
struct Byte {
+ uint8_t ConcreteMask;
uint8_t Value;
- ByteKind Kind : 2;
- // TODO: provenance
+ // TODO: captured capabilities of pointers.
+
+ static Byte poison() { return Byte{0, 0}; }
+ static Byte undef() { return Byte{0, 255}; }
+ static Byte concrete(uint8_t Val) { return Byte{255, Val}; }
+
+ void zeroBits(uint8_t Mask) {
+ ConcreteMask |= Mask;
+ Value &= ~Mask;
+ }
+
+ void poisonBits(uint8_t Mask) {
+ ConcreteMask &= ~Mask;
+ Value &= ~Mask;
+ }
+
+ void undefBits(uint8_t Mask) {
+ ConcreteMask &= ~Mask;
+ Value |= Mask;
+ }
+
+ void writeBits(uint8_t Mask, uint8_t Val) {
+ ConcreteMask |= Mask;
+ Value = (Value & ~Mask) | (Val & Mask);
+ }
+
+ /// Returns a logical byte that is part of two adjacent bytes.
+ /// Example with ShAmt = 5:
+ /// | Byte0 | Byte1 |
+ /// LSB | 0 1 0 1 0 1 0 1 | 0 0 0 0 1 1 1 1 | MSB
+ /// Result = | 1 0 1 0 0 0 0 1 |
+ Byte fshr(const Byte &High, uint32_t ShAmt) const {
+ return Byte{static_cast<uint8_t>(
+ (ConcreteMask | (High.ConcreteMask << 8)) >> ShAmt),
+ static_cast<uint8_t>((Value | (High.Value << 8)) >> ShAmt)};
+ }
- void set(uint8_t V) {
- Value = V;
- Kind = ByteKind::Concrete;
+ Byte lshr(uint8_t Shift) const {
+ return Byte{static_cast<uint8_t>(ConcreteMask >> Shift),
+ static_cast<uint8_t>(Value >> Shift)};
}
};
diff --git a/llvm/tools/llubi/llubi.cpp b/llvm/tools/llubi/llubi.cpp
index 1d2d4dc050b5d..de76a7e64c27b 100644
--- a/llvm/tools/llubi/llubi.cpp
+++ b/llvm/tools/llubi/llubi.cpp
@@ -74,6 +74,19 @@ static cl::opt<unsigned>
VScale("vscale", cl::desc("The value of llvm.vscale (default = 4)"),
cl::value_desc("N"), cl::init(4), cl::cat(InterpreterCategory));
+static cl::opt<unsigned>
+ Seed("seed",
+ cl::desc("Random seed for non-deterministic behavior (default = 0)"),
+ cl::value_desc("N"), cl::init(0), cl::cat(InterpreterCategory));
+
+cl::opt<ubi::UndefValueBehavior> UndefBehavior(
+ "", cl::desc("Choose undef value behavior:"),
+ cl::values(clEnumVal(ubi::UndefValueBehavior::NonDeterministic,
+ "Each load of an uninitialized byte yields a freshly "
+ "random value."),
+ clEnumVal(ubi::UndefValueBehavior::Zero,
+ "All uses of an uninitialized byte yield zero.")));
+
class VerboseEventHandler : public ubi::EventHandler {
public:
bool onInstructionExecuted(Instruction &I,
@@ -164,6 +177,8 @@ int main(int argc, char **argv) {
Ctx.setVScale(VScale);
Ctx.setMaxSteps(MaxSteps);
Ctx.setMaxStackDepth(MaxStackDepth);
+ Ctx.setUndefValueBehavior(UndefBehavior);
+ Ctx.reseed(Seed);
if (!Ctx.initGlobalValues()) {
WithColor::error() << "Failed to initialize global values (e.g., the "
@@ -182,8 +197,8 @@ int main(int argc, char **argv) {
}
TargetLibraryInfo TLI(Ctx.getTLIImpl());
Type *IntTy = IntegerType::get(Ctx.getContext(), TLI.getIntSize());
- auto *MainFuncTy = FunctionType::get(
- IntTy, {IntTy, PointerType::getUnqual(Ctx.getContext())}, false);
+ Type *PtrTy = PointerType::getUnqual(Ctx.getContext());
+ auto *MainFuncTy = FunctionType::get(IntTy, {IntTy, PtrTy}, false);
SmallVector<ubi::AnyValue> Args;
if (EntryFn->getFunctionType() == MainFuncTy) {
Args.push_back(
@@ -206,8 +221,8 @@ int main(int argc, char **argv) {
return 1;
}
ubi::Pointer ArgPtr = Ctx.deriveFromMemoryObject(ArgvStrMem);
- ArgvStrMem->writeRawBytes(0, Arg.c_str(), Arg.length());
- ArgvPtrsMem->writePointer(Idx * PtrSize, ArgPtr, Ctx.getDataLayout());
+ Ctx.storeRawBytes(*ArgvStrMem, 0, Arg.c_str(), Arg.length());
+ Ctx.store(*ArgvPtrsMem, Idx * PtrSize, ArgPtr, PtrTy);
}
Args.push_back(Ctx.deriveFromMemoryObject(ArgvPtrsMem));
} else if (!EntryFn->arg_empty()) {
>From c1d67b96883394d3b0b10928d1db372b7a401ce8 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sat, 21 Feb 2026 00:30:32 +0800
Subject: [PATCH 2/6] [llubi] Update offset after loading poison bits
---
llvm/test/tools/llubi/loadstore_be.ll | 9 +++++++++
llvm/test/tools/llubi/loadstore_le.ll | 9 +++++++++
llvm/tools/llubi/lib/Context.cpp | 6 ++++--
llvm/tools/llubi/lib/Interpreter.cpp | 9 ++++++---
llvm/tools/llubi/lib/Value.h | 8 ++++----
5 files changed, 32 insertions(+), 9 deletions(-)
diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index 0b78536cd808f..4e194a9703356 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -86,6 +86,11 @@ define void @main() {
store [2 x i32] [i32 1, i32 2], ptr %alloc_array
%val26 = load [2 x i32], ptr %alloc_array
+ %alloc_i1_vec = alloca <4 x i1>
+ store <4 x i1> <i1 1, i1 0, i1 poison, i1 0>, ptr %alloc_i1_vec
+ %val27 = load <4 x i1>, ptr %alloc_i1_vec
+ %val28 = load i8, ptr %alloc_i1_vec
+
ret void
}
; CHECK: Entering function: main
@@ -149,5 +154,9 @@ define void @main() {
; CHECK-NEXT: %alloc_array = alloca [2 x i32], align 4 => ptr 0x70 [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: 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: 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 62caea6aceacc..d54a54fc8d3f2 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -87,6 +87,11 @@ define void @main() {
store [2 x i32] [i32 1, i32 2], ptr %alloc_array
%val26 = load [2 x i32], ptr %alloc_array
+ %alloc_i1_vec = alloca <4 x i1>
+ store <4 x i1> <i1 1, i1 0, i1 poison, i1 0>, ptr %alloc_i1_vec
+ %val27 = load <4 x i1>, ptr %alloc_i1_vec
+ %val28 = load i8, ptr %alloc_i1_vec
+
ret void
}
; CHECK: Entering function: main
@@ -151,5 +156,9 @@ define void @main() {
; CHECK-NEXT: %alloc_array = alloca [2 x i32], align 4 => ptr 0x70 [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: 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: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 1e7b0dd723dc2..631826fd76143 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -124,12 +124,14 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
LogicalByte = Bytes[BitsStart / 8].lshr(BitsStart % 8);
else
LogicalByte =
- Bytes[BitsStart / 8].fshr(Bytes[BitsEnd / 8], BitsStart % 8);
+ Byte::fshr(Bytes[BitsStart / 8], Bytes[BitsEnd / 8], BitsStart % 8);
uint32_t Mask = (1U << NumBitsInByte) - 1;
// If any of the bits in the byte is poison, the whole value is poison.
- if (~LogicalByte.ConcreteMask & ~LogicalByte.Value & Mask)
+ if (~LogicalByte.ConcreteMask & ~LogicalByte.Value & Mask) {
+ OffsetInBits = NewOffsetInBits;
return AnyValue::poison();
+ }
uint8_t RandomBits = 0;
if (UndefBehavior == UndefValueBehavior::NonDeterministic &&
(~LogicalByte.ConcreteMask & Mask)) {
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 59b5667c6d63c..19493c06163a8 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -260,7 +260,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
// 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.");
return std::nullopt;
}
@@ -294,7 +295,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
auto &PtrVal = Ptr.asPointer();
auto *MO = PtrVal.getMemoryObject();
if (!MO) {
- reportImmediateUB("Invalid memory access via a pointer with nullary provenance.");
+ reportImmediateUB(
+ "Invalid memory access via a pointer with nullary provenance.");
return AnyValue::getPoisonValue(Ctx, ValTy);
}
// TODO: pointer capability check
@@ -319,7 +321,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
auto &PtrVal = Ptr.asPointer();
auto *MO = PtrVal.getMemoryObject();
if (!MO) {
- reportImmediateUB("Invalid memory access via a pointer with nullary provenance.");
+ reportImmediateUB(
+ "Invalid memory access via a pointer with nullary provenance.");
return;
}
// TODO: pointer capability check
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index b6ce06af19f54..b4686160ea8b8 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -58,13 +58,13 @@ struct Byte {
/// Returns a logical byte that is part of two adjacent bytes.
/// Example with ShAmt = 5:
- /// | Byte0 | Byte1 |
+ /// | Low | High |
/// LSB | 0 1 0 1 0 1 0 1 | 0 0 0 0 1 1 1 1 | MSB
/// Result = | 1 0 1 0 0 0 0 1 |
- Byte fshr(const Byte &High, uint32_t ShAmt) const {
+ static Byte fshr(const Byte &Low, const Byte &High, uint32_t ShAmt) {
return Byte{static_cast<uint8_t>(
- (ConcreteMask | (High.ConcreteMask << 8)) >> ShAmt),
- static_cast<uint8_t>((Value | (High.Value << 8)) >> ShAmt)};
+ (Low.ConcreteMask | (High.ConcreteMask << 8)) >> ShAmt),
+ static_cast<uint8_t>((Low.Value | (High.Value << 8)) >> ShAmt)};
}
Byte lshr(uint8_t Shift) const {
>From 3f276f08e5b23373fd22532d8f84b0a7f9e4bf24 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sat, 21 Feb 2026 01:39:31 +0800
Subject: [PATCH 3/6] [llubi] Fix build error
---
llvm/tools/llubi/lib/Context.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 631826fd76143..46778949454e6 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -136,8 +136,8 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
if (UndefBehavior == UndefValueBehavior::NonDeterministic &&
(~LogicalByte.ConcreteMask & Mask)) {
// This byte contains undef bits.
- std::uniform_int_distribution<uint8_t> Distrib;
- RandomBits = Distrib(Rng);
+ std::uniform_int_distribution<uint32_t> Distrib(0, 255);
+ RandomBits = static_cast<uint8_t>(Distrib(Rng));
}
uint8_t ActualBits = ((LogicalByte.Value & LogicalByte.ConcreteMask) |
(RandomBits & ~LogicalByte.ConcreteMask)) &
>From c495ac29200d3c2447bea4b48065797507e6f5c2 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 5 Mar 2026 05:33:12 +0800
Subject: [PATCH 4/6] [llubi] Address review comments.
---
llvm/test/tools/llubi/loadstore_be.ll | 32 +++++++++++---
llvm/test/tools/llubi/loadstore_le.ll | 30 ++++++++++---
llvm/tools/llubi/lib/Context.cpp | 63 +++++++++++++++++----------
llvm/tools/llubi/lib/Context.h | 14 +++++-
llvm/tools/llubi/lib/Interpreter.cpp | 36 ++++++++-------
5 files changed, 117 insertions(+), 58 deletions(-)
diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index 4e194a9703356..6e6d738b002ca 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -91,6 +91,16 @@ define void @main() {
%val27 = load <4 x i1>, ptr %alloc_i1_vec
%val28 = load i8, ptr %alloc_i1_vec
+ %alloc_padding = alloca i31
+ store i32 0, ptr %alloc_padding
+
+ %alloc_padding_vec = alloca i64
+ store { <6 x i5>, i32 } { <6 x i5> zeroinitializer, i32 -1}, ptr %alloc_padding_vec
+ %load_agg = load { <6 x i5>, i32 }, ptr %alloc_padding_vec
+ %load_vec = load <6 x i5>, ptr %alloc_padding_vec
+ %load_int_non_zero_padding = load i33, ptr %alloc_padding_vec
+ %load_vec_non_zero_padding = load <3 x i11>, ptr %alloc_padding_vec
+
ret void
}
; CHECK: Entering function: main
@@ -107,8 +117,8 @@ define void @main() {
; CHECK-NEXT: %val6 = load <4 x i8>, ptr %alloc, align 4 => { i8 7, i8 6, i8 9, i8 8 }
; CHECK-NEXT: %val7 = load <8 x i4>, ptr %alloc, align 4 => { i4 0, i4 7, i4 0, i4 6, i4 0, i4 -7, i4 0, i4 -8 }
; CHECK-NEXT: store <3 x i3> <i3 1, i3 2, i3 3>, ptr %alloc, align 2
-; CHECK-NEXT: %val8 = load <16 x i1>, ptr %alloc, align 2 => { T, F, F, F, F, T, F, F, F, T, F, T, F, F, T, T }
-; CHECK-NEXT: %val9 = load <16 x i1>, ptr %alloc, align 2 => { F, F, T, F, F, T, F, F, F, T, F, T, F, F, T, T }
+; CHECK-NEXT: %val8 = load <16 x i1>, ptr %alloc, align 2 => { F, F, F, F, F, F, F, F, F, T, F, T, F, F, T, T }
+; CHECK-NEXT: %val9 = load <16 x i1>, ptr %alloc, align 2 => { F, F, F, F, F, F, F, F, F, T, F, T, F, F, T, T }
; CHECK-NEXT: store <8 x i3> <i3 0, i3 1, i3 2, i3 3, i3 -4, i3 -3, i3 -2, i3 -1>, ptr %alloc, align 4
; CHECK-NEXT: %val_bitcast = load <3 x i8>, ptr %alloc, align 4 => { i8 5, i8 57, i8 119 }
; CHECK-NEXT: store i25 -1, ptr %alloc, align 4
@@ -120,8 +130,8 @@ define void @main() {
; CHECK-NEXT: %alloc_lifetime = alloca i32, align 4 => ptr 0xC [alloc_lifetime]
; 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 -1295355583
-; CHECK-NEXT: %val14 = load i32, ptr %alloc_lifetime, align 4 => i32 -1809495666
+; CHECK-NEXT: %val13 = load i32, ptr %alloc_lifetime, align 4 => i32 -1744110296
+; CHECK-NEXT: %val14 = load i32, ptr %alloc_lifetime, align 4 => i32 1822494346
; CHECK-NEXT: store i32 77, ptr %alloc_lifetime, align 4
; CHECK-NEXT: %val15 = load i32, ptr %alloc_lifetime, align 4 => i32 77
; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %alloc_lifetime)
@@ -139,8 +149,8 @@ define void @main() {
; CHECK-NEXT: %alloc_struct = alloca %struct, align 8 => ptr 0x30 [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 281483653031312
-; CHECK-NEXT: %val21 = load i64, ptr %alloc_struct, align 8 => i64 281487549378445
+; CHECK-NEXT: %val20 = load i64, ptr %alloc_struct, align 8 => i64 281486375577815
+; CHECK-NEXT: %val21 = load i64, ptr %alloc_struct, align 8 => i64 281485466753262
; CHECK-NEXT: %alloc_struct_packed = alloca %struct.packed, align 8 => ptr 0x40 [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 }
@@ -156,7 +166,15 @@ define void @main() {
; 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: 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: %val27 = load <4 x i1>, ptr %alloc_i1_vec, align 1 => { F, F, F, 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: store i32 0, ptr %alloc_padding, align 4
+; CHECK-NEXT: %alloc_padding_vec = alloca i64, align 8 => ptr 0x80 [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 255, i11 0, i11 0 }
; 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 d54a54fc8d3f2..c876337d3f2a7 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -92,6 +92,16 @@ define void @main() {
%val27 = load <4 x i1>, ptr %alloc_i1_vec
%val28 = load i8, ptr %alloc_i1_vec
+ %alloc_padding = alloca i31
+ store i32 0, ptr %alloc_padding
+
+ %alloc_padding_vec = alloca i64
+ store { <6 x i5>, i32 } { <6 x i5> zeroinitializer, i32 -1}, ptr %alloc_padding_vec
+ %load_agg = load { <6 x i5>, i32 }, ptr %alloc_padding_vec
+ %load_vec = load <6 x i5>, ptr %alloc_padding_vec
+ %load_int_non_zero_padding = load i33, ptr %alloc_padding_vec
+ %load_vec_non_zero_padding = load <3 x i11>, ptr %alloc_padding_vec
+
ret void
}
; CHECK: Entering function: main
@@ -108,8 +118,8 @@ define void @main() {
; CHECK-NEXT: %val6 = load <4 x i8>, ptr %alloc, align 4 => { i8 7, i8 6, i8 9, i8 8 }
; CHECK-NEXT: %val7 = load <8 x i4>, ptr %alloc, align 4 => { i4 7, i4 0, i4 6, i4 0, i4 -7, i4 0, i4 -8, i4 0 }
; CHECK-NEXT: store <3 x i3> <i3 1, i3 2, i3 3>, ptr %alloc, align 2
-; CHECK-NEXT: %val8 = load <16 x i1>, ptr %alloc, align 2 => { T, F, F, F, T, F, T, T, F, F, T, F, F, F, F, T }
-; CHECK-NEXT: %val9 = load <16 x i1>, ptr %alloc, align 2 => { T, F, F, F, T, F, T, T, F, F, T, F, F, T, F, F }
+; CHECK-NEXT: %val8 = load <16 x i1>, ptr %alloc, align 2 => { T, F, F, F, T, F, T, T, F, F, F, F, F, F, F, F }
+; CHECK-NEXT: %val9 = load <16 x i1>, ptr %alloc, align 2 => { T, F, F, F, T, F, T, T, F, F, F, F, F, F, F, F }
; CHECK-NEXT: store <8 x i3> <i3 0, i3 1, i3 2, i3 3, i3 -4, i3 -3, i3 -2, i3 -1>, ptr %alloc, align 4
; CHECK-NEXT: %val_bitcast = load <3 x i8>, ptr %alloc, align 4 => { i8 -120, i8 -58, i8 -6 }
; CHECK-NEXT: store i25 -1, ptr %alloc, align 4
@@ -122,8 +132,8 @@ define void @main() {
; CHECK-NEXT: %alloc_lifetime = alloca i32, align 4 => ptr 0xC [alloc_lifetime]
; 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 -1295355583
-; CHECK-NEXT: %val14 = load i32, ptr %alloc_lifetime, align 4 => i32 -1809495666
+; CHECK-NEXT: %val13 = load i32, ptr %alloc_lifetime, align 4 => i32 -1744110296
+; CHECK-NEXT: %val14 = load i32, ptr %alloc_lifetime, align 4 => i32 1822494346
; CHECK-NEXT: store i32 77, ptr %alloc_lifetime, align 4
; CHECK-NEXT: %val15 = load i32, ptr %alloc_lifetime, align 4 => i32 77
; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %alloc_lifetime)
@@ -141,8 +151,8 @@ define void @main() {
; CHECK-NEXT: %alloc_struct = alloca %struct, align 8 => ptr 0x30 [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 371025319710294017
-; CHECK-NEXT: %val21 = load i64, ptr %alloc_struct, align 8 => i64 -1341035243900895231
+; CHECK-NEXT: %val20 = load i64, ptr %alloc_struct, align 8 => i64 -6382470561775091711
+; CHECK-NEXT: %val21 = load i64, ptr %alloc_struct, align 8 => i64 8160901778997641217
; CHECK-NEXT: %alloc_struct_packed = alloca %struct.packed, align 8 => ptr 0x40 [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 }
@@ -160,5 +170,13 @@ define void @main() {
; 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: store i32 0, ptr %alloc_padding, align 4
+; CHECK-NEXT: %alloc_padding_vec = alloca i64, align 8 => ptr 0x80 [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: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 46778949454e6..cfe96625b2c09 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -112,14 +112,17 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
NewOffsetInBits = alignTo(NewOffsetInBits, 8);
bool NeedsPadding = NewOffsetInBits != OffsetInBits + NumBits;
uint32_t NumBitsToExtract = NewOffsetInBits - OffsetInBits;
- SmallVector<uint64_t> BitsData(alignTo(NumBitsToExtract, 8));
+ SmallVector<uint64_t> RawBits(alignTo(NumBitsToExtract, 8));
for (uint32_t I = 0; I < NumBitsToExtract; I += 8) {
+ // Try to form a 'logical' byte that represents the bits in the range
+ // [BitsStart, BitsEnd].
uint32_t NumBitsInByte = std::min(8U, NumBitsToExtract - I);
uint32_t BitsStart =
OffsetInBits +
(DL.isLittleEndian() ? I : (NumBitsToExtract - NumBitsInByte - I));
uint32_t BitsEnd = BitsStart + NumBitsInByte - 1;
Byte LogicalByte;
+ // Check whether it is a cross-byte access.
if (((BitsStart ^ BitsEnd) & ~7) == 0)
LogicalByte = Bytes[BitsStart / 8].lshr(BitsStart % 8);
else
@@ -142,11 +145,11 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
uint8_t ActualBits = ((LogicalByte.Value & LogicalByte.ConcreteMask) |
(RandomBits & ~LogicalByte.ConcreteMask)) &
Mask;
- BitsData[I / 64] |= static_cast<APInt::WordType>(ActualBits) << (I % 64);
+ RawBits[I / 64] |= static_cast<APInt::WordType>(ActualBits) << (I % 64);
}
OffsetInBits = NewOffsetInBits;
- APInt Bits(NumBitsToExtract, BitsData);
+ APInt Bits(NumBitsToExtract, RawBits);
// Padding bits for non-byte-sized scalar types must be zero.
if (NeedsPadding) {
@@ -167,14 +170,37 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
assert(OffsetInBits % 8 == 0 && "Missing padding bits.");
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
Type *ElemTy = VecTy->getElementType();
- std::vector<AnyValue> ValVec;
+ uint32_t ElemBits = DL.getTypeSizeInBits(ElemTy).getFixedValue();
uint32_t NumElements = getEVL(VecTy->getElementCount());
+ // Check padding bits. <N x iM> acts as if an integer type with N * M bits.
+ uint32_t NewOffsetInBits = OffsetInBits + ElemBits * NumElements;
+ uint32_t AlignedNewOffsetInBits = alignTo(NewOffsetInBits, 8);
+ if (NewOffsetInBits != AlignedNewOffsetInBits) {
+ assert(NewOffsetInBits % 8 != 0 &&
+ AlignedNewOffsetInBits - NewOffsetInBits < 8 &&
+ "Unexpected offset.");
+ // The padding bits are located in the last byte on little-endian systems.
+ // On big-endian systems, the padding bits are located in the first byte.
+ const Byte &PaddingByte =
+ Bytes[(DL.isBigEndian() ? OffsetInBits : NewOffsetInBits) / 8];
+ uint32_t Mask = (~0U << (NewOffsetInBits % 8)) & 255U;
+ // Make sure all high padding bits are zero.
+ if ((PaddingByte.ConcreteMask & ~PaddingByte.Value & Mask) != Mask) {
+ OffsetInBits = AlignedNewOffsetInBits;
+ return AnyValue::getPoisonValue(*this, Ty);
+ }
+ if (DL.isBigEndian())
+ OffsetInBits += AlignedNewOffsetInBits - NewOffsetInBits;
+ }
+
+ std::vector<AnyValue> ValVec;
ValVec.reserve(NumElements);
for (uint32_t I = 0; I != NumElements; ++I)
ValVec.push_back(
fromBytes(Bytes, ElemTy, OffsetInBits, /*CheckPaddingBits=*/false));
if (DL.isBigEndian())
std::reverse(ValVec.begin(), ValVec.end());
+ OffsetInBits = AlignedNewOffsetInBits;
return AnyValue(std::move(ValVec));
}
if (auto *ArrTy = dyn_cast<ArrayType>(Ty)) {
@@ -196,11 +222,7 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
for (uint32_t I = 0; I != NumElements; ++I) {
Type *ElemTy = StructTy->getElementType(I);
TypeSize ElemOffset = Layout->getElementOffset(I);
- OffsetInBits =
- BaseOffsetInBits + (ElemOffset.isScalable()
- ? ElemOffset.getKnownMinValue() * VScale
- : ElemOffset.getFixedValue()) *
- 8;
+ OffsetInBits = BaseOffsetInBits + getEffectiveTypeSize(ElemOffset) * 8;
ValVec.push_back(
fromBytes(Bytes, ElemTy, OffsetInBits, /*CheckPaddingBits=*/true));
}
@@ -234,7 +256,8 @@ void Context::toBytes(const AnyValue &Val, Type *Ty, uint32_t &OffsetInBits,
static_cast<uint8_t>(((1U << NumBitsInByte) - 1)
<< (BitsStart % 8)),
static_cast<uint8_t>(BitsVal << (BitsStart % 8)));
- // Crosses the byte boundary.
+ // If it is a cross-byte access, write the remaining bits to the next
+ // byte.
if (((BitsStart ^ BitsEnd) & ~7) != 0)
Bytes[BitsEnd / 8].writeBits(
static_cast<uint8_t>((1U << (BitsEnd % 8 + 1)) - 1),
@@ -291,8 +314,8 @@ void Context::toBytes(const AnyValue &Val, Type *Ty, uint32_t &OffsetInBits,
if (NewOffsetInBits != OffsetInBits) {
assert(OffsetInBits % 8 != 0 && NewOffsetInBits - OffsetInBits < 8 &&
"Unexpected offset.");
- // Fill remaining bits with undef.
- Bytes[OffsetInBits / 8].undefBits(
+ // Fill remaining bits with zero.
+ Bytes[OffsetInBits / 8].zeroBits(
static_cast<uint8_t>(~0U << (OffsetInBits % 8)));
}
OffsetInBits = NewOffsetInBits;
@@ -321,10 +344,7 @@ void Context::toBytes(const AnyValue &Val, Type *Ty, uint32_t &OffsetInBits,
Type *ElemTy = StructTy->getElementType(I);
TypeSize ElemOffset = Layout->getElementOffset(I);
uint32_t NewOffsetInBits =
- BaseOffsetInBits + (ElemOffset.isScalable()
- ? ElemOffset.getKnownMinValue() * VScale
- : ElemOffset.getFixedValue()) *
- 8;
+ BaseOffsetInBits + getEffectiveTypeSize(ElemOffset) * 8;
FillUndefBytes(NewOffsetInBits);
toBytes(Val.asAggregate()[I], ElemTy, OffsetInBits, Bytes,
/*PaddingBits=*/true);
@@ -439,16 +459,11 @@ BasicBlock *Context::getTargetBlock(const Pointer &Ptr) {
}
uint64_t Context::getEffectiveTypeAllocSize(Type *Ty) {
- TypeSize Size = DL.getTypeAllocSize(Ty);
- if (Size.isScalable())
- return Size.getKnownMinValue() * VScale;
- return Size.getFixedValue();
+ // FIXME: It is incorrect for overaligned scalable vector types.
+ return getEffectiveTypeSize(DL.getTypeAllocSize(Ty));
}
uint64_t Context::getEffectiveTypeStoreSize(Type *Ty) {
- TypeSize Size = DL.getTypeStoreSize(Ty);
- if (Size.isScalable())
- return Size.getKnownMinValue() * VScale;
- return Size.getFixedValue();
+ return getEffectiveTypeSize(DL.getTypeStoreSize(Ty));
}
void MemoryObject::markAsFreed() {
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 625b214391c8d..a250004b3cb54 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -189,11 +189,21 @@ class Context {
LLVMContext &getContext() const { return Ctx; }
const DataLayout &getDataLayout() const { return DL; }
const TargetLibraryInfoImpl &getTLIImpl() const { return TLIImpl; }
+ /// Get the effective vector length for a vector type.
uint32_t getEVL(ElementCount EC) const {
if (EC.isScalable())
return VScale * EC.getKnownMinValue();
return EC.getFixedValue();
}
+ /// The result is multiplied by VScale for scalable type sizes.
+ uint64_t getEffectiveTypeSize(TypeSize Size) const {
+ if (Size.isScalable())
+ return VScale * Size.getKnownMinValue();
+ return Size.getFixedValue();
+ }
+ /// Returns DL.getTypeAllocSize/getTypeStoreSize for the given type.
+ /// An exception to this is that for scalable vector types, the size is
+ /// computed as if the vector has getEVL(ElementCount) elements.
uint64_t getEffectiveTypeAllocSize(Type *Ty);
uint64_t getEffectiveTypeStoreSize(Type *Ty);
@@ -205,8 +215,8 @@ 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);
- /// Convert byte sequence to an value of the given type. Uninitialized bits
- /// are flushed according to the options.
+ /// Convert byte sequence to a value of the given type. Uninitialized bits are
+ /// flushed according to the options.
AnyValue fromBytes(ArrayRef<Byte> Bytes, Type *Ty);
/// Convert a value to byte sequence. Padding bits are set to zero.
void toBytes(const AnyValue &Val, Type *Ty, MutableArrayRef<Byte> Bytes);
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 19493c06163a8..0e940fe502800 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -255,8 +255,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
/// to the underlying object if it is valid.
std::optional<uint64_t> verifyMemAccess(const MemoryObject &MO,
const APInt &Address,
- uint64_t AccessSize,
- uint64_t Alignment, bool IsStore) {
+ uint64_t AccessSize, Align Alignment,
+ bool IsStore) {
// 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.
@@ -266,8 +266,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
return std::nullopt;
}
- assert(isPowerOf2_64(Alignment) && "Alignment should be a power of 2.");
- if (Address.countr_zero() < Log2_64(Alignment)) {
+ if (Address.countr_zero() < Log2(Alignment)) {
reportImmediateUB("Misaligned memory access.");
return std::nullopt;
}
@@ -287,7 +286,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
return Offset.getZExtValue();
}
- AnyValue load(const AnyValue &Ptr, uint64_t Align, Type *ValTy) {
+ AnyValue load(const AnyValue &Ptr, Align Alignment, Type *ValTy) {
if (Ptr.isPoison()) {
reportImmediateUB("Invalid memory access with a poison pointer.");
return AnyValue::getPoisonValue(Ctx, ValTy);
@@ -300,9 +299,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
return AnyValue::getPoisonValue(Ctx, ValTy);
}
// TODO: pointer capability check
- if (auto Offset = verifyMemAccess(
- *MO, PtrVal.address(), Ctx.getEffectiveTypeStoreSize(ValTy), Align,
- /*IsStore=*/false)) {
+ if (auto Offset =
+ verifyMemAccess(*MO, PtrVal.address(),
+ Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
+ /*IsStore=*/false)) {
// Load from a dead stack object yields poison value.
if (MO->getState() == MemoryObjectState::Dead)
return AnyValue::getPoisonValue(Ctx, ValTy);
@@ -312,7 +312,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
return AnyValue::getPoisonValue(Ctx, ValTy);
}
- void store(const AnyValue &Ptr, uint64_t Align, const AnyValue &Val,
+ void store(const AnyValue &Ptr, Align Alignment, const AnyValue &Val,
Type *ValTy) {
if (Ptr.isPoison()) {
reportImmediateUB("Invalid memory access with a poison pointer.");
@@ -326,9 +326,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
return;
}
// TODO: pointer capability check
- if (auto Offset = verifyMemAccess(
- *MO, PtrVal.address(), Ctx.getEffectiveTypeStoreSize(ValTy), Align,
- /*IsStore=*/true))
+ if (auto Offset =
+ verifyMemAccess(*MO, PtrVal.address(),
+ Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
+ /*IsStore=*/true))
Ctx.store(*MO, *Offset, Val, ValTy);
}
@@ -1001,10 +1002,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
// TODO: Should be documented in LangRef: GEPs with nowrap flags should
// return poison when the type size exceeds index space.
TypeSize Offset = GTI.getSequentialElementStride(DL);
- APInt Scale(IndexBitWidth,
- Offset.isScalable()
- ? Offset.getKnownMinValue() * Ctx.getVScale()
- : Offset.getFixedValue(),
+ APInt Scale(IndexBitWidth, Ctx.getEffectiveTypeSize(Offset),
/*isSigned=*/false, /*implicitTrunc=*/true);
if (!Scale.isZero())
ApplyScaledOffset(getValue(V), Scale);
@@ -1025,8 +1023,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
}
void visitLoadInst(LoadInst &LI) {
- auto RetVal = load(getValue(LI.getPointerOperand()), LI.getAlign().value(),
- LI.getType());
+ auto RetVal =
+ load(getValue(LI.getPointerOperand()), LI.getAlign(), LI.getType());
// TODO: track volatile loads
// TODO: handle metadata
setResult(LI, std::move(RetVal));
@@ -1037,7 +1035,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
auto &Val = getValue(SI.getValueOperand());
// TODO: track volatile stores
// TODO: handle metadata
- store(Ptr, SI.getAlign().value(), Val, SI.getValueOperand()->getType());
+ store(Ptr, SI.getAlign(), Val, SI.getValueOperand()->getType());
if (Status)
Status &= Handler.onInstructionExecuted(SI, AnyValue());
}
>From b63f1b1eacc35e6079eb20e27b09fd9de0ac8a62 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 5 Mar 2026 05:53:00 +0800
Subject: [PATCH 5/6] [llubi] Handle over-aligned arrays
---
.../test/tools/llubi/loadstore_overaligned.ll | 30 +++++++++++++++++
llvm/tools/llubi/lib/Context.cpp | 33 ++++++++++++-------
2 files changed, 51 insertions(+), 12 deletions(-)
create mode 100644 llvm/test/tools/llubi/loadstore_overaligned.ll
diff --git a/llvm/test/tools/llubi/loadstore_overaligned.ll b/llvm/test/tools/llubi/loadstore_overaligned.ll
new file mode 100644
index 0000000000000..91a4eb63d9d2a
--- /dev/null
+++ b/llvm/test/tools/llubi/loadstore_overaligned.ll
@@ -0,0 +1,30 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+; For over-aligned arrays, the padding bytes between elements should be filled with undef.
+
+target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:64:64-i64:64:64"
+
+define void @main() {
+ %alloca = alloca [2 x i32]
+ store <4 x i32> zeroinitializer, ptr %alloca, align 8
+ store [2 x i32] [i32 1, i32 1], ptr %alloca, align 8
+ ; The padding bytes in the middle are filled with undef,
+ ; while the tail padding bytes are still zero-initialized.
+ %load1 = load <4 x i32>, ptr %alloca, align 8
+ %load2 = load <4 x i32>, ptr %alloca, align 8
+ %load3 = load <4 x i32>, ptr %alloca, align 8
+ %load_arr = load [2 x i32], ptr %alloca, align 8
+ ret void
+}
+
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloca = alloca [2 x i32], align 8 => ptr 0x8 [alloca]
+; CHECK-NEXT: store <4 x i32> zeroinitializer, ptr %alloca, align 8
+; CHECK-NEXT: store [2 x i32] [i32 1, i32 1], ptr %alloca, align 8
+; CHECK-NEXT: %load1 = load <4 x i32>, ptr %alloca, align 8 => { i32 1, i32 -1744110296, i32 1, i32 0 }
+; CHECK-NEXT: %load2 = load <4 x i32>, ptr %alloca, align 8 => { i32 1, i32 1822494346, i32 1, i32 0 }
+; CHECK-NEXT: %load3 = load <4 x i32>, ptr %alloca, align 8 => { i32 1, i32 -1486034729, i32 1, i32 0 }
+; CHECK-NEXT: %load_arr = load [2 x i32], ptr %alloca, align 8 => { i32 1, i32 1 }
+; 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 cfe96625b2c09..fe95eef57c2ab 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -205,12 +205,16 @@ AnyValue Context::fromBytes(ArrayRef<Byte> Bytes, Type *Ty,
}
if (auto *ArrTy = dyn_cast<ArrayType>(Ty)) {
Type *ElemTy = ArrTy->getElementType();
+ uint32_t StrideInBits = getEffectiveTypeAllocSize(ElemTy) * 8;
std::vector<AnyValue> ValVec;
uint32_t NumElements = ArrTy->getNumElements();
ValVec.reserve(NumElements);
- for (uint32_t I = 0; I != NumElements; ++I)
+ uint32_t BaseOffsetInBits = OffsetInBits;
+ for (uint32_t I = 0; I != NumElements; ++I) {
+ OffsetInBits = BaseOffsetInBits + I * StrideInBits;
ValVec.push_back(
fromBytes(Bytes, ElemTy, OffsetInBits, /*CheckPaddingBits=*/true));
+ }
return AnyValue(std::move(ValVec));
}
if (auto *StructTy = dyn_cast<StructType>(Ty)) {
@@ -321,25 +325,30 @@ void Context::toBytes(const AnyValue &Val, Type *Ty, uint32_t &OffsetInBits,
OffsetInBits = NewOffsetInBits;
return;
}
+ auto FillUndefBytes = [&](uint32_t NewOffsetInBits) {
+ if (OffsetInBits == NewOffsetInBits)
+ return;
+ // Fill padding bits due to alignment requirement.
+ assert(NewOffsetInBits > OffsetInBits &&
+ "Unexpected negative padding bits!");
+ fill(Bytes.slice(OffsetInBits / 8, (NewOffsetInBits - OffsetInBits) / 8),
+ Byte::undef());
+ OffsetInBits = NewOffsetInBits;
+ };
if (auto *ArrTy = dyn_cast<ArrayType>(Ty)) {
Type *ElemTy = ArrTy->getElementType();
- for (const auto &SubVal : Val.asAggregate())
+ uint32_t CurrentOffsetInBits = OffsetInBits;
+ uint32_t StrideInBits = getEffectiveTypeAllocSize(ElemTy) * 8;
+ for (const auto &SubVal : Val.asAggregate()) {
+ FillUndefBytes(CurrentOffsetInBits);
toBytes(SubVal, ElemTy, OffsetInBits, Bytes, /*PaddingBits=*/true);
+ CurrentOffsetInBits += StrideInBits;
+ }
return;
}
if (auto *StructTy = dyn_cast<StructType>(Ty)) {
auto *Layout = DL.getStructLayout(StructTy);
uint32_t BaseOffsetInBits = OffsetInBits;
- auto FillUndefBytes = [&](uint32_t NewOffsetInBits) {
- if (OffsetInBits == NewOffsetInBits)
- return;
- // Fill padding bits due to alignment requirement.
- assert(NewOffsetInBits > OffsetInBits &&
- "Unexpected negative padding bits!");
- fill(Bytes.slice(OffsetInBits / 8, (NewOffsetInBits - OffsetInBits) / 8),
- Byte::undef());
- OffsetInBits = NewOffsetInBits;
- };
for (uint32_t I = 0, E = Val.asAggregate().size(); I != E; ++I) {
Type *ElemTy = StructTy->getElementType(I);
TypeSize ElemOffset = Layout->getElementOffset(I);
>From 3ebdd689ae06f24b93410c7cb81b3b6a38700823 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sat, 7 Mar 2026 01:55:31 +0800
Subject: [PATCH 6/6] [llubi] Address review comments.
---
llvm/test/tools/llubi/loadstore_be.ll | 14 ++++++++++++--
llvm/test/tools/llubi/loadstore_le.ll | 14 ++++++++++++--
llvm/tools/llubi/lib/Interpreter.cpp | 1 -
3 files changed, 24 insertions(+), 5 deletions(-)
diff --git a/llvm/test/tools/llubi/loadstore_be.ll b/llvm/test/tools/llubi/loadstore_be.ll
index 6e6d738b002ca..2e03939db7859 100644
--- a/llvm/test/tools/llubi/loadstore_be.ll
+++ b/llvm/test/tools/llubi/loadstore_be.ll
@@ -46,6 +46,12 @@ define void @main() {
%val14 = load i32, ptr %alloc_lifetime
store i32 77, ptr %alloc_lifetime
%val15 = load i32, ptr %alloc_lifetime
+ ; Calling lifetime.start on an alive object makes the contents uninitialized again.
+ call void @llvm.lifetime.start.p0(ptr %alloc_lifetime)
+ %val_undef1 = load i32, ptr %alloc_lifetime
+ %val_undef2 = load i32, ptr %alloc_lifetime
+ call void @llvm.lifetime.end.p0(ptr %alloc_lifetime)
+ ; Calling lifetime.end on an already dead object is a no-op.
call void @llvm.lifetime.end.p0(ptr %alloc_lifetime)
; Load of an dead object yields poison value.
%val16 = load i32, ptr %alloc_lifetime
@@ -134,6 +140,10 @@ define void @main() {
; CHECK-NEXT: %val14 = load i32, ptr %alloc_lifetime, align 4 => i32 1822494346
; CHECK-NEXT: store i32 77, ptr %alloc_lifetime, align 4
; CHECK-NEXT: %val15 = load i32, ptr %alloc_lifetime, align 4 => i32 77
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %alloc_lifetime)
+; CHECK-NEXT: %val_undef1 = load i32, ptr %alloc_lifetime, align 4 => i32 -1486034729
+; CHECK-NEXT: %val_undef2 = load i32, ptr %alloc_lifetime, align 4 => i32 1900108014
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %alloc_lifetime)
; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %alloc_lifetime)
; CHECK-NEXT: %val16 = load i32, ptr %alloc_lifetime, align 4 => poison
; CHECK-NEXT: store i32 -524288, ptr %alloc, align 4
@@ -149,8 +159,8 @@ define void @main() {
; CHECK-NEXT: %alloc_struct = alloca %struct, align 8 => ptr 0x30 [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 281486375577815
-; CHECK-NEXT: %val21 = load i64, ptr %alloc_struct, align 8 => i64 281485466753262
+; CHECK-NEXT: %val20 = load i64, ptr %alloc_struct, align 8 => i64 281484800733898
+; CHECK-NEXT: %val21 = load i64, ptr %alloc_struct, align 8 => i64 281484196877349
; CHECK-NEXT: %alloc_struct_packed = alloca %struct.packed, align 8 => ptr 0x40 [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 }
diff --git a/llvm/test/tools/llubi/loadstore_le.ll b/llvm/test/tools/llubi/loadstore_le.ll
index c876337d3f2a7..e1170fb3854a2 100644
--- a/llvm/test/tools/llubi/loadstore_le.ll
+++ b/llvm/test/tools/llubi/loadstore_le.ll
@@ -47,6 +47,12 @@ define void @main() {
%val14 = load i32, ptr %alloc_lifetime
store i32 77, ptr %alloc_lifetime
%val15 = load i32, ptr %alloc_lifetime
+ ; Calling lifetime.start on an alive object makes the contents uninitialized again.
+ call void @llvm.lifetime.start.p0(ptr %alloc_lifetime)
+ %val_undef1 = load i32, ptr %alloc_lifetime
+ %val_undef2 = load i32, ptr %alloc_lifetime
+ call void @llvm.lifetime.end.p0(ptr %alloc_lifetime)
+ ; Calling lifetime.end on an already dead object is a no-op.
call void @llvm.lifetime.end.p0(ptr %alloc_lifetime)
; Load of an dead object yields poison value.
%val16 = load i32, ptr %alloc_lifetime
@@ -136,6 +142,10 @@ define void @main() {
; CHECK-NEXT: %val14 = load i32, ptr %alloc_lifetime, align 4 => i32 1822494346
; CHECK-NEXT: store i32 77, ptr %alloc_lifetime, align 4
; CHECK-NEXT: %val15 = load i32, ptr %alloc_lifetime, align 4 => i32 77
+; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr %alloc_lifetime)
+; CHECK-NEXT: %val_undef1 = load i32, ptr %alloc_lifetime, align 4 => i32 -1486034729
+; CHECK-NEXT: %val_undef2 = load i32, ptr %alloc_lifetime, align 4 => i32 1900108014
+; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %alloc_lifetime)
; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr %alloc_lifetime)
; CHECK-NEXT: %val16 = load i32, ptr %alloc_lifetime, align 4 => poison
; CHECK-NEXT: store i32 -524288, ptr %alloc, align 4
@@ -151,8 +161,8 @@ define void @main() {
; CHECK-NEXT: %alloc_struct = alloca %struct, align 8 => ptr 0x30 [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 -6382470561775091711
-; CHECK-NEXT: %val21 = load i64, ptr %alloc_struct, align 8 => i64 8160901778997641217
+; CHECK-NEXT: %val20 = load i64, ptr %alloc_struct, align 8 => i64 5300370392114921473
+; CHECK-NEXT: %val21 = load i64, ptr %alloc_struct, align 8 => i64 2706826262684499969
; CHECK-NEXT: %alloc_struct_packed = alloca %struct.packed, align 8 => ptr 0x40 [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 }
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 0e940fe502800..dd5530a355538 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -536,7 +536,6 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
fill(MO->getBytes(), Byte::undef());
} else {
MO->setState(MemoryObjectState::Dead);
- fill(MO->getBytes(), Byte::poison());
}
return AnyValue();
}
More information about the llvm-commits
mailing list