[llvm] [llubi] Add support for byval pointer arguments (PR #201852)
Yingwei Zheng via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 8 09:53:19 PDT 2026
https://github.com/dtcxzyw updated https://github.com/llvm/llvm-project/pull/201852
>From e31964dd1f984d66a0cc9ec7b9d6bd91e3a8f37b Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Fri, 5 Jun 2026 22:37:50 +0800
Subject: [PATCH 1/4] [llubi] Add support for byval pointer arguments
---
llvm/test/tools/llubi/byval.ll | 56 +++++++++++++++++++++++
llvm/test/tools/llubi/byval_large_size.ll | 18 ++++++++
llvm/test/tools/llubi/byval_lifetime.ll | 25 ++++++++++
llvm/test/tools/llubi/byval_misalign.ll | 18 ++++++++
llvm/test/tools/llubi/byval_oversize.ll | 18 ++++++++
llvm/test/tools/llubi/byval_padding.ll | 41 +++++++++++++++++
llvm/test/tools/llubi/byval_poison.ll | 16 +++++++
llvm/tools/llubi/lib/ExecutorBase.h | 3 ++
llvm/tools/llubi/lib/Interpreter.cpp | 46 ++++++++++++++++++-
9 files changed, 240 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/tools/llubi/byval.ll
create mode 100644 llvm/test/tools/llubi/byval_large_size.ll
create mode 100644 llvm/test/tools/llubi/byval_lifetime.ll
create mode 100644 llvm/test/tools/llubi/byval_misalign.ll
create mode 100644 llvm/test/tools/llubi/byval_oversize.ll
create mode 100644 llvm/test/tools/llubi/byval_padding.ll
create mode 100644 llvm/test/tools/llubi/byval_poison.ll
diff --git a/llvm/test/tools/llubi/byval.ll b/llvm/test/tools/llubi/byval.ll
new file mode 100644
index 0000000000000..5bbb905b46f0c
--- /dev/null
+++ b/llvm/test/tools/llubi/byval.ll
@@ -0,0 +1,56 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @write(ptr byval(i32) %p) {
+ store i32 1, ptr %p
+ ret void
+}
+
+define void @write_callsite_byval(ptr %p) {
+ store i32 2, ptr %p
+ ret void
+}
+
+define void @write_underalign(ptr byval(i32) align 1 %p) {
+ store i32 3, ptr %p, align 1
+ ret void
+}
+
+define void @main() {
+ %var = alloca i32
+ store i32 0, ptr %var
+ call void @write(ptr %var)
+ %v1 = load i32, ptr %var
+ call void @write_callsite_byval(ptr byval(i32) %var)
+ %v2 = load i32, ptr %var
+ call void @write_underalign(ptr %var)
+ %v3 = load i32, ptr %var
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %var = alloca i32, align 4 => ptr 0x8 [var]
+; CHECK-NEXT: store i32 0, ptr %var, align 4
+; CHECK-NEXT: Entering function: write
+; CHECK-NEXT: ptr %p = ptr 0xC [p]
+; CHECK-NEXT: store i32 1, ptr %p, align 4
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: write
+; CHECK-NEXT: call void @write(ptr %var)
+; CHECK-NEXT: %v1 = load i32, ptr %var, align 4 => i32 0
+; CHECK-NEXT: Entering function: write_callsite_byval
+; CHECK-NEXT: ptr %p = ptr 0x10 [p]
+; CHECK-NEXT: store i32 2, ptr %p, align 4
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: write_callsite_byval
+; CHECK-NEXT: call void @write_callsite_byval(ptr byval(i32) %var)
+; CHECK-NEXT: %v2 = load i32, ptr %var, align 4 => i32 0
+; CHECK-NEXT: Entering function: write_underalign
+; CHECK-NEXT: ptr %p = ptr 0x14 [p]
+; CHECK-NEXT: store i32 3, ptr %p, align 1
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: write_underalign
+; CHECK-NEXT: call void @write_underalign(ptr %var)
+; CHECK-NEXT: %v3 = load i32, ptr %var, align 4 => i32 0
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/byval_large_size.ll b/llvm/test/tools/llubi/byval_large_size.ll
new file mode 100644
index 0000000000000..03c9f0c40f31c
--- /dev/null
+++ b/llvm/test/tools/llubi/byval_large_size.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 --max-mem=100 < %s 2>&1 | FileCheck %s
+
+define void @callee(ptr byval([50 x i8]) %p) {
+ ret void
+}
+
+define void @main() {
+ %alloc = alloca [50 x i8]
+ call void @callee(ptr %alloc)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca [50 x i8], align 1 => ptr 0x8 [alloc]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @callee(ptr %alloc) at @main <stdin>:10
+; CHECK-NEXT: Error: Insufficient stack space for byval pointer argument.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/byval_lifetime.ll b/llvm/test/tools/llubi/byval_lifetime.ll
new file mode 100644
index 0000000000000..578a1c54bdb8a
--- /dev/null
+++ b/llvm/test/tools/llubi/byval_lifetime.ll
@@ -0,0 +1,25 @@
+; 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 ptr @identity(ptr byval(i32) %p) {
+ ret ptr %p
+}
+
+define void @main() {
+ %var = alloca i32
+ %p = call ptr @identity(ptr %var)
+ store i32 0, ptr %p
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %var = alloca i32, align 4 => ptr 0x8 [var]
+; CHECK-NEXT: Entering function: identity
+; CHECK-NEXT: ptr %p = ptr 0xC [p]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: identity
+; CHECK-NEXT: %p = call ptr @identity(ptr %var) => ptr 0xC [p]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 store i32 0, ptr %p, align 4 at @main <stdin>:11
+; CHECK-NEXT: Immediate UB detected: Try to access a dead memory object at address 0xc.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/byval_misalign.ll b/llvm/test/tools/llubi/byval_misalign.ll
new file mode 100644
index 0000000000000..cfd3027f14ce0
--- /dev/null
+++ b/llvm/test/tools/llubi/byval_misalign.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 @callee(ptr byval(i32) align 16 %p) {
+ ret void
+}
+
+define void @main() {
+ %alloc = alloca i32
+ call void @callee(ptr %alloc)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @callee(ptr %alloc) at @main <stdin>:10
+; CHECK-NEXT: Immediate UB detected: Misaligned memory access. Address: 0x8, Required alignment: 16.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/byval_oversize.ll b/llvm/test/tools/llubi/byval_oversize.ll
new file mode 100644
index 0000000000000..55bb3437552d8
--- /dev/null
+++ b/llvm/test/tools/llubi/byval_oversize.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 @callee(ptr byval(i32) %p) {
+ ret void
+}
+
+define void @main() {
+ %alloc = alloca i8
+ call void @callee(ptr %alloc)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i8, align 1 => ptr 0x8 [alloc]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @callee(ptr %alloc) at @main <stdin>:10
+; CHECK-NEXT: Immediate UB detected: Memory access is out of bounds. Accessed size: 4, Address: 0x8, Object base: 0x8, Object size: 1.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/byval_padding.ll b/llvm/test/tools/llubi/byval_padding.ll
new file mode 100644
index 0000000000000..042f821097f9d
--- /dev/null
+++ b/llvm/test/tools/llubi/byval_padding.ll
@@ -0,0 +1,41 @@
+; 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 {i8, i32, i8}
+
+define void @callee(ptr byval(%struct) %p) {
+ %concrete_zero1 = load i8, ptr %p
+ %concrete_zero2 = load i8, ptr %p
+ %p_padding1 = getelementptr i8, ptr %p, i64 1
+ %undef1_padding1 = load i8, ptr %p_padding1
+ %undef2_padding1 = load i8, ptr %p_padding1
+ %p_padding2 = getelementptr i8, ptr %p, i64 9
+ %undef1_padding2 = load i8, ptr %p_padding2
+ %undef2_padding2 = load i8, ptr %p_padding2
+ ret void
+}
+
+ at g = global [12 x i8] zeroinitializer
+
+define void @main() {
+ call void @callee(ptr @g)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Entering function: callee
+; CHECK-NEXT: ptr %p = ptr 0x14 [p]
+; CHECK-NEXT: %concrete_zero1 = load i8, ptr %p, align 1 => i8 0
+; CHECK-NEXT: %concrete_zero2 = load i8, ptr %p, align 1 => i8 0
+; CHECK-NEXT: %p_padding1 = getelementptr i8, ptr %p, i64 1 => ptr 0x15 [p + 1]
+; CHECK-NEXT: %undef1_padding1 = load i8, ptr %p_padding1, align 1 => i8 62
+; CHECK-NEXT: %undef2_padding1 = load i8, ptr %p_padding1, align 1 => i8 -117
+; CHECK-NEXT: %p_padding2 = getelementptr i8, ptr %p, i64 9 => ptr 0x1D [p + 9]
+; CHECK-NEXT: %undef1_padding2 = load i8, ptr %p_padding2, align 1 => i8 -71
+; CHECK-NEXT: %undef2_padding2 = load i8, ptr %p_padding2, align 1 => i8 -18
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: callee
+; CHECK-NEXT: call void @callee(ptr @g)
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/byval_poison.ll b/llvm/test/tools/llubi/byval_poison.ll
new file mode 100644
index 0000000000000..be4e6aae56694
--- /dev/null
+++ b/llvm/test/tools/llubi/byval_poison.ll
@@ -0,0 +1,16 @@
+; 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 @callee(ptr byval(i32) %p) {
+ ret void
+}
+
+define void @main() {
+ call void @callee(ptr poison)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @callee(ptr poison) at @main <stdin>:9
+; CHECK-NEXT: Immediate UB detected: Invalid poison byval pointer argument.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index d07b4dae1792e..d1744e48cab44 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -64,6 +64,9 @@ struct Frame {
// Reserved for in-flight subroutines.
Function *ResolvedCallee = nullptr;
SmallVector<AnyValue> CalleeArgs;
+ // Temporary memory objects created via pointer arguments with byval.
+ // They are belong to the caller.
+ SmallVector<IntrusiveRefCntPtr<MemoryObject>> CalleeByValArgs;
AnyValue CalleeRetVal;
Frame(Function &F, CallBase *CallSite, Frame *LastFrame,
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 81cc968535a2f..6d6a0f57d4756 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -907,6 +907,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
setResult(CB, std::move(RetVal));
+ for (auto &ByValArgs : CurrentFrame->CalleeByValArgs)
+ Ctx.free(*ByValArgs);
+ CurrentFrame->CalleeByValArgs.clear();
+
if (auto *II = dyn_cast<InvokeInst>(&CB))
jumpTo(*II, II->getNormalDest());
else if (CurrentFrame->State == FrameState::Pending)
@@ -1775,7 +1779,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
void enterCall(CallBase &CB) {
Function *Callee = CB.getCalledFunction();
- // TODO: handle byval/initializes
+ // TODO: handle initializes
auto &CalleeArgs = CurrentFrame->CalleeArgs;
assert(CalleeArgs.empty() &&
"Forgot to call returnFromCallee before entering a new call.");
@@ -1826,10 +1830,50 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
for (auto [I, Arg] : enumerate(CB.args())) {
Type *ArgTy = Arg->getType();
AnyValue &ArgVal = CalleeArgs[I];
+
// CallBase::paramHasAttr also checks parameter attributes at known
// callee. We do it explicitly to avoid duplication.
AttributeSet AttrsAtCallSite = CB.getParamAttributes(I);
AttributeSet AttrsAtCallee = Callee->getAttributes().getParamAttrs(I);
+
+ if (auto *ByValTy = CB.getParamByValType(I)) {
+ if (ArgVal.isPoison()) {
+ reportImmediateUB() << "Invalid poison byval pointer argument.";
+ return;
+ }
+
+ uint64_t Size = Ctx.getEffectiveTypeAllocSize(ByValTy);
+ MaybeAlign AllocAlign = AttrsAtCallSite.getAlignment();
+ if (AllocAlign.has_value()) {
+ if (MaybeAlign CalleeAlign = AttrsAtCallee.getAlignment())
+ AllocAlign = std::max(AllocAlign.value(), CalleeAlign.value());
+ } else {
+ AllocAlign = AttrsAtCallee.getAlignment();
+ }
+ if (!AllocAlign.has_value())
+ AllocAlign = DL.getABITypeAlign(ByValTy);
+ // Byval pointers cannot be passed via variadic arguments.
+ auto Obj = Ctx.allocate(
+ Size, AllocAlign.value().value(), Callee->getArg(I)->getName(),
+ ArgTy->getPointerAddressSpace(), MemInitKind::Uninitialized,
+ MemAllocKind::Stack);
+ if (!Obj) {
+ reportError()
+ << "Insufficient stack space for byval pointer argument.";
+ return;
+ }
+ CurrentFrame->CalleeByValArgs.push_back(Obj);
+
+ auto &SrcPtr = ArgVal.asPointer();
+ auto Val = load(SrcPtr, AllocAlign.value(), ByValTy, /*NoUndef=*/false);
+ if (hasProgramExited())
+ return;
+ auto TgtPtr = Ctx.deriveFromMemoryObject(std::move(Obj));
+ store(TgtPtr, AllocAlign.value(), Val, ByValTy);
+ if (hasProgramExited())
+ return;
+ ArgVal = std::move(TgtPtr);
+ }
handleAttributes(ArgTy, ArgVal, AttrsAtCallSite, AttrsAtCallee);
}
>From f66eab6a3de7edcd16078e9837ba28117bde28ba Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Mon, 8 Jun 2026 08:39:53 +0800
Subject: [PATCH 2/4] [llubi] Address review comments.
---
llvm/test/tools/llubi/byval.ll | 18 +++---
llvm/test/tools/llubi/byval_large_size.ll | 4 +-
llvm/test/tools/llubi/byval_lifetime.ll | 4 +-
llvm/test/tools/llubi/byval_misalign.ll | 4 +-
llvm/test/tools/llubi/byval_mismatch1.ll | 18 ++++++
llvm/test/tools/llubi/byval_mismatch2.ll | 18 ++++++
llvm/test/tools/llubi/byval_mismatch3.ll | 18 ++++++
llvm/test/tools/llubi/byval_oversize.ll | 4 +-
llvm/test/tools/llubi/byval_padding.ll | 4 +-
llvm/test/tools/llubi/byval_poison.ll | 4 +-
llvm/tools/llubi/lib/ExecutorBase.h | 2 +-
llvm/tools/llubi/lib/Interpreter.cpp | 75 +++++++++++++----------
12 files changed, 119 insertions(+), 54 deletions(-)
create mode 100644 llvm/test/tools/llubi/byval_mismatch1.ll
create mode 100644 llvm/test/tools/llubi/byval_mismatch2.ll
create mode 100644 llvm/test/tools/llubi/byval_mismatch3.ll
diff --git a/llvm/test/tools/llubi/byval.ll b/llvm/test/tools/llubi/byval.ll
index 5bbb905b46f0c..0f2df14979070 100644
--- a/llvm/test/tools/llubi/byval.ll
+++ b/llvm/test/tools/llubi/byval.ll
@@ -6,7 +6,7 @@ define void @write(ptr byval(i32) %p) {
ret void
}
-define void @write_callsite_byval(ptr %p) {
+define void @write_callsite_align(ptr byval(i32) %p) {
store i32 2, ptr %p
ret void
}
@@ -19,11 +19,11 @@ define void @write_underalign(ptr byval(i32) align 1 %p) {
define void @main() {
%var = alloca i32
store i32 0, ptr %var
- call void @write(ptr %var)
+ call void @write(ptr byval(i32) %var)
%v1 = load i32, ptr %var
- call void @write_callsite_byval(ptr byval(i32) %var)
+ call void @write_callsite_align(ptr byval(i32) align 8 %var)
%v2 = load i32, ptr %var
- call void @write_underalign(ptr %var)
+ call void @write_underalign(ptr byval(i32) %var)
%v3 = load i32, ptr %var
ret void
@@ -36,21 +36,21 @@ define void @main() {
; CHECK-NEXT: store i32 1, ptr %p, align 4
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: write
-; CHECK-NEXT: call void @write(ptr %var)
+; CHECK-NEXT: call void @write(ptr byval(i32) %var)
; CHECK-NEXT: %v1 = load i32, ptr %var, align 4 => i32 0
-; CHECK-NEXT: Entering function: write_callsite_byval
+; CHECK-NEXT: Entering function: write_callsite_align
; CHECK-NEXT: ptr %p = ptr 0x10 [p]
; CHECK-NEXT: store i32 2, ptr %p, align 4
; CHECK-NEXT: ret void
-; CHECK-NEXT: Exiting function: write_callsite_byval
-; CHECK-NEXT: call void @write_callsite_byval(ptr byval(i32) %var)
+; CHECK-NEXT: Exiting function: write_callsite_align
+; CHECK-NEXT: call void @write_callsite_align(ptr byval(i32) align 8 %var)
; CHECK-NEXT: %v2 = load i32, ptr %var, align 4 => i32 0
; CHECK-NEXT: Entering function: write_underalign
; CHECK-NEXT: ptr %p = ptr 0x14 [p]
; CHECK-NEXT: store i32 3, ptr %p, align 1
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: write_underalign
-; CHECK-NEXT: call void @write_underalign(ptr %var)
+; CHECK-NEXT: call void @write_underalign(ptr byval(i32) %var)
; CHECK-NEXT: %v3 = load i32, ptr %var, align 4 => i32 0
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/byval_large_size.ll b/llvm/test/tools/llubi/byval_large_size.ll
index 03c9f0c40f31c..c252bf13c18ad 100644
--- a/llvm/test/tools/llubi/byval_large_size.ll
+++ b/llvm/test/tools/llubi/byval_large_size.ll
@@ -7,12 +7,12 @@ define void @callee(ptr byval([50 x i8]) %p) {
define void @main() {
%alloc = alloca [50 x i8]
- call void @callee(ptr %alloc)
+ call void @callee(ptr byval([50 x i8]) %alloc)
ret void
}
; CHECK: Entering function: main
; CHECK-NEXT: %alloc = alloca [50 x i8], align 1 => ptr 0x8 [alloc]
; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0 call void @callee(ptr %alloc) at @main <stdin>:10
+; CHECK-NEXT: #0 call void @callee(ptr byval([50 x i8]) %alloc) at @main <stdin>:10
; CHECK-NEXT: Error: Insufficient stack space for byval pointer argument.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/byval_lifetime.ll b/llvm/test/tools/llubi/byval_lifetime.ll
index 578a1c54bdb8a..df5aef9cc497d 100644
--- a/llvm/test/tools/llubi/byval_lifetime.ll
+++ b/llvm/test/tools/llubi/byval_lifetime.ll
@@ -7,7 +7,7 @@ define ptr @identity(ptr byval(i32) %p) {
define void @main() {
%var = alloca i32
- %p = call ptr @identity(ptr %var)
+ %p = call ptr @identity(ptr byval(i32) %var)
store i32 0, ptr %p
ret void
@@ -18,7 +18,7 @@ define void @main() {
; CHECK-NEXT: ptr %p = ptr 0xC [p]
; CHECK-NEXT: ret ptr %p
; CHECK-NEXT: Exiting function: identity
-; CHECK-NEXT: %p = call ptr @identity(ptr %var) => ptr 0xC [p]
+; CHECK-NEXT: %p = call ptr @identity(ptr byval(i32) %var) => ptr 0xC [p]
; CHECK-NEXT: Stacktrace:
; CHECK-NEXT: #0 store i32 0, ptr %p, align 4 at @main <stdin>:11
; CHECK-NEXT: Immediate UB detected: Try to access a dead memory object at address 0xc.
diff --git a/llvm/test/tools/llubi/byval_misalign.ll b/llvm/test/tools/llubi/byval_misalign.ll
index cfd3027f14ce0..b65ea5dd8fc31 100644
--- a/llvm/test/tools/llubi/byval_misalign.ll
+++ b/llvm/test/tools/llubi/byval_misalign.ll
@@ -7,12 +7,12 @@ define void @callee(ptr byval(i32) align 16 %p) {
define void @main() {
%alloc = alloca i32
- call void @callee(ptr %alloc)
+ call void @callee(ptr byval(i32) %alloc)
ret void
}
; CHECK: Entering function: main
; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0 call void @callee(ptr %alloc) at @main <stdin>:10
+; CHECK-NEXT: #0 call void @callee(ptr byval(i32) %alloc) at @main <stdin>:10
; CHECK-NEXT: Immediate UB detected: Misaligned memory access. Address: 0x8, Required alignment: 16.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/byval_mismatch1.ll b/llvm/test/tools/llubi/byval_mismatch1.ll
new file mode 100644
index 0000000000000..17f0fa1837487
--- /dev/null
+++ b/llvm/test/tools/llubi/byval_mismatch1.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 @callee(ptr byval(i32) %p) {
+ ret void
+}
+
+define void @main() {
+ %alloc = alloca i32
+ call void @callee(ptr %alloc)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @callee(ptr %alloc) at @main <stdin>:10
+; CHECK-NEXT: Immediate UB detected: Mismatched byval attribute between callee and callsite.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/byval_mismatch2.ll b/llvm/test/tools/llubi/byval_mismatch2.ll
new file mode 100644
index 0000000000000..68b025eddf48b
--- /dev/null
+++ b/llvm/test/tools/llubi/byval_mismatch2.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 @callee(ptr %p) {
+ ret void
+}
+
+define void @main() {
+ %alloc = alloca i32
+ call void @callee(ptr byval(i32) %alloc)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @callee(ptr byval(i32) %alloc) at @main <stdin>:10
+; CHECK-NEXT: Immediate UB detected: Mismatched byval attribute between callee and callsite.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/byval_mismatch3.ll b/llvm/test/tools/llubi/byval_mismatch3.ll
new file mode 100644
index 0000000000000..379af24de53f2
--- /dev/null
+++ b/llvm/test/tools/llubi/byval_mismatch3.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 @callee(ptr byval([4 x i8]) %p) {
+ ret void
+}
+
+define void @main() {
+ %alloc = alloca i32
+ call void @callee(ptr byval(i32) %alloc)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @callee(ptr byval(i32) %alloc) at @main <stdin>:10
+; CHECK-NEXT: Immediate UB detected: Mismatched byval attribute between callee and callsite.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/byval_oversize.ll b/llvm/test/tools/llubi/byval_oversize.ll
index 55bb3437552d8..821d69463b0f8 100644
--- a/llvm/test/tools/llubi/byval_oversize.ll
+++ b/llvm/test/tools/llubi/byval_oversize.ll
@@ -7,12 +7,12 @@ define void @callee(ptr byval(i32) %p) {
define void @main() {
%alloc = alloca i8
- call void @callee(ptr %alloc)
+ call void @callee(ptr byval(i32) %alloc)
ret void
}
; CHECK: Entering function: main
; CHECK-NEXT: %alloc = alloca i8, align 1 => ptr 0x8 [alloc]
; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0 call void @callee(ptr %alloc) at @main <stdin>:10
+; CHECK-NEXT: #0 call void @callee(ptr byval(i32) %alloc) at @main <stdin>:10
; CHECK-NEXT: Immediate UB detected: Memory access is out of bounds. Accessed size: 4, Address: 0x8, Object base: 0x8, Object size: 1.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/byval_padding.ll b/llvm/test/tools/llubi/byval_padding.ll
index 042f821097f9d..63814735842c6 100644
--- a/llvm/test/tools/llubi/byval_padding.ll
+++ b/llvm/test/tools/llubi/byval_padding.ll
@@ -20,7 +20,7 @@ define void @callee(ptr byval(%struct) %p) {
@g = global [12 x i8] zeroinitializer
define void @main() {
- call void @callee(ptr @g)
+ call void @callee(ptr byval(%struct) @g)
ret void
}
; CHECK: Entering function: main
@@ -36,6 +36,6 @@ define void @main() {
; CHECK-NEXT: %undef2_padding2 = load i8, ptr %p_padding2, align 1 => i8 -18
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: callee
-; CHECK-NEXT: call void @callee(ptr @g)
+; CHECK-NEXT: call void @callee(ptr byval(%struct) @g)
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/byval_poison.ll b/llvm/test/tools/llubi/byval_poison.ll
index be4e6aae56694..fdc8641ed886a 100644
--- a/llvm/test/tools/llubi/byval_poison.ll
+++ b/llvm/test/tools/llubi/byval_poison.ll
@@ -6,11 +6,11 @@ define void @callee(ptr byval(i32) %p) {
}
define void @main() {
- call void @callee(ptr poison)
+ call void @callee(ptr byval(i32) poison)
ret void
}
; CHECK: Entering function: main
; CHECK-NEXT: Stacktrace:
-; CHECK-NEXT: #0 call void @callee(ptr poison) at @main <stdin>:9
+; CHECK-NEXT: #0 call void @callee(ptr byval(i32) poison) at @main <stdin>:9
; CHECK-NEXT: Immediate UB detected: Invalid poison byval pointer argument.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index d1744e48cab44..424e1a1774202 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -65,7 +65,7 @@ struct Frame {
Function *ResolvedCallee = nullptr;
SmallVector<AnyValue> CalleeArgs;
// Temporary memory objects created via pointer arguments with byval.
- // They are belong to the caller.
+ // They belong to the caller.
SmallVector<IntrusiveRefCntPtr<MemoryObject>> CalleeByValArgs;
AnyValue CalleeRetVal;
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 6d6a0f57d4756..4bb0f86ed8bc9 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -1836,43 +1836,54 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
AttributeSet AttrsAtCallSite = CB.getParamAttributes(I);
AttributeSet AttrsAtCallee = Callee->getAttributes().getParamAttrs(I);
- if (auto *ByValTy = CB.getParamByValType(I)) {
- if (ArgVal.isPoison()) {
- reportImmediateUB() << "Invalid poison byval pointer argument.";
+ if (ArgTy->isPointerTy()) {
+ auto *ByValTy = AttrsAtCallSite.getByValType();
+ auto *ByValTyFromCallee = AttrsAtCallee.getByValType();
+ if (ByValTy != ByValTyFromCallee) {
+ reportImmediateUB()
+ << "Mismatched byval attribute between callee and callsite.";
return;
}
+ if (ByValTy) {
+ if (ArgVal.isPoison()) {
+ reportImmediateUB() << "Invalid poison byval pointer argument.";
+ return;
+ }
- uint64_t Size = Ctx.getEffectiveTypeAllocSize(ByValTy);
- MaybeAlign AllocAlign = AttrsAtCallSite.getAlignment();
- if (AllocAlign.has_value()) {
+ uint64_t Size = Ctx.getEffectiveTypeAllocSize(ByValTy);
+ MaybeAlign AllocAlign = AttrsAtCallSite.getAlignment();
+ // Ignore the alignment at the callsite when it is set on the callee.
if (MaybeAlign CalleeAlign = AttrsAtCallee.getAlignment())
- AllocAlign = std::max(AllocAlign.value(), CalleeAlign.value());
- } else {
- AllocAlign = AttrsAtCallee.getAlignment();
- }
- if (!AllocAlign.has_value())
- AllocAlign = DL.getABITypeAlign(ByValTy);
- // Byval pointers cannot be passed via variadic arguments.
- auto Obj = Ctx.allocate(
- Size, AllocAlign.value().value(), Callee->getArg(I)->getName(),
- ArgTy->getPointerAddressSpace(), MemInitKind::Uninitialized,
- MemAllocKind::Stack);
- if (!Obj) {
- reportError()
- << "Insufficient stack space for byval pointer argument.";
- return;
- }
- CurrentFrame->CalleeByValArgs.push_back(Obj);
+ AllocAlign = CalleeAlign;
+ if (!AllocAlign.has_value()) {
+ // If the alignment is not specified, we use the default ABI
+ // alignment. This is the default behavior of
+ // TargetLoweringBase::getByValTypeAlignment.
+ AllocAlign = DL.getABITypeAlign(ByValTy);
+ }
+ // Byval pointers cannot be passed via variadic arguments.
+ auto Obj = Ctx.allocate(
+ Size, AllocAlign.value().value(), Callee->getArg(I)->getName(),
+ ArgTy->getPointerAddressSpace(), MemInitKind::Uninitialized,
+ MemAllocKind::Stack);
+ if (!Obj) {
+ reportError()
+ << "Insufficient stack space for byval pointer argument.";
+ return;
+ }
+ CurrentFrame->CalleeByValArgs.push_back(Obj);
- auto &SrcPtr = ArgVal.asPointer();
- auto Val = load(SrcPtr, AllocAlign.value(), ByValTy, /*NoUndef=*/false);
- if (hasProgramExited())
- return;
- auto TgtPtr = Ctx.deriveFromMemoryObject(std::move(Obj));
- store(TgtPtr, AllocAlign.value(), Val, ByValTy);
- if (hasProgramExited())
- return;
- ArgVal = std::move(TgtPtr);
+ auto &SrcPtr = ArgVal.asPointer();
+ auto Val =
+ load(SrcPtr, AllocAlign.value(), ByValTy, /*NoUndef=*/false);
+ if (hasProgramExited())
+ return;
+ auto TgtPtr = Ctx.deriveFromMemoryObject(std::move(Obj));
+ store(TgtPtr, AllocAlign.value(), Val, ByValTy);
+ if (hasProgramExited())
+ return;
+ ArgVal = std::move(TgtPtr);
+ }
}
handleAttributes(ArgTy, ArgVal, AttrsAtCallSite, AttrsAtCallee);
}
>From e9702fe66de21b6213060533f75113460ab23e4e Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Tue, 9 Jun 2026 00:50:18 +0800
Subject: [PATCH 3/4] [llubi] Address review comments. NFC.
---
llvm/test/tools/llubi/byval_null.ll | 16 ++++++++++++++++
llvm/tools/llubi/lib/Interpreter.cpp | 6 +++---
2 files changed, 19 insertions(+), 3 deletions(-)
create mode 100644 llvm/test/tools/llubi/byval_null.ll
diff --git a/llvm/test/tools/llubi/byval_null.ll b/llvm/test/tools/llubi/byval_null.ll
new file mode 100644
index 0000000000000..7e4f1a423f04e
--- /dev/null
+++ b/llvm/test/tools/llubi/byval_null.ll
@@ -0,0 +1,16 @@
+; 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 @callee(ptr byval(i32) %p) {
+ ret void
+}
+
+define void @main() {
+ call void @callee(ptr byval(i32) null)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @callee(ptr byval(i32) null) at @main <stdin>:9
+; 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/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 4bb0f86ed8bc9..1ae6dc3de713b 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -907,8 +907,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
setResult(CB, std::move(RetVal));
- for (auto &ByValArgs : CurrentFrame->CalleeByValArgs)
- Ctx.free(*ByValArgs);
+ for (auto &ByValArg : CurrentFrame->CalleeByValArgs)
+ Ctx.free(*ByValArg);
CurrentFrame->CalleeByValArgs.clear();
if (auto *II = dyn_cast<InvokeInst>(&CB))
@@ -1863,7 +1863,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
// Byval pointers cannot be passed via variadic arguments.
auto Obj = Ctx.allocate(
- Size, AllocAlign.value().value(), Callee->getArg(I)->getName(),
+ Size, AllocAlign->value(), Callee->getArg(I)->getName(),
ArgTy->getPointerAddressSpace(), MemInitKind::Uninitialized,
MemAllocKind::Stack);
if (!Obj) {
>From 2b808fae13a0ac9be6a1ca50867a944a87ca3fd3 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Tue, 9 Jun 2026 00:52:57 +0800
Subject: [PATCH 4/4] [llubi] Convert comments into assertions.
---
llvm/tools/llubi/lib/Interpreter.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 1ae6dc3de713b..6242c9cef288f 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -1861,7 +1861,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
// TargetLoweringBase::getByValTypeAlignment.
AllocAlign = DL.getABITypeAlign(ByValTy);
}
- // Byval pointers cannot be passed via variadic arguments.
+ assert(I < Callee->arg_size() &&
+ "Byval pointers cannot be passed via variadic arguments.");
auto Obj = Ctx.allocate(
Size, AllocAlign->value(), Callee->getArg(I)->getName(),
ArgTy->getPointerAddressSpace(), MemInitKind::Uninitialized,
More information about the llvm-commits
mailing list