[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