[llvm] [llubi] Implement memory manipulation intrinsics (PR #204932)

Zhige Chen via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 22 04:23:42 PDT 2026


https://github.com/nofe1248 updated https://github.com/llvm/llvm-project/pull/204932

>From 902b849527c6fc508ca974e51f83f51837b53620 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Sat, 20 Jun 2026 22:30:12 +0800
Subject: [PATCH 1/2] [llubi] Implement memory manipulation intrinsics

---
 llvm/test/tools/llubi/intr_memory.ll    |  75 ++++++++++++++
 llvm/test/tools/llubi/intr_memory_ub.ll |  57 +++++++++++
 llvm/tools/llubi/lib/Interpreter.cpp    | 124 ++++++++++++++++++++++++
 3 files changed, 256 insertions(+)
 create mode 100644 llvm/test/tools/llubi/intr_memory.ll
 create mode 100644 llvm/test/tools/llubi/intr_memory_ub.ll

diff --git a/llvm/test/tools/llubi/intr_memory.ll b/llvm/test/tools/llubi/intr_memory.ll
new file mode 100644
index 0000000000000..f612ef0fb3faa
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_memory.ll
@@ -0,0 +1,75 @@
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+declare void @llvm.memset.p0.i16(ptr, i8, i16, i1)
+declare void @llvm.memset.inline.p0.i8(ptr, i8, i8, i1)
+declare void @llvm.memcpy.p0.p0.i8(ptr, ptr, i8, i1)
+declare void @llvm.memcpy.inline.p0.p0.i8(ptr, ptr, i8, i1)
+declare void @llvm.memmove.p0.p0.i8(ptr, ptr, i8, i1)
+
+define void @main() {
+  %src = alloca [8 x i8], align 1
+  %dst = alloca [8 x i8], align 1
+  call void @llvm.memset.p0.i16(ptr %src, i8 17, i16 8, i1 false)
+  call void @llvm.memcpy.p0.p0.i8(ptr %dst, ptr %src, i8 8, i1 false)
+
+  %dst2 = getelementptr i8, ptr %dst, i64 2
+  call void @llvm.memset.inline.p0.i8(ptr %dst2, i8 34, i8 3, i1 false)
+
+  %src4 = getelementptr i8, ptr %src, i64 4
+  %dst5 = getelementptr i8, ptr %dst, i64 5
+  call void @llvm.memcpy.inline.p0.p0.i8(ptr %dst5, ptr %src4, i8 2, i1 false)
+
+  %v0 = load i8, ptr %dst, align 1
+  %p1 = getelementptr i8, ptr %dst, i64 1
+  %v1 = load i8, ptr %p1, align 1
+  %p2 = getelementptr i8, ptr %dst, i64 2
+  %v2 = load i8, ptr %p2, align 1
+  %p4 = getelementptr i8, ptr %dst, i64 4
+  %v4 = load i8, ptr %p4, align 1
+  %p5 = getelementptr i8, ptr %dst, i64 5
+  %v5 = load i8, ptr %p5, align 1
+
+  %move = alloca [6 x i8], align 1
+  store i8 1, ptr %move, align 1
+  %m1 = getelementptr i8, ptr %move, i64 1
+  store i8 2, ptr %m1, align 1
+  %m2 = getelementptr i8, ptr %move, i64 2
+  store i8 3, ptr %m2, align 1
+  %m3 = getelementptr i8, ptr %move, i64 3
+  store i8 4, ptr %m3, align 1
+  %m4 = getelementptr i8, ptr %move, i64 4
+  store i8 5, ptr %m4, align 1
+  %m5 = getelementptr i8, ptr %move, i64 5
+  store i8 6, ptr %m5, align 1
+  call void @llvm.memmove.p0.p0.i8(ptr %m1, ptr %move, i8 5, i1 false)
+
+  %mv0 = load i8, ptr %move, align 1
+  %mv1 = load i8, ptr %m1, align 1
+  %mv2 = load i8, ptr %m2, align 1
+  %mv5 = load i8, ptr %m5, align 1
+
+  call void @llvm.memset.p0.i16(ptr poison, i8 0, i16 0, i1 false)
+  call void @llvm.memcpy.p0.p0.i8(ptr poison, ptr poison, i8 0, i1 false)
+
+  ret void
+}
+
+; CHECK: Entering function: main
+; CHECK:   call void @llvm.memset.p0.i16(ptr %src, i8 17, i16 8, i1 false)
+; CHECK:   call void @llvm.memcpy.p0.p0.i8(ptr %dst, ptr %src, i8 8, i1 false)
+; CHECK:   call void @llvm.memset.inline.p0.i8(ptr %dst2, i8 34, i8 3, i1 false)
+; CHECK:   call void @llvm.memcpy.inline.p0.p0.i8(ptr %dst5, ptr %src4, i8 2, i1 false)
+; CHECK:   %v0 = load i8, ptr %dst, align 1 => i8 17
+; CHECK:   %v1 = load i8, ptr %p1, align 1 => i8 17
+; CHECK:   %v2 = load i8, ptr %p2, align 1 => i8 34
+; CHECK:   %v4 = load i8, ptr %p4, align 1 => i8 34
+; CHECK:   %v5 = load i8, ptr %p5, align 1 => i8 17
+; CHECK:   call void @llvm.memmove.p0.p0.i8(ptr %m1, ptr %move, i8 5, i1 false)
+; CHECK:   %mv0 = load i8, ptr %move, align 1 => i8 1
+; CHECK:   %mv1 = load i8, ptr %m1, align 1 => i8 1
+; CHECK:   %mv2 = load i8, ptr %m2, align 1 => i8 2
+; CHECK:   %mv5 = load i8, ptr %m5, align 1 => i8 5
+; CHECK:   call void @llvm.memset.p0.i16(ptr poison, i8 0, i16 0, i1 false)
+; CHECK:   call void @llvm.memcpy.p0.p0.i8(ptr poison, ptr poison, i8 0, i1 false)
+; CHECK:   ret void
+; CHECK: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_memory_ub.ll b/llvm/test/tools/llubi/intr_memory_ub.ll
new file mode 100644
index 0000000000000..c3e301618eddb
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_memory_ub.ll
@@ -0,0 +1,57 @@
+; RUN: sed 's/OP/memcpy_overlap/g' %s | not llubi --verbose 2>&1 | FileCheck %s --check-prefix=OVERLAP
+; RUN: sed 's/OP/memcpy_inline_poison_src/g' %s | not llubi --verbose 2>&1 | FileCheck %s --check-prefix=COPY-SRC
+; RUN: sed 's/OP/memset_poison_dst/g' %s | not llubi --verbose 2>&1 | FileCheck %s --check-prefix=SET-DST
+; RUN: sed 's/OP/memset_poison_len/g' %s | not llubi --verbose 2>&1 | FileCheck %s --check-prefix=SET-LEN
+
+declare void @llvm.memcpy.p0.p0.i8(ptr, ptr, i8, i1)
+declare void @llvm.memcpy.inline.p0.p0.i8(ptr, ptr, i8, i1)
+declare void @llvm.memset.p0.i8(ptr, i8, i8, i1)
+
+define void @main() {
+  call void @OP()
+  ret void
+}
+
+define void @memcpy_overlap() {
+  %buf = alloca [4 x i8], align 1
+  %dst = getelementptr i8, ptr %buf, i64 1
+  call void @llvm.memcpy.p0.p0.i8(ptr %dst, ptr %buf, i8 2, i1 false)
+  ret void
+}
+
+define void @memcpy_inline_poison_src() {
+  %dst = alloca [4 x i8], align 1
+  call void @llvm.memcpy.inline.p0.p0.i8(ptr %dst, ptr poison, i8 1, i1 false)
+  ret void
+}
+
+define void @memset_poison_dst() {
+  call void @llvm.memset.p0.i8(ptr poison, i8 0, i8 1, i1 false)
+  ret void
+}
+
+define void @memset_poison_len() {
+  %dst = alloca [4 x i8], align 1
+  call void @llvm.memset.p0.i8(ptr %dst, i8 0, i8 poison, i1 false)
+  ret void
+}
+
+; OVERLAP: Entering function: main
+; OVERLAP: Entering function: memcpy_overlap
+; OVERLAP: Immediate UB detected: memcpy with overlapping source and destination.
+; OVERLAP: error: Execution of function 'main' failed.
+
+; COPY-SRC: Entering function: main
+; COPY-SRC: Entering function: memcpy_inline_poison_src
+; COPY-SRC: Immediate UB detected: Memory copy intrinsic with poison source pointer.
+; COPY-SRC: error: Execution of function 'main' failed.
+
+; SET-DST: Entering function: main
+; SET-DST: Entering function: memset_poison_dst
+; SET-DST: Immediate UB detected: memset called with poison destination pointer.
+; SET-DST: error: Execution of function 'main' failed.
+
+; SET-LEN: Entering function: main
+; SET-LEN: Entering function: memset_poison_len
+; SET-LEN: Immediate UB detected: memset called with poison length.
+; SET-LEN: error: Execution of function 'main' failed.
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 588f1069c2a80..ac126bbcdcb57 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -19,6 +19,7 @@
 #include "llvm/Analysis/VectorUtils.h"
 #include "llvm/IR/InlineAsm.h"
 #include "llvm/IR/InstVisitor.h"
+#include "llvm/IR/Intrinsics.h"
 #include "llvm/IR/Operator.h"
 #include "llvm/IR/PatternMatch.h"
 #include "llvm/Support/Allocator.h"
@@ -698,6 +699,120 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     return V.asInteger();
   }
 
+  AnyValue callMemTransferIntrinsic(ArrayRef<AnyValue> Args,
+                                    Intrinsic::ID IID) {
+    if (Args[2].isPoison()) {
+      reportImmediateUB() << "Memory transfer intrinsic with poison length.";
+      return AnyValue::poison();
+    }
+
+    const APInt &Length = Args[2].asInteger();
+    if (Length.getActiveBits() > 64) {
+      reportImmediateUB()
+          << "Memory transfer intrinsic length overflows uint64_t.";
+      return AnyValue::poison();
+    }
+
+    const uint64_t Len = Length.getZExtValue();
+    if (Len == 0)
+      return AnyValue();
+
+    if (Args[0].isPoison()) {
+      reportImmediateUB()
+          << "Memory transfer intrinsic with poison destination pointer.";
+      return AnyValue::poison();
+    }
+
+    if (Args[1].isPoison()) {
+      reportImmediateUB()
+          << "Memory copy intrinsic with poison source pointer.";
+      return AnyValue::poison();
+    }
+
+    const Pointer &DstPtr = Args[0].asPointer();
+    const Pointer &SrcPtr = Args[1].asPointer();
+
+    auto [SrcMO, SrcOffset] =
+        verifyMemAccess(SrcPtr, Len, Align(1), /*IsStore=*/false);
+    if (!SrcMO)
+      return AnyValue();
+
+    auto [DstMO, DstOffset] =
+        verifyMemAccess(DstPtr, Len, Align(1), /*IsStore=*/true);
+    if (!DstMO)
+      return AnyValue();
+
+    if (DstMO->isConstant()) {
+      reportImmediateUB() << "Try to write to a constant memory object: "
+                          << DstPtr << ".";
+      return AnyValue::poison();
+    }
+
+    if (IID == Intrinsic::memcpy || IID == Intrinsic::memcpy_inline) {
+      if (SrcMO == DstMO && SrcOffset != DstOffset) {
+        const uint64_t SrcEnd = SrcOffset + Len;
+        const uint64_t DstEnd = DstOffset + Len;
+        if (SrcOffset < DstEnd && DstOffset < SrcEnd) {
+          reportImmediateUB()
+              << "memcpy with overlapping source and destination.";
+          return AnyValue::poison();
+        }
+      }
+    }
+
+    SmallVector<Byte, 16> Tmp;
+    if (SrcMO->getState() == MemoryObjectState::Dead) {
+      Tmp.assign(Len, Byte::poison());
+    } else {
+      ArrayRef<Byte> SrcBytes = SrcMO->getBytes().slice(SrcOffset, Len);
+      Tmp.assign(SrcBytes);
+    }
+    MutableArrayRef<Byte> DstBytes = DstMO->getBytes().slice(DstOffset, Len);
+    copy(Tmp, DstBytes.begin());
+    return AnyValue();
+  }
+
+  AnyValue callMemSetIntrinsic(ArrayRef<AnyValue> Args) {
+    if (Args[2].isPoison()) {
+      reportImmediateUB() << "memset called with poison length.";
+      return AnyValue::poison();
+    }
+
+    const APInt &Length = Args[2].asInteger();
+    if (Length.getActiveBits() > 64) {
+      reportImmediateUB() << "memset called with length overflows uint64_t.";
+      return AnyValue::poison();
+    }
+
+    const uint64_t Len = Length.getZExtValue();
+    if (Len == 0)
+      return AnyValue();
+
+    if (Args[0].isPoison()) {
+      reportImmediateUB() << "memset called with poison destination pointer.";
+      return AnyValue::poison();
+    }
+
+    const Pointer &DstPtr = Args[0].asPointer();
+
+    auto [DstMO, DstOffset] =
+        verifyMemAccess(DstPtr, Len, Align(1), /*IsStore=*/true);
+    if (!DstMO)
+      return AnyValue();
+
+    if (DstMO->isConstant()) {
+      reportImmediateUB() << "Try to write to a constant memory object: "
+                          << DstPtr << ".";
+      return AnyValue::poison();
+    }
+
+    Byte FillByte = Args[1].isPoison()
+                        ? Byte::poison()
+                        : Byte::concrete(Args[1].asInteger().getZExtValue());
+    fill(DstMO->getBytes().slice(DstOffset, Len), FillByte);
+    return AnyValue();
+  }
+
 public:
   InstExecutor(Context &C, EventHandler &H, Function &F,
                ArrayRef<AnyValue> Args, AnyValue &RetVal)
@@ -1500,6 +1615,15 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
         return V;
       });
     }
+    case Intrinsic::memcpy:
+    case Intrinsic::memcpy_inline:
+    case Intrinsic::memmove: {
+      return callMemTransferIntrinsic(Args, IID);
+    }
+    case Intrinsic::memset:
+    case Intrinsic::memset_inline: {
+      return callMemSetIntrinsic(Args);
+    }
     default:
       Handler.onUnrecognizedInstruction(CB);
       setFailed();

>From c3708b8250c5001458d7be385cd8b935b018d11b Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Mon, 22 Jun 2026 19:22:35 +0800
Subject: [PATCH 2/2] [llubi] Address comments

---
 llvm/test/tools/llubi/intr_memory.ll          |  79 ++++++++----
 llvm/test/tools/llubi/intr_memory_align_ub.ll |  49 ++++++++
 .../tools/llubi/intr_memory_constant_ub.ll    |  15 +++
 llvm/test/tools/llubi/intr_memory_len_ub.ll   |  30 +++++
 llvm/test/tools/llubi/intr_memory_ub.ll       |   6 +-
 llvm/tools/llubi/lib/ExecutorBase.cpp         |  14 +--
 llvm/tools/llubi/lib/Interpreter.cpp          | 115 ++++++++++--------
 7 files changed, 223 insertions(+), 85 deletions(-)
 create mode 100644 llvm/test/tools/llubi/intr_memory_align_ub.ll
 create mode 100644 llvm/test/tools/llubi/intr_memory_constant_ub.ll
 create mode 100644 llvm/test/tools/llubi/intr_memory_len_ub.ll

diff --git a/llvm/test/tools/llubi/intr_memory.ll b/llvm/test/tools/llubi/intr_memory.ll
index f612ef0fb3faa..5ab3a1d101fa1 100644
--- a/llvm/test/tools/llubi/intr_memory.ll
+++ b/llvm/test/tools/llubi/intr_memory.ll
@@ -1,11 +1,6 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
 ; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
 
-declare void @llvm.memset.p0.i16(ptr, i8, i16, i1)
-declare void @llvm.memset.inline.p0.i8(ptr, i8, i8, i1)
-declare void @llvm.memcpy.p0.p0.i8(ptr, ptr, i8, i1)
-declare void @llvm.memcpy.inline.p0.p0.i8(ptr, ptr, i8, i1)
-declare void @llvm.memmove.p0.p0.i8(ptr, ptr, i8, i1)
-
 define void @main() {
   %src = alloca [8 x i8], align 1
   %dst = alloca [8 x i8], align 1
@@ -48,6 +43,14 @@ define void @main() {
   %mv2 = load i8, ptr %m2, align 1
   %mv5 = load i8, ptr %m5, align 1
 
+  %prov_src = alloca ptr, align 8
+  %prov_dst = alloca ptr, align 8
+  %prov_ptr = alloca i8, align 1
+  store ptr %prov_ptr, ptr %prov_src, align 8
+  call void @llvm.memcpy.p0.p0.i8(ptr %prov_dst, ptr %prov_src, i8 8, i1 false)
+  %prov_copy = load ptr, ptr %prov_dst, align 8
+  store i8 0, ptr %prov_copy, align 1
+
   call void @llvm.memset.p0.i16(ptr poison, i8 0, i16 0, i1 false)
   call void @llvm.memcpy.p0.p0.i8(ptr poison, ptr poison, i8 0, i1 false)
 
@@ -55,21 +58,49 @@ define void @main() {
 }
 
 ; CHECK: Entering function: main
-; CHECK:   call void @llvm.memset.p0.i16(ptr %src, i8 17, i16 8, i1 false)
-; CHECK:   call void @llvm.memcpy.p0.p0.i8(ptr %dst, ptr %src, i8 8, i1 false)
-; CHECK:   call void @llvm.memset.inline.p0.i8(ptr %dst2, i8 34, i8 3, i1 false)
-; CHECK:   call void @llvm.memcpy.inline.p0.p0.i8(ptr %dst5, ptr %src4, i8 2, i1 false)
-; CHECK:   %v0 = load i8, ptr %dst, align 1 => i8 17
-; CHECK:   %v1 = load i8, ptr %p1, align 1 => i8 17
-; CHECK:   %v2 = load i8, ptr %p2, align 1 => i8 34
-; CHECK:   %v4 = load i8, ptr %p4, align 1 => i8 34
-; CHECK:   %v5 = load i8, ptr %p5, align 1 => i8 17
-; CHECK:   call void @llvm.memmove.p0.p0.i8(ptr %m1, ptr %move, i8 5, i1 false)
-; CHECK:   %mv0 = load i8, ptr %move, align 1 => i8 1
-; CHECK:   %mv1 = load i8, ptr %m1, align 1 => i8 1
-; CHECK:   %mv2 = load i8, ptr %m2, align 1 => i8 2
-; CHECK:   %mv5 = load i8, ptr %m5, align 1 => i8 5
-; CHECK:   call void @llvm.memset.p0.i16(ptr poison, i8 0, i16 0, i1 false)
-; CHECK:   call void @llvm.memcpy.p0.p0.i8(ptr poison, ptr poison, i8 0, i1 false)
-; CHECK:   ret void
-; CHECK: Exiting function: main
+; CHECK-NEXT:   %src = alloca [8 x i8], align 1 => ptr 0x8 [src]
+; CHECK-NEXT:   %dst = alloca [8 x i8], align 1 => ptr 0x11 [dst]
+; CHECK-NEXT:   call void @llvm.memset.p0.i16(ptr %src, i8 17, i16 8, i1 false)
+; CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i8(ptr %dst, ptr %src, i8 8, i1 false)
+; CHECK-NEXT:   %dst2 = getelementptr i8, ptr %dst, i64 2 => ptr 0x13 [dst + 2]
+; CHECK-NEXT:   call void @llvm.memset.inline.p0.i8(ptr %dst2, i8 34, i8 3, i1 false)
+; CHECK-NEXT:   %src4 = getelementptr i8, ptr %src, i64 4 => ptr 0xC [src + 4]
+; CHECK-NEXT:   %dst5 = getelementptr i8, ptr %dst, i64 5 => ptr 0x16 [dst + 5]
+; CHECK-NEXT:   call void @llvm.memcpy.inline.p0.p0.i8(ptr %dst5, ptr %src4, i8 2, i1 false)
+; CHECK-NEXT:   %v0 = load i8, ptr %dst, align 1 => i8 17
+; CHECK-NEXT:   %p1 = getelementptr i8, ptr %dst, i64 1 => ptr 0x12 [dst + 1]
+; CHECK-NEXT:   %v1 = load i8, ptr %p1, align 1 => i8 17
+; CHECK-NEXT:   %p2 = getelementptr i8, ptr %dst, i64 2 => ptr 0x13 [dst + 2]
+; CHECK-NEXT:   %v2 = load i8, ptr %p2, align 1 => i8 34
+; CHECK-NEXT:   %p4 = getelementptr i8, ptr %dst, i64 4 => ptr 0x15 [dst + 4]
+; CHECK-NEXT:   %v4 = load i8, ptr %p4, align 1 => i8 34
+; CHECK-NEXT:   %p5 = getelementptr i8, ptr %dst, i64 5 => ptr 0x16 [dst + 5]
+; CHECK-NEXT:   %v5 = load i8, ptr %p5, align 1 => i8 17
+; CHECK-NEXT:   %move = alloca [6 x i8], align 1 => ptr 0x1A [move]
+; CHECK-NEXT:   store i8 1, ptr %move, align 1
+; CHECK-NEXT:   %m1 = getelementptr i8, ptr %move, i64 1 => ptr 0x1B [move + 1]
+; CHECK-NEXT:   store i8 2, ptr %m1, align 1
+; CHECK-NEXT:   %m2 = getelementptr i8, ptr %move, i64 2 => ptr 0x1C [move + 2]
+; CHECK-NEXT:   store i8 3, ptr %m2, align 1
+; CHECK-NEXT:   %m3 = getelementptr i8, ptr %move, i64 3 => ptr 0x1D [move + 3]
+; CHECK-NEXT:   store i8 4, ptr %m3, align 1
+; CHECK-NEXT:   %m4 = getelementptr i8, ptr %move, i64 4 => ptr 0x1E [move + 4]
+; CHECK-NEXT:   store i8 5, ptr %m4, align 1
+; CHECK-NEXT:   %m5 = getelementptr i8, ptr %move, i64 5 => ptr 0x1F [move + 5]
+; CHECK-NEXT:   store i8 6, ptr %m5, align 1
+; CHECK-NEXT:   call void @llvm.memmove.p0.p0.i8(ptr %m1, ptr %move, i8 5, i1 false)
+; CHECK-NEXT:   %mv0 = load i8, ptr %move, align 1 => i8 1
+; CHECK-NEXT:   %mv1 = load i8, ptr %m1, align 1 => i8 1
+; CHECK-NEXT:   %mv2 = load i8, ptr %m2, align 1 => i8 2
+; CHECK-NEXT:   %mv5 = load i8, ptr %m5, align 1 => i8 5
+; CHECK-NEXT:   %prov_src = alloca ptr, align 8 => ptr 0x28 [prov_src]
+; CHECK-NEXT:   %prov_dst = alloca ptr, align 8 => ptr 0x38 [prov_dst]
+; CHECK-NEXT:   %prov_ptr = alloca i8, align 1 => ptr 0x41 [prov_ptr]
+; CHECK-NEXT:   store ptr %prov_ptr, ptr %prov_src, align 8
+; CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i8(ptr %prov_dst, ptr %prov_src, i8 8, i1 false)
+; CHECK-NEXT:   %prov_copy = load ptr, ptr %prov_dst, align 8 => ptr 0x41 [prov_ptr]
+; CHECK-NEXT:   store i8 0, ptr %prov_copy, align 1
+; CHECK-NEXT:   call void @llvm.memset.p0.i16(ptr poison, i8 0, i16 0, i1 false)
+; CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i8(ptr poison, ptr poison, i8 0, i1 false)
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_memory_align_ub.ll b/llvm/test/tools/llubi/intr_memory_align_ub.ll
new file mode 100644
index 0000000000000..c1c8cb0222f5c
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_memory_align_ub.ll
@@ -0,0 +1,49 @@
+; RUN: sed 's/OP/memcpy_misaligned_dst/g' %s | not llubi --verbose 2>&1 | FileCheck %s --check-prefix=COPY-DST
+; RUN: sed 's/OP/memcpy_misaligned_src/g' %s | not llubi --verbose 2>&1 | FileCheck %s --check-prefix=COPY-SRC
+; RUN: sed 's/OP/memset_misaligned_dst/g' %s | not llubi --verbose 2>&1 | FileCheck %s --check-prefix=SET-DST
+
+define void @main() {
+  call void @OP()
+  ret void
+}
+
+define void @memcpy_misaligned_dst() {
+  %src = alloca [4 x i8], align 2
+  %dst = alloca [4 x i8], align 2
+  %dst1 = getelementptr i8, ptr %dst, i64 1
+  call void @llvm.memcpy.p0.p0.i8(ptr align 2 %dst1, ptr align 1 %src, i8 1, i1 false)
+  ret void
+}
+
+define void @memcpy_misaligned_src() {
+  %src = alloca [4 x i8], align 1
+  %dst = alloca [4 x i8], align 2
+  %src1 = getelementptr i8, ptr %src, i64 1
+  call void @llvm.memcpy.p0.p0.i8(ptr align 1 %dst, ptr align 2 %src1, i8 1, i1 false)
+  ret void
+}
+
+define void @memset_misaligned_dst() {
+  %dst = alloca [4 x i8], align 1
+  %dst1 = getelementptr i8, ptr %dst, i64 1
+  call void @llvm.memset.p0.i8(ptr align 2 %dst1, i8 0, i8 1, i1 false)
+  ret void
+}
+
+; COPY-DST: Entering function: main
+; COPY-DST: Entering function: memcpy_misaligned_dst
+; COPY-DST: Immediate UB detected: Misaligned memory access.
+; COPY-DST-SAME: Required alignment: 2.
+; COPY-DST: error: Execution of function 'main' failed.
+
+; COPY-SRC: Entering function: main
+; COPY-SRC: Entering function: memcpy_misaligned_src
+; COPY-SRC: Immediate UB detected: Misaligned memory access.
+; COPY-SRC-SAME: Required alignment: 2.
+; COPY-SRC: error: Execution of function 'main' failed.
+
+; SET-DST: Entering function: main
+; SET-DST: Entering function: memset_misaligned_dst
+; SET-DST: Immediate UB detected: Misaligned memory access.
+; SET-DST-SAME: Required alignment: 2.
+; SET-DST: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/intr_memory_constant_ub.ll b/llvm/test/tools/llubi/intr_memory_constant_ub.ll
new file mode 100644
index 0000000000000..09c68eeaf5fab
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_memory_constant_ub.ll
@@ -0,0 +1,15 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+ at constant_dst = constant [4 x i8] zeroinitializer
+
+define void @main() {
+  call void @llvm.memset.p0.i8(ptr @constant_dst, i8 0, i8 1, i1 false)
+  ret void
+}
+
+; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0   call void @llvm.memset.p0.i8(ptr @constant_dst, i8 0, i8 1, i1 false) at @main <stdin>:6
+; CHECK-NEXT: Immediate UB detected: Try to write to a constant memory object: ptr 0x8 [@constant_dst].
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/intr_memory_len_ub.ll b/llvm/test/tools/llubi/intr_memory_len_ub.ll
new file mode 100644
index 0000000000000..3153bc44864db
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_memory_len_ub.ll
@@ -0,0 +1,30 @@
+; RUN: sed 's/OP/memcpy_len_overflow/g' %s | not llubi --verbose 2>&1 | FileCheck %s --check-prefix=COPY
+; RUN: sed 's/OP/memset_len_overflow/g' %s | not llubi --verbose 2>&1 | FileCheck %s --check-prefix=SET
+
+define void @main() {
+  call void @OP()
+  ret void
+}
+
+define void @memcpy_len_overflow() {
+  %src = alloca [4 x i8], align 1
+  %dst = alloca [4 x i8], align 1
+  call void @llvm.memcpy.p0.p0.i128(ptr %dst, ptr %src, i128 18446744073709551616, i1 false)
+  ret void
+}
+
+define void @memset_len_overflow() {
+  %dst = alloca [4 x i8], align 1
+  call void @llvm.memset.p0.i128(ptr %dst, i8 0, i128 18446744073709551616, i1 false)
+  ret void
+}
+
+; COPY: Entering function: main
+; COPY: Entering function: memcpy_len_overflow
+; COPY: Immediate UB detected: Memory transfer intrinsic length overflows uint64_t.
+; COPY: error: Execution of function 'main' failed.
+
+; SET: Entering function: main
+; SET: Entering function: memset_len_overflow
+; SET: Immediate UB detected: memset called with length overflows uint64_t.
+; SET: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/intr_memory_ub.ll b/llvm/test/tools/llubi/intr_memory_ub.ll
index c3e301618eddb..945870300a84d 100644
--- a/llvm/test/tools/llubi/intr_memory_ub.ll
+++ b/llvm/test/tools/llubi/intr_memory_ub.ll
@@ -3,10 +3,6 @@
 ; RUN: sed 's/OP/memset_poison_dst/g' %s | not llubi --verbose 2>&1 | FileCheck %s --check-prefix=SET-DST
 ; RUN: sed 's/OP/memset_poison_len/g' %s | not llubi --verbose 2>&1 | FileCheck %s --check-prefix=SET-LEN
 
-declare void @llvm.memcpy.p0.p0.i8(ptr, ptr, i8, i1)
-declare void @llvm.memcpy.inline.p0.p0.i8(ptr, ptr, i8, i1)
-declare void @llvm.memset.p0.i8(ptr, i8, i8, i1)
-
 define void @main() {
   call void @OP()
   ret void
@@ -43,7 +39,7 @@ define void @memset_poison_len() {
 
 ; COPY-SRC: Entering function: main
 ; COPY-SRC: Entering function: memcpy_inline_poison_src
-; COPY-SRC: Immediate UB detected: Memory copy intrinsic with poison source pointer.
+; COPY-SRC: Immediate UB detected: Memory transfer intrinsic with poison source pointer.
 ; COPY-SRC: error: Execution of function 'main' failed.
 
 ; SET-DST: Entering function: main
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index 482930af81b5b..da49a934123c9 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -77,6 +77,12 @@ ExecutorBase::verifyMemAccess(const Pointer &Ptr, uint64_t AccessSize,
     return {};
   }
 
+  if (IsStore && MO->isConstant()) {
+    reportImmediateUB() << "Try to write to a constant memory object: " << Ptr
+                        << ".";
+    return {};
+  }
+
   if (Address.countr_zero() < Log2(Alignment)) {
     reportImmediateUB() << "Misaligned memory access. Address: 0x"
                         << Twine::utohexstr(Address.getZExtValue())
@@ -144,14 +150,8 @@ void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
   if (auto [MO, Offset] = verifyMemAccess(
           PtrVal, Ctx.getEffectiveTypeStoreSize(ValTy), Alignment,
           /*IsStore=*/true);
-      MO) {
-    if (MO->isConstant()) {
-      reportImmediateUB() << "Try to write to a constant memory object: "
-                          << PtrVal << ".";
-      return;
-    }
+      MO)
     Ctx.store(*MO, Offset, Val, ValTy);
-  }
 }
 
 void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index ac126bbcdcb57..d5dba7d8b0f21 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -699,55 +699,70 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     return V.asInteger();
   }
 
-  AnyValue callMemTransferIntrinsic(ArrayRef<AnyValue> Args,
+  bool handlesMemoryIntrinsicAlignment(Intrinsic::ID IID, unsigned ArgNo) {
+    switch (IID) {
+    case Intrinsic::memcpy:
+    case Intrinsic::memcpy_inline:
+    case Intrinsic::memmove:
+      return ArgNo == 0 || ArgNo == 1;
+    case Intrinsic::memset:
+    case Intrinsic::memset_inline:
+      return ArgNo == 0;
+    default:
+      return false;
+    }
+  }
+
+  AnyValue callMemTransferIntrinsic(CallBase &CB, ArrayRef<AnyValue> Args,
                                     Intrinsic::ID IID) {
-    if (Args[2].isPoison()) {
+    const AnyValue &Dest = Args[0];
+    const AnyValue &Src = Args[1];
+    const AnyValue &Length = Args[2];
+    // TODO: Handle isvolatile argument.
+    if (Length.isPoison()) {
       reportImmediateUB() << "Memory transfer intrinsic with poison length.";
       return AnyValue::poison();
     }
 
-    const APInt &Length = Args[2].asInteger();
-    if (Length.getActiveBits() > 64) {
+    const APInt &LengthInt = Args[2].asInteger();
+    if (LengthInt.getActiveBits() > 64) {
       reportImmediateUB()
           << "Memory transfer intrinsic length overflows uint64_t.";
-      return AnyValue::poison();
+      return AnyValue();
     }
 
-    const uint64_t Len = Length.getZExtValue();
+    const uint64_t Len = LengthInt.getZExtValue();
     if (Len == 0)
       return AnyValue();
 
-    if (Args[0].isPoison()) {
+    if (Dest.isPoison()) {
       reportImmediateUB()
           << "Memory transfer intrinsic with poison destination pointer.";
-      return AnyValue::poison();
+      return AnyValue();
     }
 
-    if (Args[1].isPoison()) {
+    if (Src.isPoison()) {
       reportImmediateUB()
-          << "Memory copy intrinsic with poison source pointer.";
-      return AnyValue::poison();
+          << "Memory transfer intrinsic with poison source pointer.";
+      return AnyValue();
     }
 
-    const Pointer &DstPtr = Args[0].asPointer();
-    const Pointer &SrcPtr = Args[1].asPointer();
+    const Pointer &DstPtr = Dest.asPointer();
+    const Pointer &SrcPtr = Src.asPointer();
+
+    Align DstAlign = CB.getParamAlign(0).valueOrOne();
+    Align SrcAlign = CB.getParamAlign(1).valueOrOne();
 
     auto [SrcMO, SrcOffset] =
-        verifyMemAccess(SrcPtr, Len, Align(1), /*IsStore=*/false);
+        verifyMemAccess(SrcPtr, Len, SrcAlign, /*IsStore=*/false);
     if (!SrcMO)
       return AnyValue();
 
     auto [DstMO, DstOffset] =
-        verifyMemAccess(DstPtr, Len, Align(1), /*IsStore=*/true);
+        verifyMemAccess(DstPtr, Len, DstAlign, /*IsStore=*/true);
     if (!DstMO)
       return AnyValue();
 
-    if (DstMO->isConstant()) {
-      reportImmediateUB() << "Try to write to a constant memory object: "
-                          << DstPtr << ".";
-      return AnyValue::poison();
-    }
-
     if (IID == Intrinsic::memcpy || IID == Intrinsic::memcpy_inline) {
       if (SrcMO == DstMO && SrcOffset != DstOffset) {
         const uint64_t SrcEnd = SrcOffset + Len;
@@ -755,7 +770,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
         if (SrcOffset < DstEnd && DstOffset < SrcEnd) {
           reportImmediateUB()
               << "memcpy with overlapping source and destination.";
-          return AnyValue::poison();
+          return AnyValue();
         }
       }
     }
@@ -772,41 +787,40 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     return AnyValue();
   }
 
-  AnyValue callMemSetIntrinsic(ArrayRef<AnyValue> Args) {
-    if (Args[2].isPoison()) {
+  AnyValue callMemSetIntrinsic(CallBase &CB, ArrayRef<AnyValue> Args) {
+    const AnyValue &Dest = Args[0];
+    const AnyValue &Val = Args[1];
+    const AnyValue &Length = Args[2];
+
+    if (Length.isPoison()) {
       reportImmediateUB() << "memset called with poison length.";
-      return AnyValue::poison();
+      return AnyValue();
     }
 
-    const APInt &Length = Args[2].asInteger();
-    if (Length.getActiveBits() > 64) {
+    const APInt &LengthInt = Length.asInteger();
+    if (LengthInt.getActiveBits() > 64) {
       reportImmediateUB() << "memset called with length overflows uint64_t.";
-      return AnyValue::poison();
+      return AnyValue();
     }
 
-    const uint64_t Len = Length.getZExtValue();
+    const uint64_t Len = LengthInt.getZExtValue();
     if (Len == 0)
       return AnyValue();
 
-    if (Args[0].isPoison()) {
+    if (Dest.isPoison()) {
       reportImmediateUB() << "memset called with poison destination pointer.";
-      return AnyValue::poison();
+      return AnyValue();
     }
 
-    const Pointer &DstPtr = Args[0].asPointer();
+    const Pointer &DstPtr = Dest.asPointer();
 
+    Align DstAlign = CB.getParamAlign(0).valueOrOne();
     auto [DstMO, DstOffset] =
-        verifyMemAccess(DstPtr, Len, Align(1), /*IsStore=*/true);
+        verifyMemAccess(DstPtr, Len, DstAlign, /*IsStore=*/true);
     if (!DstMO)
       return AnyValue();
 
-    if (DstMO->isConstant()) {
-      reportImmediateUB() << "Try to write to a constant memory object: "
-                          << DstPtr << ".";
-      return AnyValue::poison();
-    }
-
-    Byte FillByte = Args[1].isPoison()
+    Byte FillByte = Val.isPoison()
                         ? Byte::poison()
                         : Byte::concrete(Args[1].asInteger().getZExtValue());
     fill(DstMO->getBytes().slice(DstOffset, Len), FillByte);
@@ -1617,13 +1631,11 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     }
     case Intrinsic::memcpy:
     case Intrinsic::memcpy_inline:
-    case Intrinsic::memmove: {
-      return callMemTransferIntrinsic(Args, IID);
-    }
+    case Intrinsic::memmove:
+      return callMemTransferIntrinsic(CB, Args, IID);
     case Intrinsic::memset:
-    case Intrinsic::memset_inline: {
-      return callMemSetIntrinsic(Args);
-    }
+    case Intrinsic::memset_inline:
+      return callMemSetIntrinsic(CB, Args);
     default:
       Handler.onUnrecognizedInstruction(CB);
       setFailed();
@@ -1657,7 +1669,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   /// Handle both poison-generating and UB-implying attributes for parameters
   /// and return values.
   void handleAttributes(Type *Ty, AnyValue &V, AttributeSet AttrsAtCallSite,
-                        AttributeSet AttrsAtCallee) {
+                        AttributeSet AttrsAtCallee,
+                        bool HandleAlignAttr = true) {
     if (Ty->isIntOrIntVectorTy()) {
       if (auto CRAttr = AttrsAtCallSite.getAttribute(Attribute::Range);
           CRAttr.isValid())
@@ -1679,7 +1692,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
           AttrsAtCallee.hasAttribute(Attribute::NonNull))
         applyNonNullAttr(V, Ty->getPointerAddressSpace(), DL);
     }
-    if (Ty->isPtrOrPtrVectorTy()) {
+    if (HandleAlignAttr && Ty->isPtrOrPtrVectorTy()) {
       if (MaybeAlign Align = AttrsAtCallSite.getAlignment())
         applyAlignAttr(V, *Align);
       if (MaybeAlign Align = AttrsAtCallee.getAlignment())
@@ -1837,7 +1850,11 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       // callee. We do it explicitly to avoid duplication.
       AttributeSet AttrsAtCallSite = CB.getParamAttributes(I);
       AttributeSet AttrsAtCallee = Callee->getAttributes().getParamAttrs(I);
-      handleAttributes(ArgTy, ArgVal, AttrsAtCallSite, AttrsAtCallee);
+      bool HandleAlignAttr =
+          !Callee->isIntrinsic() ||
+          !handlesMemoryIntrinsicAlignment(Callee->getIntrinsicID(), I);
+      handleAttributes(ArgTy, ArgVal, AttrsAtCallSite, AttrsAtCallee,
+                       HandleAlignAttr);
     }
 
     CurrentFrame->ResolvedCallee = Callee;



More information about the llvm-commits mailing list