[llvm] eed3366 - [llubi] Add support for poison-generating/UB-implying annotations (#195339)
via llvm-commits
llvm-commits at lists.llvm.org
Sun May 3 19:03:54 PDT 2026
Author: Yingwei Zheng
Date: 2026-05-04T10:03:49+08:00
New Revision: eed33661398a4e013bc9054fddb7c408d6d1e2c4
URL: https://github.com/llvm/llvm-project/commit/eed33661398a4e013bc9054fddb7c408d6d1e2c4
DIFF: https://github.com/llvm/llvm-project/commit/eed33661398a4e013bc9054fddb7c408d6d1e2c4.diff
LOG: [llubi] Add support for poison-generating/UB-implying annotations (#195339)
This patch adds support for poison-generating/UB-implying annotations,
including:
1. Parameter/retval attributes on function declarations and call sites
(range/nofpclass/align/nonnull/noundef/dereferenceable[_or_null]).
2. Metadata
(!range/!nofpclass/!align/!nonnull/!noundef/!dereferenceable[_or_null])
3. Assume operand bundles (nonnull/align/dereferenceable[_or_null])
I put all of them into a single patch as they share most of the common
logic.
Note that there are two todos to reach the full support:
1. Load with `!noundef` metadata doesn't check undef bits for now.
2. !dereferenceable[_or_null] on load (and inttoptr) are not tested by
this patch, as it needs the provenance support
(https://github.com/llvm/llvm-project/pull/185977). But it should be
fine as they are tested by metadata on call sites.
Added:
llvm/test/tools/llubi/assume_invalid_align.ll
llvm/test/tools/llubi/assume_misalign.ll
llvm/test/tools/llubi/assume_misalign_all_ones.ll
llvm/test/tools/llubi/assume_nondereferenceable.ll
llvm/test/tools/llubi/assume_null.ll
llvm/test/tools/llubi/assume_null_all_ones.ll
llvm/test/tools/llubi/assume_operand_bundles.ll
llvm/test/tools/llubi/assume_poison_align.ll
llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll
llvm/test/tools/llubi/attribute_dereferenceable_ub_oob1.ll
llvm/test/tools/llubi/attribute_dereferenceable_ub_oob2.ll
llvm/test/tools/llubi/attribute_dereferenceable_ub_oob3.ll
llvm/test/tools/llubi/attribute_dereferenceable_ub_poison.ll
llvm/test/tools/llubi/attribute_noundef_agg_ub.ll
llvm/test/tools/llubi/attribute_noundef_ub.ll
llvm/test/tools/llubi/attributes.ll
llvm/test/tools/llubi/metadata.ll
llvm/test/tools/llubi/metadata_noundef_ub.ll
Modified:
llvm/test/tools/llubi/assume_poison.ll
llvm/tools/llubi/lib/Context.cpp
llvm/tools/llubi/lib/Interpreter.cpp
llvm/tools/llubi/lib/Library.cpp
llvm/tools/llubi/lib/Value.cpp
llvm/tools/llubi/lib/Value.h
Removed:
################################################################################
diff --git a/llvm/test/tools/llubi/assume_invalid_align.ll b/llvm/test/tools/llubi/assume_invalid_align.ll
new file mode 100644
index 0000000000000..c73bbe66f4058
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_invalid_align.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 @main() {
+ %alloc = alloca i32
+ call void @llvm.assume(i1 true) ["align"(ptr null, i128 18446744073709551617)]
+ call void @llvm.assume(i1 true) ["align"(ptr %alloc, i128 18446744073709551616)]
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "align"(ptr null, i128 18446744073709551617) ]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @llvm.assume(i1 true) [ "align"(ptr %alloc, i128 18446744073709551616) ] at @main
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0x8 [alloc] violates align(18446744073709551616) assumption.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_misalign.ll b/llvm/test/tools/llubi/assume_misalign.ll
new file mode 100644
index 0000000000000..5f8e80a956027
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_misalign.ll
@@ -0,0 +1,14 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+ %alloc = alloca i32
+ call void @llvm.assume(i1 true) ["align"(ptr %alloc, i32 2048)]
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @llvm.assume(i1 true) [ "align"(ptr %alloc, i32 2048) ] at @main
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0x8 [alloc] violates align(2048) assumption.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_misalign_all_ones.ll b/llvm/test/tools/llubi/assume_misalign_all_ones.ll
new file mode 100644
index 0000000000000..aaf54f892789f
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_misalign_all_ones.ll
@@ -0,0 +1,14 @@
+; 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
+
+target datalayout = "po1:64:64"
+
+define void @main() {
+ call void @llvm.assume(i1 true) ["align"(ptr addrspace(1) null, i32 2048)]
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @llvm.assume(i1 true) [ "align"(ptr addrspace(1) null, i32 2048) ] at @main
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0xFFFFFFFFFFFFFFFF [dangling] violates align(2048) assumption.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_nondereferenceable.ll b/llvm/test/tools/llubi/assume_nondereferenceable.ll
new file mode 100644
index 0000000000000..34a4b9c84a0a6
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_nondereferenceable.ll
@@ -0,0 +1,14 @@
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/dereferenceable/dereferenceable_or_null/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @main() {
+ %alloc = alloca i32
+ call void @llvm.assume(i1 true) ["dereferenceable"(ptr %alloc, i32 2048)]
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @llvm.assume(i1 true) [ "dereferenceable{{(_or_null)?}}"(ptr %alloc, i32 2048) ] at @main
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0x8 [alloc] violates dereferenceable{{(_or_null)?}}(2048) assumption.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_null.ll b/llvm/test/tools/llubi/assume_null.ll
new file mode 100644
index 0000000000000..ef55ce54ed6b7
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_null.ll
@@ -0,0 +1,12 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+ call void @llvm.assume(i1 true) ["nonnull"(ptr null)]
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @llvm.assume(i1 true) [ "nonnull"(ptr null) ] at @main
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0x0 [dangling] violates nonnull assumption.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_null_all_ones.ll b/llvm/test/tools/llubi/assume_null_all_ones.ll
new file mode 100644
index 0000000000000..94dcebb600056
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_null_all_ones.ll
@@ -0,0 +1,20 @@
+; 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
+
+target datalayout = "po1:64:64"
+
+define void @main() {
+ %storage = alloca ptr
+ store i64 -1, ptr %storage
+ %res = load ptr addrspace(1), ptr %storage
+ call void @llvm.assume(i1 true) ["nonnull"(ptr addrspace(1) %res)]
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %storage = alloca ptr, align 8 => ptr 0x8 [storage]
+; CHECK-NEXT: store i64 -1, ptr %storage, align 4
+; CHECK-NEXT: %res = load ptr addrspace(1), ptr %storage, align 8 => ptr 0xFFFFFFFFFFFFFFFF [dangling]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @llvm.assume(i1 true) [ "nonnull"(ptr addrspace(1) %res) ] at @main
+; CHECK-NEXT: Immediate UB detected: The pointer ptr 0xFFFFFFFFFFFFFFFF [dangling] violates nonnull assumption.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_operand_bundles.ll b/llvm/test/tools/llubi/assume_operand_bundles.ll
new file mode 100644
index 0000000000000..e9abb574e0a1b
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_operand_bundles.ll
@@ -0,0 +1,46 @@
+; 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 @assume_align_dynamic(ptr %p, i32 %align) {
+ call void @llvm.assume(i1 true) ["align"(ptr %p, i32 4)]
+ call void @llvm.assume(i1 true) ["align"(ptr %p, i32 %align)]
+ call void @llvm.assume(i1 true) ["align"(ptr %p, i32 %align, i32 20)]
+ ret void
+}
+
+define void @main() {
+ %alloc = alloca i32
+ call void @llvm.assume(i1 true) ["nonnull"(ptr %alloc)]
+ call void @llvm.assume(i1 true) ["cold"(), "nonnull"(ptr %alloc), "cold"()]
+ call void @assume_align_dynamic(ptr %alloc, i32 8)
+ call void @llvm.assume(i1 true) ["align"(ptr null, i32 17)]
+ call void @llvm.assume(i1 true) ["align"(ptr null, i32 0)]
+ call void @llvm.assume(i1 true) ["dereferenceable"(ptr %alloc, i32 4)]
+ call void @llvm.assume(i1 true) ["dereferenceable"(ptr %alloc, i32 0)]
+ call void @llvm.assume(i1 true) ["dereferenceable_or_null"(ptr %alloc, i32 4)]
+ call void @llvm.assume(i1 true) ["dereferenceable_or_null"(ptr null, i32 4)]
+ call void @llvm.assume(i1 true) ["dereferenceable"(ptr %alloc, i32 4), "dereferenceable_or_null"(ptr %alloc, i32 4), "dereferenceable_or_null"(ptr null, i32 4)]
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "nonnull"(ptr %alloc) ]
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "cold"(), "nonnull"(ptr %alloc), "cold"() ]
+; CHECK-NEXT: Entering function: assume_align_dynamic
+; CHECK-NEXT: ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT: i32 %align = i32 8
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "align"(ptr %p, i32 4) ]
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "align"(ptr %p, i32 %align) ]
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "align"(ptr %p, i32 %align, i32 20) ]
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: assume_align_dynamic
+; CHECK-NEXT: call void @assume_align_dynamic(ptr %alloc, i32 8)
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "align"(ptr null, i32 17) ]
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "align"(ptr null, i32 0) ]
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %alloc, i32 4) ]
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %alloc, i32 0) ]
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable_or_null"(ptr %alloc, i32 4) ]
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable_or_null"(ptr null, i32 4) ]
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %alloc, i32 4), "dereferenceable_or_null"(ptr %alloc, i32 4), "dereferenceable_or_null"(ptr null, i32 4) ]
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/assume_poison.ll b/llvm/test/tools/llubi/assume_poison.ll
index a33bf9224a497..7ab2c586fedea 100644
--- a/llvm/test/tools/llubi/assume_poison.ll
+++ b/llvm/test/tools/llubi/assume_poison.ll
@@ -8,5 +8,5 @@ define void @main() {
; CHECK: Entering function: main
; CHECK-NEXT: Stacktrace:
; CHECK-NEXT: #0 call void @llvm.assume(i1 poison) at @main
-; CHECK-NEXT: Immediate UB detected: Assume on false or poison condition.
+; CHECK-NEXT: Immediate UB detected: The value poison violates noundef attribute.
; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_poison_align.ll b/llvm/test/tools/llubi/assume_poison_align.ll
new file mode 100644
index 0000000000000..9e67964846373
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_poison_align.ll
@@ -0,0 +1,12 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+ call void @llvm.assume(i1 true) ["align"(ptr poison, i32 4)]
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @llvm.assume(i1 true) [ "align"(ptr poison, i32 4) ] at @main
+; CHECK-NEXT: Immediate UB detected: Assume on poison pointer.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll
new file mode 100644
index 0000000000000..b4d585c2a71c3
--- /dev/null
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_nullary_provenance.ll
@@ -0,0 +1,20 @@
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/dereferenceable/dereferenceable_or_null/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @callee(ptr dereferenceable(4) %x) {
+ ret void
+}
+
+define void @main() {
+ %ptr_storage = alloca i64
+ %p = load ptr, ptr %ptr_storage
+ call void @callee(ptr %p)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %ptr_storage = alloca i64, align 8 => ptr 0x8 [ptr_storage]
+; CHECK-NEXT: %p = load ptr, ptr %ptr_storage, align 8 => ptr 0xE82FEEACEEB98B3E [dangling]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @callee(ptr %p) at @main
+; CHECK-NEXT: Immediate UB detected: The value ptr 0xE82FEEACEEB98B3E [dangling] violates dereferenceable{{(_or_null)?}}(4) attribute.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob1.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob1.ll
new file mode 100644
index 0000000000000..861010b1bca62
--- /dev/null
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob1.ll
@@ -0,0 +1,18 @@
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/dereferenceable/dereferenceable_or_null/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @callee(ptr dereferenceable(8) %x) {
+ 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
+; CHECK-NEXT: Immediate UB detected: The value ptr 0x8 [alloc] violates dereferenceable{{(_or_null)?}}(8) attribute.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob2.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob2.ll
new file mode 100644
index 0000000000000..aabbd988f2a6c
--- /dev/null
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob2.ll
@@ -0,0 +1,20 @@
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/dereferenceable/dereferenceable_or_null/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @callee(ptr dereferenceable(2) %x) {
+ ret void
+}
+
+define void @main() {
+ %alloc = alloca i32
+ %gep = getelementptr i8, ptr %alloc, i32 -1
+ call void @callee(ptr %gep)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %alloc, i32 -1 => ptr 0x7 [alloc + -1]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @callee(ptr %gep) at @main
+; CHECK-NEXT: Immediate UB detected: The value ptr 0x7 [alloc + -1] violates dereferenceable{{(_or_null)?}}(2) attribute.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob3.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob3.ll
new file mode 100644
index 0000000000000..c5402ff3864a5
--- /dev/null
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_oob3.ll
@@ -0,0 +1,20 @@
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/dereferenceable/dereferenceable_or_null/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @callee(ptr dereferenceable(3) %x) {
+ ret void
+}
+
+define void @main() {
+ %alloc = alloca i32
+ %gep = getelementptr i8, ptr %alloc, i32 2
+ call void @callee(ptr %gep)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %alloc, i32 2 => ptr 0xA [alloc + 2]
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @callee(ptr %gep) at @main
+; CHECK-NEXT: Immediate UB detected: The value ptr 0xA [alloc + 2] violates dereferenceable{{(_or_null)?}}(3) attribute.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_dereferenceable_ub_poison.ll b/llvm/test/tools/llubi/attribute_dereferenceable_ub_poison.ll
new file mode 100644
index 0000000000000..94771016c2bd9
--- /dev/null
+++ b/llvm/test/tools/llubi/attribute_dereferenceable_ub_poison.ll
@@ -0,0 +1,16 @@
+; RUN: not llubi --verbose < %s 2>&1 | FileCheck %s
+; RUN: sed 's/dereferenceable/dereferenceable_or_null/g' %s | not llubi --verbose 2>&1 | FileCheck %s
+
+define void @callee(ptr dereferenceable(4) %x) {
+ 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
+; CHECK-NEXT: Immediate UB detected: The value poison violates dereferenceable{{(_or_null)?}}(4) attribute.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_noundef_agg_ub.ll b/llvm/test/tools/llubi/attribute_noundef_agg_ub.ll
new file mode 100644
index 0000000000000..d3f722b4d12b4
--- /dev/null
+++ b/llvm/test/tools/llubi/attribute_noundef_agg_ub.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({i32, i32} noundef %x) {
+ ret void
+}
+
+define void @main() {
+ call void @callee({i32, i32} {i32 0, i32 poison})
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @callee({ i32, i32 } { i32 0, i32 poison }) at @main
+; CHECK-NEXT: Immediate UB detected: The value { i32 0, poison } violates noundef attribute.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attribute_noundef_ub.ll b/llvm/test/tools/llubi/attribute_noundef_ub.ll
new file mode 100644
index 0000000000000..9eb63f8205cfc
--- /dev/null
+++ b/llvm/test/tools/llubi/attribute_noundef_ub.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(i32 noundef %x) {
+ ret void
+}
+
+define void @main() {
+ call void @callee(i32 poison)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 call void @callee(i32 poison) at @main
+; CHECK-NEXT: Immediate UB detected: The value poison violates noundef attribute.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/attributes.ll b/llvm/test/tools/llubi/attributes.ll
new file mode 100644
index 0000000000000..1a4acfb76a0c2
--- /dev/null
+++ b/llvm/test/tools/llubi/attributes.ll
@@ -0,0 +1,328 @@
+; 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 range(i32 0, 2) i32 @add_with_range(i32 range(i32 0, 2) %x) {
+ %add = add i32 %x, 1
+ ret i32 %add
+}
+
+define range(i32 0, 2) <4 x i32> @add_with_range_vec(<4 x i32> range(i32 0, 2) %x) {
+ %add = add <4 x i32> %x, splat(i32 1)
+ ret <4 x i32> %add
+}
+
+define nofpclass(nan) half @identity_nofpclass(half nofpclass(inf) %x) {
+ ret half %x
+}
+
+define nofpclass(nan) <4 x half> @identity_nofpclass_vec(<4 x half> nofpclass(inf) %x) {
+ ret <4 x half> %x
+}
+
+define nofpclass(nan) {<2 x half>, <2 x half>} @identity_nofpclass_agg({<2 x half>, <2 x half>} nofpclass(inf) %x) {
+ ret {<2 x half>, <2 x half>} %x
+}
+
+define nonnull ptr @gep_nonnull(ptr nonnull %p) {
+ %gep = getelementptr i8, ptr %p, i32 -1
+ ret ptr %gep
+}
+
+define ptr @gep(ptr %p) {
+ %gep = getelementptr i8, ptr %p, i32 -1
+ ret ptr %gep
+}
+
+define align 16 ptr @gep_align(ptr align 8 %p) {
+ %gep = getelementptr i8, ptr %p, i32 8
+ ret ptr %gep
+}
+
+define align 16 <4 x ptr> @gep_align_vec(<4 x ptr> align 8 %p) {
+ %gep = getelementptr i8, <4 x ptr> %p, i32 8
+ ret <4 x ptr> %gep
+}
+
+define noundef i32 @identity_noundef(i32 noundef %x) {
+ ret i32 %x
+}
+
+define noundef {i32, <2 x i32>, [2 x i32]} @identity_noundef_agg({i32, <2 x i32>, [2 x i32]} noundef %x) {
+ ret {i32, <2 x i32>, [2 x i32]} %x
+}
+
+define noundef dereferenceable(4) ptr @identity_dereferenceable(ptr noundef dereferenceable(4) %p) {
+ ret ptr %p
+}
+
+define noundef dereferenceable(1) ptr @identity_dereferenceable_single_byte(ptr noundef dereferenceable(1) %p) {
+ ret ptr %p
+}
+
+define noundef dereferenceable_or_null(1) ptr @identity_dereferenceable_or_null(ptr noundef dereferenceable_or_null(1) %p) {
+ ret ptr %p
+}
+
+declare i32 @printf(ptr, ...)
+
+define void @main() {
+ %range_valid = call i32 @add_with_range(i32 0)
+ %range_poison_input = call i32 @add_with_range(i32 poison)
+ %range_invalid_input = call i32 @add_with_range(i32 3)
+ %range_invalid_output = call i32 @add_with_range(i32 1)
+ %range_vec = call <4 x i32> @add_with_range_vec(<4 x i32> <i32 0, i32 poison, i32 3, i32 1>)
+ %range_intrinsic_valid = call i32 @llvm.ctpop.i32(i32 range(i32 1, 255) 15)
+ %range_intrinsic_invalid_input = call i32 @llvm.ctpop.i32(i32 range(i32 1, 255) 1500)
+ %range_intrinsic_invalid_output = call range(i32 1, 32) i32 @llvm.ctpop.i32(i32 0)
+ %range_intrinsic_vec = call range(i32 1, 32) <4 x i32> @llvm.ctpop.v4i32(<4 x i32> range(i32 1, 255) <i32 15, i32 1500, i32 0, i32 poison>)
+
+ %nofpclass_valid = call half @identity_nofpclass(half 1.0)
+ %nofpclass_poison_input = call half @identity_nofpclass(half poison)
+ %nofpclass_invalid_input = call half @identity_nofpclass(half 0xH7C00)
+ %nofpclass_invalid_output = call half @identity_nofpclass(half 0xH7E00)
+ %nofpclass_vec = call <4 x half> @identity_nofpclass_vec(<4 x half> <half 1.0, half poison, half 0xH7C00, half 0xH7E00>)
+ %nofpclass_callsite_invalid_input = call half @identity_nofpclass(half nofpclass(norm) 1.0)
+ %nofpclass_callsite_invalid_output = call nofpclass(norm) half @identity_nofpclass(half 1.0)
+ %nofpclass_agg = call {<2 x half>, <2 x half>} @identity_nofpclass_agg({<2 x half>, <2 x half>} {<2 x half> <half 1.0, half poison>, <2 x half> <half 0xH7C00, half 0xH7E00>})
+
+ %alloc = alloca i32
+ %ptr_one = getelementptr i8, ptr null, i32 1
+ %nonnull_valid = call ptr @gep_nonnull(ptr %alloc)
+ %nonnull_invalid_input = call ptr @gep_nonnull(ptr null)
+ %nonnull_invalid_output = call ptr @gep_nonnull(ptr %ptr_one)
+ %nonnull_callsite_valid = call nonnull ptr @gep(ptr nonnull %alloc)
+ %nonnull_callsite_invalid_input = call ptr @gep(ptr nonnull null)
+ %nonnull_callsite_invalid_output = call nonnull ptr @gep(ptr %ptr_one)
+
+ %align_valid = call ptr @gep_align(ptr %alloc)
+ %align_invalid_input = call ptr @gep_align(ptr %ptr_one)
+ %align_invalid_output = call ptr @gep_align(ptr null)
+ %ptr_vec_1 = insertelement <4 x ptr> poison, ptr %alloc, i32 0
+ %ptr_vec_2 = insertelement <4 x ptr> %ptr_vec_1, ptr %ptr_one, i32 1
+ %ptr_vec_3 = insertelement <4 x ptr> %ptr_vec_2, ptr null, i32 2
+ %align_vec = call <4 x ptr> @gep_align_vec(<4 x ptr> %ptr_vec_3)
+ %align_valid_mixed = call align 1 ptr @gep_align(ptr align 4 %alloc)
+ %align_invalid_mixed1 = call ptr @gep_align(ptr align 1024 %alloc)
+ %align_invalid_mixed2 = call align 1024 ptr @gep_align(ptr %alloc)
+
+ %noundef_valid = call noundef i32 @identity_noundef(i32 noundef 1)
+ %noundef_valid_agg = call noundef {i32, <2 x i32>, [2 x i32]} @identity_noundef_agg({i32, <2 x i32>, [2 x i32]} noundef zeroinitializer)
+
+ %deref_valid = call ptr @identity_dereferenceable(ptr %alloc)
+ %deref_valid_mixed = call dereferenceable(1) ptr @identity_dereferenceable(ptr dereferenceable(1) %alloc)
+ %gep = getelementptr i8, ptr %alloc, i32 3
+ %deref_valid_middle = call ptr @identity_dereferenceable_single_byte(ptr %gep)
+ %deref_or_null_valid1 = call ptr @identity_dereferenceable_or_null(ptr %alloc)
+ %deref_or_null_valid2 = call ptr @identity_dereferenceable_or_null(ptr null)
+ %deref_or_null_mixed = call dereferenceable_or_null(1) dereferenceable(1) ptr @identity_dereferenceable_or_null(ptr dereferenceable_or_null(1) dereferenceable(1) %alloc)
+
+ %fmt_n_out = alloca [6 x i8]
+ store [6 x i8] c"N=%d\0A\00", ptr %fmt_n_out
+ %res = call range(i32 0, 15) noundef i32 (ptr, ...) @printf(ptr noundef nonnull %fmt_n_out, i32 noundef range(i32 0, 15) 6)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Entering function: add_with_range
+; CHECK-NEXT: i32 %x = i32 0
+; CHECK-NEXT: %add = add i32 %x, 1 => i32 1
+; CHECK-NEXT: ret i32 %add
+; CHECK-NEXT: Exiting function: add_with_range
+; CHECK-NEXT: %range_valid = call i32 @add_with_range(i32 0) => i32 1
+; CHECK-NEXT: Entering function: add_with_range
+; CHECK-NEXT: i32 %x = poison
+; CHECK-NEXT: %add = add i32 %x, 1 => poison
+; CHECK-NEXT: ret i32 %add
+; CHECK-NEXT: Exiting function: add_with_range
+; CHECK-NEXT: %range_poison_input = call i32 @add_with_range(i32 poison) => poison
+; CHECK-NEXT: Entering function: add_with_range
+; CHECK-NEXT: i32 %x = poison
+; CHECK-NEXT: %add = add i32 %x, 1 => poison
+; CHECK-NEXT: ret i32 %add
+; CHECK-NEXT: Exiting function: add_with_range
+; CHECK-NEXT: %range_invalid_input = call i32 @add_with_range(i32 3) => poison
+; CHECK-NEXT: Entering function: add_with_range
+; CHECK-NEXT: i32 %x = i32 1
+; CHECK-NEXT: %add = add i32 %x, 1 => i32 2
+; CHECK-NEXT: ret i32 %add
+; CHECK-NEXT: Exiting function: add_with_range
+; CHECK-NEXT: %range_invalid_output = call i32 @add_with_range(i32 1) => poison
+; CHECK-NEXT: Entering function: add_with_range_vec
+; CHECK-NEXT: <4 x i32> %x = { i32 0, poison, poison, i32 1 }
+; CHECK-NEXT: %add = add <4 x i32> %x, splat (i32 1) => { i32 1, poison, poison, i32 2 }
+; CHECK-NEXT: ret <4 x i32> %add
+; CHECK-NEXT: Exiting function: add_with_range_vec
+; CHECK-NEXT: %range_vec = call <4 x i32> @add_with_range_vec(<4 x i32> <i32 0, i32 poison, i32 3, i32 1>) => { i32 1, poison, poison, poison }
+; CHECK-NEXT: %range_intrinsic_valid = call i32 @llvm.ctpop.i32(i32 range(i32 1, 255) 15) => i32 4
+; CHECK-NEXT: %range_intrinsic_invalid_input = call i32 @llvm.ctpop.i32(i32 range(i32 1, 255) 1500) => poison
+; CHECK-NEXT: %range_intrinsic_invalid_output = call range(i32 1, 32) i32 @llvm.ctpop.i32(i32 0) => poison
+; CHECK-NEXT: %range_intrinsic_vec = call range(i32 1, 32) <4 x i32> @llvm.ctpop.v4i32(<4 x i32> range(i32 1, 255) <i32 15, i32 1500, i32 0, i32 poison>) => { i32 4, poison, poison, poison }
+; CHECK-NEXT: Entering function: identity_nofpclass
+; CHECK-NEXT: half %x = half 1.000000e+00
+; CHECK-NEXT: ret half %x
+; CHECK-NEXT: Exiting function: identity_nofpclass
+; CHECK-NEXT: %nofpclass_valid = call half @identity_nofpclass(half 0xH3C00) => half 1.000000e+00
+; CHECK-NEXT: Entering function: identity_nofpclass
+; CHECK-NEXT: half %x = poison
+; CHECK-NEXT: ret half %x
+; CHECK-NEXT: Exiting function: identity_nofpclass
+; CHECK-NEXT: %nofpclass_poison_input = call half @identity_nofpclass(half poison) => poison
+; CHECK-NEXT: Entering function: identity_nofpclass
+; CHECK-NEXT: half %x = poison
+; CHECK-NEXT: ret half %x
+; CHECK-NEXT: Exiting function: identity_nofpclass
+; CHECK-NEXT: %nofpclass_invalid_input = call half @identity_nofpclass(half 0xH7C00) => poison
+; CHECK-NEXT: Entering function: identity_nofpclass
+; CHECK-NEXT: half %x = half NaN
+; CHECK-NEXT: ret half %x
+; CHECK-NEXT: Exiting function: identity_nofpclass
+; CHECK-NEXT: %nofpclass_invalid_output = call half @identity_nofpclass(half 0xH7E00) => poison
+; CHECK-NEXT: Entering function: identity_nofpclass_vec
+; CHECK-NEXT: <4 x half> %x = { half 1.000000e+00, poison, poison, half NaN }
+; CHECK-NEXT: ret <4 x half> %x
+; CHECK-NEXT: Exiting function: identity_nofpclass_vec
+; CHECK-NEXT: %nofpclass_vec = call <4 x half> @identity_nofpclass_vec(<4 x half> <half 0xH3C00, half poison, half 0xH7C00, half 0xH7E00>) => { half 1.000000e+00, poison, poison, poison }
+; CHECK-NEXT: Entering function: identity_nofpclass
+; CHECK-NEXT: half %x = poison
+; CHECK-NEXT: ret half %x
+; CHECK-NEXT: Exiting function: identity_nofpclass
+; CHECK-NEXT: %nofpclass_callsite_invalid_input = call half @identity_nofpclass(half nofpclass(norm) 0xH3C00) => poison
+; CHECK-NEXT: Entering function: identity_nofpclass
+; CHECK-NEXT: half %x = half 1.000000e+00
+; CHECK-NEXT: ret half %x
+; CHECK-NEXT: Exiting function: identity_nofpclass
+; CHECK-NEXT: %nofpclass_callsite_invalid_output = call nofpclass(norm) half @identity_nofpclass(half 0xH3C00) => poison
+; CHECK-NEXT: Entering function: identity_nofpclass_agg
+; CHECK-NEXT: { <2 x half>, <2 x half> } %x = { { half 1.000000e+00, poison }, { poison, half NaN } }
+; CHECK-NEXT: ret { <2 x half>, <2 x half> } %x
+; CHECK-NEXT: Exiting function: identity_nofpclass_agg
+; CHECK-NEXT: %nofpclass_agg = call { <2 x half>, <2 x half> } @identity_nofpclass_agg({ <2 x half>, <2 x half> } { <2 x half> <half 0xH3C00, half poison>, <2 x half> <half 0xH7C00, half 0xH7E00> }) => { { half 1.000000e+00, poison }, { poison, poison } }
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: %ptr_one = getelementptr i8, ptr null, i32 1 => ptr 0x1 [dangling]
+; CHECK-NEXT: Entering function: gep_nonnull
+; CHECK-NEXT: ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 -1 => ptr 0x7 [alloc + -1]
+; CHECK-NEXT: ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_nonnull
+; CHECK-NEXT: %nonnull_valid = call ptr @gep_nonnull(ptr %alloc) => ptr 0x7 [alloc + -1]
+; CHECK-NEXT: Entering function: gep_nonnull
+; CHECK-NEXT: ptr %p = poison
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 -1 => poison
+; CHECK-NEXT: ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_nonnull
+; CHECK-NEXT: %nonnull_invalid_input = call ptr @gep_nonnull(ptr null) => poison
+; CHECK-NEXT: Entering function: gep_nonnull
+; CHECK-NEXT: ptr %p = ptr 0x1 [dangling]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 -1 => ptr 0x0 [dangling]
+; CHECK-NEXT: ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_nonnull
+; CHECK-NEXT: %nonnull_invalid_output = call ptr @gep_nonnull(ptr %ptr_one) => poison
+; CHECK-NEXT: Entering function: gep
+; CHECK-NEXT: ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 -1 => ptr 0x7 [alloc + -1]
+; CHECK-NEXT: ret ptr %gep
+; CHECK-NEXT: Exiting function: gep
+; CHECK-NEXT: %nonnull_callsite_valid = call nonnull ptr @gep(ptr nonnull %alloc) => ptr 0x7 [alloc + -1]
+; CHECK-NEXT: Entering function: gep
+; CHECK-NEXT: ptr %p = poison
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 -1 => poison
+; CHECK-NEXT: ret ptr %gep
+; CHECK-NEXT: Exiting function: gep
+; CHECK-NEXT: %nonnull_callsite_invalid_input = call ptr @gep(ptr nonnull null) => poison
+; CHECK-NEXT: Entering function: gep
+; CHECK-NEXT: ptr %p = ptr 0x1 [dangling]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 -1 => ptr 0x0 [dangling]
+; CHECK-NEXT: ret ptr %gep
+; CHECK-NEXT: Exiting function: gep
+; CHECK-NEXT: %nonnull_callsite_invalid_output = call nonnull ptr @gep(ptr %ptr_one) => poison
+; CHECK-NEXT: Entering function: gep_align
+; CHECK-NEXT: ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 8 => ptr 0x10 [alloc + 8]
+; CHECK-NEXT: ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_align
+; CHECK-NEXT: %align_valid = call ptr @gep_align(ptr %alloc) => ptr 0x10 [alloc + 8]
+; CHECK-NEXT: Entering function: gep_align
+; CHECK-NEXT: ptr %p = poison
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 8 => poison
+; CHECK-NEXT: ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_align
+; CHECK-NEXT: %align_invalid_input = call ptr @gep_align(ptr %ptr_one) => poison
+; CHECK-NEXT: Entering function: gep_align
+; CHECK-NEXT: ptr %p = ptr 0x0 [dangling]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 8 => ptr 0x8 [dangling]
+; CHECK-NEXT: ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_align
+; CHECK-NEXT: %align_invalid_output = call ptr @gep_align(ptr null) => poison
+; CHECK-NEXT: %ptr_vec_1 = insertelement <4 x ptr> poison, ptr %alloc, i32 0 => { ptr 0x8 [alloc], poison, poison, poison }
+; CHECK-NEXT: %ptr_vec_2 = insertelement <4 x ptr> %ptr_vec_1, ptr %ptr_one, i32 1 => { ptr 0x8 [alloc], ptr 0x1 [dangling], poison, poison }
+; CHECK-NEXT: %ptr_vec_3 = insertelement <4 x ptr> %ptr_vec_2, ptr null, i32 2 => { ptr 0x8 [alloc], ptr 0x1 [dangling], ptr 0x0 [dangling], poison }
+; CHECK-NEXT: Entering function: gep_align_vec
+; CHECK-NEXT: <4 x ptr> %p = { ptr 0x8 [alloc], poison, ptr 0x0 [dangling], poison }
+; CHECK-NEXT: %gep = getelementptr i8, <4 x ptr> %p, i32 8 => { ptr 0x10 [alloc + 8], poison, ptr 0x8 [dangling], poison }
+; CHECK-NEXT: ret <4 x ptr> %gep
+; CHECK-NEXT: Exiting function: gep_align_vec
+; CHECK-NEXT: %align_vec = call <4 x ptr> @gep_align_vec(<4 x ptr> %ptr_vec_3) => { ptr 0x10 [alloc + 8], poison, poison, poison }
+; CHECK-NEXT: Entering function: gep_align
+; CHECK-NEXT: ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 8 => ptr 0x10 [alloc + 8]
+; CHECK-NEXT: ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_align
+; CHECK-NEXT: %align_valid_mixed = call align 1 ptr @gep_align(ptr align 4 %alloc) => ptr 0x10 [alloc + 8]
+; CHECK-NEXT: Entering function: gep_align
+; CHECK-NEXT: ptr %p = poison
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 8 => poison
+; CHECK-NEXT: ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_align
+; CHECK-NEXT: %align_invalid_mixed1 = call ptr @gep_align(ptr align 1024 %alloc) => poison
+; CHECK-NEXT: Entering function: gep_align
+; CHECK-NEXT: ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %p, i32 8 => ptr 0x10 [alloc + 8]
+; CHECK-NEXT: ret ptr %gep
+; CHECK-NEXT: Exiting function: gep_align
+; CHECK-NEXT: %align_invalid_mixed2 = call align 1024 ptr @gep_align(ptr %alloc) => poison
+; CHECK-NEXT: Entering function: identity_noundef
+; CHECK-NEXT: i32 %x = i32 1
+; CHECK-NEXT: ret i32 %x
+; CHECK-NEXT: Exiting function: identity_noundef
+; CHECK-NEXT: %noundef_valid = call noundef i32 @identity_noundef(i32 noundef 1) => i32 1
+; CHECK-NEXT: Entering function: identity_noundef_agg
+; CHECK-NEXT: { i32, <2 x i32>, [2 x i32] } %x = { i32 0, { i32 0, i32 0 }, { i32 0, i32 0 } }
+; CHECK-NEXT: ret { i32, <2 x i32>, [2 x i32] } %x
+; CHECK-NEXT: Exiting function: identity_noundef_agg
+; CHECK-NEXT: %noundef_valid_agg = call noundef { i32, <2 x i32>, [2 x i32] } @identity_noundef_agg({ i32, <2 x i32>, [2 x i32] } noundef zeroinitializer) => { i32 0, { i32 0, i32 0 }, { i32 0, i32 0 } }
+; CHECK-NEXT: Entering function: identity_dereferenceable
+; CHECK-NEXT: ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: identity_dereferenceable
+; CHECK-NEXT: %deref_valid = call ptr @identity_dereferenceable(ptr %alloc) => ptr 0x8 [alloc]
+; CHECK-NEXT: Entering function: identity_dereferenceable
+; CHECK-NEXT: ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: identity_dereferenceable
+; CHECK-NEXT: %deref_valid_mixed = call dereferenceable(1) ptr @identity_dereferenceable(ptr dereferenceable(1) %alloc) => ptr 0x8 [alloc]
+; CHECK-NEXT: %gep = getelementptr i8, ptr %alloc, i32 3 => ptr 0xB [alloc + 3]
+; CHECK-NEXT: Entering function: identity_dereferenceable_single_byte
+; CHECK-NEXT: ptr %p = ptr 0xB [alloc + 3]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: identity_dereferenceable_single_byte
+; CHECK-NEXT: %deref_valid_middle = call ptr @identity_dereferenceable_single_byte(ptr %gep) => ptr 0xB [alloc + 3]
+; CHECK-NEXT: Entering function: identity_dereferenceable_or_null
+; CHECK-NEXT: ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: identity_dereferenceable_or_null
+; CHECK-NEXT: %deref_or_null_valid1 = call ptr @identity_dereferenceable_or_null(ptr %alloc) => ptr 0x8 [alloc]
+; CHECK-NEXT: Entering function: identity_dereferenceable_or_null
+; CHECK-NEXT: ptr %p = ptr 0x0 [dangling]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: identity_dereferenceable_or_null
+; CHECK-NEXT: %deref_or_null_valid2 = call ptr @identity_dereferenceable_or_null(ptr null) => ptr 0x0 [dangling]
+; CHECK-NEXT: Entering function: identity_dereferenceable_or_null
+; CHECK-NEXT: ptr %p = ptr 0x8 [alloc]
+; CHECK-NEXT: ret ptr %p
+; CHECK-NEXT: Exiting function: identity_dereferenceable_or_null
+; CHECK-NEXT: %deref_or_null_mixed = call dereferenceable(1) dereferenceable_or_null(1) ptr @identity_dereferenceable_or_null(ptr dereferenceable(1) dereferenceable_or_null(1) %alloc) => ptr 0x8 [alloc]
+; CHECK-NEXT: %fmt_n_out = alloca [6 x i8], align 1 => ptr 0xC [fmt_n_out]
+; CHECK-NEXT: store [6 x i8] c"N=%d\0A\00", ptr %fmt_n_out, align 1
+; CHECK-NEXT: N=6
+; CHECK-NEXT: %res = call noundef range(i32 0, 15) i32 (ptr, ...) @printf(ptr noundef nonnull %fmt_n_out, i32 noundef range(i32 0, 15) 6) => i32 4
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/metadata.ll b/llvm/test/tools/llubi/metadata.ll
new file mode 100644
index 0000000000000..7b4309e139e79
--- /dev/null
+++ b/llvm/test/tools/llubi/metadata.ll
@@ -0,0 +1,114 @@
+; 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 i32 @callee() {
+ ret i32 10
+}
+
+define float @callee_fp() {
+ ret float 0.0
+}
+
+define ptr @callee_ptr(ptr %x) {
+ ret ptr %x
+}
+
+define void @main() {
+ %alloc = alloca i32
+ store i32 1, ptr %alloc
+ %range_load_valid = load i32, ptr %alloc, !noundef !{}, !range !{i32 0, i32 10}
+ %range_load_invalid = load i32, ptr %alloc, !range !{i32 2, i32 10}
+ %alloc_vec = alloca <8 x i32>
+ store <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7>, ptr %alloc_vec
+ %range_list_load_vec = load <8 x i32>, ptr %alloc_vec, !range !{i32 3, i32 4, i32 5, i32 6, i32 7, i32 2}
+ store float 0.0, ptr %alloc
+ %nofpclass_load_valid = load float, ptr %alloc, !noundef !{}, !nofpclass !{i32 3}
+ %nofpclass_load_invalid = load float, ptr %alloc, !nofpclass !{i32 99}
+
+ ; TODO: Test dereferenceable[_or_null] after provenance support is ready
+
+ %alloc_ptr = alloca ptr
+ store ptr %alloc_ptr, ptr %alloc_ptr
+ %align_nonnull_load_valid = load ptr, ptr %alloc_ptr, !nonnull !{}, !align !{i32 8}, !noundef !{}
+ store ptr null, ptr %alloc_ptr
+ %align_load_valid = load ptr, ptr %alloc_ptr, !align !{i32 8}, !noundef !{}
+ %nonnull_load_invalid = load ptr, ptr %alloc_ptr, !nonnull !{}
+
+ %range_call_valid = call i32 @callee(), !noundef !{}, !range !{i32 0, i32 11}
+ %range_call_invalid = call i32 @callee(), !range !{i32 0, i32 10}
+ %nofpclass_call_valid = call float @callee_fp(), !noundef !{}, !nofpclass !{i32 3}
+ %nofpclass_call_invalid = call float @callee_fp(), !nofpclass !{i32 99}
+ %nonnull_align_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !nonnull !{}, !align !{i32 8}, !noundef !{}
+ %align_call_invalid = call ptr @callee_ptr(ptr null), !align !{i32 8}, !noundef !{}
+ %nonnull_call_invalid = call ptr @callee_ptr(ptr null), !nonnull !{}
+
+ %dereferenceable_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable !{i32 8}
+ %dereferenceable_or_null_call_valid1 = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable_or_null !{i32 8}
+ %dereferenceable_or_null_call_valid2 = call ptr @callee_ptr(ptr null), !dereferenceable_or_null !{i32 8}
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: store i32 1, ptr %alloc, align 4
+; CHECK-NEXT: %range_load_valid = load i32, ptr %alloc, align 4, !range !0, !noundef !1 => i32 1
+; CHECK-NEXT: %range_load_invalid = load i32, ptr %alloc, align 4, !range !2 => poison
+; CHECK-NEXT: %alloc_vec = alloca <8 x i32>, align 32 => ptr 0x20 [alloc_vec]
+; CHECK-NEXT: store <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7>, ptr %alloc_vec, align 32
+; CHECK-NEXT: %range_list_load_vec = load <8 x i32>, ptr %alloc_vec, align 32, !range !3 => { i32 0, i32 1, poison, i32 3, poison, i32 5, poison, i32 7 }
+; CHECK-NEXT: store float 0.000000e+00, ptr %alloc, align 4
+; CHECK-NEXT: %nofpclass_load_valid = load float, ptr %alloc, align 4, !noundef !1, !nofpclass !4 => float 0.000000e+00
+; CHECK-NEXT: %nofpclass_load_invalid = load float, ptr %alloc, align 4, !nofpclass !5 => poison
+; CHECK-NEXT: %alloc_ptr = alloca ptr, align 8 => ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: store ptr %alloc_ptr, ptr %alloc_ptr, align 8
+; CHECK-NEXT: %align_nonnull_load_valid = load ptr, ptr %alloc_ptr, align 8, !nonnull !1, !align !6, !noundef !1 => ptr 0x40 [dangling]
+; CHECK-NEXT: store ptr null, ptr %alloc_ptr, align 8
+; CHECK-NEXT: %align_load_valid = load ptr, ptr %alloc_ptr, align 8, !align !6, !noundef !1 => ptr 0x0 [dangling]
+; CHECK-NEXT: %nonnull_load_invalid = load ptr, ptr %alloc_ptr, align 8, !nonnull !1 => poison
+; CHECK-NEXT: Entering function: callee
+; CHECK-NEXT: ret i32 10
+; CHECK-NEXT: Exiting function: callee
+; CHECK-NEXT: %range_call_valid = call i32 @callee(), !range !7, !noundef !1 => i32 10
+; CHECK-NEXT: Entering function: callee
+; CHECK-NEXT: ret i32 10
+; CHECK-NEXT: Exiting function: callee
+; CHECK-NEXT: %range_call_invalid = call i32 @callee(), !range !0 => poison
+; CHECK-NEXT: Entering function: callee_fp
+; CHECK-NEXT: ret float 0.000000e+00
+; CHECK-NEXT: Exiting function: callee_fp
+; CHECK-NEXT: %nofpclass_call_valid = call float @callee_fp(), !noundef !1, !nofpclass !4 => float 0.000000e+00
+; CHECK-NEXT: Entering function: callee_fp
+; CHECK-NEXT: ret float 0.000000e+00
+; CHECK-NEXT: Exiting function: callee_fp
+; CHECK-NEXT: %nofpclass_call_invalid = call float @callee_fp(), !nofpclass !5 => poison
+; CHECK-NEXT: Entering function: callee_ptr
+; CHECK-NEXT: ptr %x = ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: ret ptr %x
+; CHECK-NEXT: Exiting function: callee_ptr
+; CHECK-NEXT: %nonnull_align_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !nonnull !1, !align !6, !noundef !1 => ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: Entering function: callee_ptr
+; CHECK-NEXT: ptr %x = ptr 0x0 [dangling]
+; CHECK-NEXT: ret ptr %x
+; CHECK-NEXT: Exiting function: callee_ptr
+; CHECK-NEXT: %align_call_invalid = call ptr @callee_ptr(ptr null), !align !6, !noundef !1 => ptr 0x0 [dangling]
+; CHECK-NEXT: Entering function: callee_ptr
+; CHECK-NEXT: ptr %x = ptr 0x0 [dangling]
+; CHECK-NEXT: ret ptr %x
+; CHECK-NEXT: Exiting function: callee_ptr
+; CHECK-NEXT: %nonnull_call_invalid = call ptr @callee_ptr(ptr null), !nonnull !1 => poison
+; CHECK-NEXT: Entering function: callee_ptr
+; CHECK-NEXT: ptr %x = ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: ret ptr %x
+; CHECK-NEXT: Exiting function: callee_ptr
+; CHECK-NEXT: %dereferenceable_call_valid = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable !6 => ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: Entering function: callee_ptr
+; CHECK-NEXT: ptr %x = ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: ret ptr %x
+; CHECK-NEXT: Exiting function: callee_ptr
+; CHECK-NEXT: %dereferenceable_or_null_call_valid1 = call ptr @callee_ptr(ptr %alloc_ptr), !dereferenceable_or_null !6 => ptr 0x40 [alloc_ptr]
+; CHECK-NEXT: Entering function: callee_ptr
+; CHECK-NEXT: ptr %x = ptr 0x0 [dangling]
+; CHECK-NEXT: ret ptr %x
+; CHECK-NEXT: Exiting function: callee_ptr
+; CHECK-NEXT: %dereferenceable_or_null_call_valid2 = call ptr @callee_ptr(ptr null), !dereferenceable_or_null !6 => ptr 0x0 [dangling]
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/metadata_noundef_ub.ll b/llvm/test/tools/llubi/metadata_noundef_ub.ll
new file mode 100644
index 0000000000000..2d9ef4da816df
--- /dev/null
+++ b/llvm/test/tools/llubi/metadata_noundef_ub.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 @main() {
+ %alloc = alloca i32
+ store i32 -1, ptr %alloc
+ %res = load i32, ptr %alloc, !noundef !{}, !range !{i32 0, i32 10}
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %alloc = alloca i32, align 4 => ptr 0x8 [alloc]
+; CHECK-NEXT: store i32 -1, ptr %alloc, align 4
+; CHECK-NEXT: Stacktrace:
+; CHECK-NEXT: #0 %res = load i32, ptr %alloc, align 4, !range !0, !noundef !1 at @main
+; CHECK-NEXT: Immediate UB detected: The value poison violates !noundef metadata.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 2b195ac38ecfc..e591e9acc181e 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -61,8 +61,7 @@ AnyValue Context::getConstantValueImpl(Constant *C) {
return AnyValue::getNullValue(*this, C->getType());
if (isa<ConstantPointerNull>(C))
- return Pointer::null(
- DL.getPointerSizeInBits(C->getType()->getPointerAddressSpace()));
+ return Pointer::null(C->getType()->getPointerAddressSpace(), DL);
if (auto *CI = dyn_cast<ConstantInt>(C)) {
if (auto *VecTy = dyn_cast<VectorType>(CI->getType()))
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 6d27accd6cb93..ec6ee07a35471 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -64,6 +64,81 @@ static AnyValue mulNoWrap(const APInt &LHS, const APInt &RHS, bool HasNSW,
return Res;
}
+/// Visit the scalar values recursively. The callback function may modify the
+/// value in-place.
+static void forEachScalarValue(AnyValue &V,
+ function_ref<void(AnyValue &)> Visit) {
+ if (V.isNone())
+ return;
+
+ if (V.isAggregate()) {
+ for (auto &SubValue : V.asAggregate())
+ forEachScalarValue(SubValue, Visit);
+ return;
+ }
+
+ Visit(V);
+}
+
+static void applyRangeAttr(AnyValue &V, const ConstantRange &CR) {
+ forEachScalarValue(V, [&](AnyValue &Scalar) {
+ if (Scalar.isInteger() && !CR.contains(Scalar.asInteger()))
+ Scalar = AnyValue::poison();
+ });
+}
+
+static void applyNoFPClassAttr(AnyValue &V, FPClassTest NoFPClass) {
+ forEachScalarValue(V, [NoFPClass](AnyValue &Scalar) {
+ if (Scalar.isFloat() && (Scalar.asFloat().classify() & NoFPClass))
+ Scalar = AnyValue::poison();
+ });
+}
+
+static void applyNonNullAttr(AnyValue &V, unsigned AS, const DataLayout &DL) {
+ if (V.isPointer() && V.asPointer().isNullPtr(AS, DL))
+ V = AnyValue::poison();
+}
+
+static void applyAlignAttr(AnyValue &V, Align Alignment) {
+ forEachScalarValue(V, [Alignment](AnyValue &Scalar) {
+ if (Scalar.isPointer() &&
+ Scalar.asPointer().address().countr_zero() < Log2(Alignment))
+ Scalar = AnyValue::poison();
+ });
+}
+
+static bool violatesNoUndefAttr(AnyValue &V) {
+ bool ContainsPoison = false;
+ forEachScalarValue(
+ V, [&](AnyValue &Scalar) { ContainsPoison |= Scalar.isPoison(); });
+ return ContainsPoison;
+}
+
+/// Assumes V is either a poison or a pointer.
+static bool violatesDereferenceableBytesAttr(const AnyValue &V, uint64_t Bytes,
+ bool OrNull, unsigned AS,
+ const DataLayout &DL) {
+ if (V.isPoison())
+ return true;
+
+ auto &Ptr = V.asPointer();
+ if (Ptr.isNullPtr(AS, DL)) {
+ if (OrNull)
+ return false;
+ return true;
+ }
+ auto *MO = Ptr.getMemoryObject();
+ if (!MO)
+ return true;
+
+ // TODO: check read_provenance
+ // TODO: check nofree for attributes/metadata.
+
+ const APInt &PtrAddr = Ptr.address();
+ return Bytes > MO->getSize() || PtrAddr.ult(MO->getAddress()) ||
+ PtrAddr.ugt(MO->getAddress() + MO->getSize() - Bytes);
+}
+
/// Instruction executor using the visitor pattern.
/// Unlike the Context class that manages the global state,
/// InstExecutor only maintains the state for call frames.
@@ -381,6 +456,14 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
return Boolean == BooleanKind::True;
}
+ APInt getIntNonPoison(const AnyValue &V) {
+ if (V.isPoison()) {
+ reportImmediateUB() << "Unexpected poison integer value.";
+ return APInt::getZero(64);
+ }
+ return V.asInteger();
+ }
+
public:
InstExecutor(Context &C, EventHandler &H, Function &F,
ArrayRef<AnyValue> Args, AnyValue &RetVal)
@@ -462,12 +545,18 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void returnFromCallee() {
- // TODO: handle retval attributes (Attributes from known callee should be
- // applied if available).
- // TODO: handle metadata
auto &CB = cast<CallBase>(*CurrentFrame->PC);
CurrentFrame->CalleeArgs.clear();
AnyValue &RetVal = CurrentFrame->CalleeRetVal;
+ if (Type *RetTy = CB.getType(); !RetTy->isVoidTy()) {
+ // Handle attributes on the return value (Attributes from resolved callee
+ // should be applied if available).
+ AttributeSet AttrsAtCallSite = CB.getRetAttributes();
+ AttributeSet AttrsAtCallee =
+ CurrentFrame->ResolvedCallee->getAttributes().getRetAttrs();
+ handleAttributes(RetTy, RetVal, AttrsAtCallSite, AttrsAtCallee);
+ handleMetadata(RetTy, RetVal, CB);
+ }
setResult(CB, std::move(RetVal));
if (auto *II = dyn_cast<InvokeInst>(&CB))
@@ -484,13 +573,86 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
case Intrinsic::assume:
switch (Args[0].asBoolean()) {
case BooleanKind::True:
+ for (unsigned Idx = 0; Idx < CB.getNumOperandBundles(); Idx++) {
+ OperandBundleUse OBU = CB.getOperandBundleAt(Idx);
+ auto GetBundleArg = [&](uint32_t Offset) -> Value * {
+ return OBU.Inputs[Offset];
+ };
+ if (OBU.Inputs.empty())
+ continue;
+ Value *WasOnVal = GetBundleArg(0);
+ // Bail out on unrecognized operand bundles.
+ if (!WasOnVal->getType()->isPointerTy())
+ continue;
+ unsigned AS = WasOnVal->getType()->getPointerAddressSpace();
+ const AnyValue &WasOn = getValue(WasOnVal);
+ if (WasOn.isPoison()) {
+ reportImmediateUB() << "Assume on poison pointer.";
+ break;
+ }
+ const Pointer &WasOnPtr = WasOn.asPointer();
+ Attribute::AttrKind Kind =
+ Attribute::getAttrKindFromName(OBU.getTagName());
+ switch (Kind) {
+ case Attribute::Alignment: {
+ // Alignment assumptions should have 2 or 3 arguments.
+ // If there are two integer arguments, use the largest power of 2
+ // that divides them as the alignment.
+ APInt Alignment = getIntNonPoison(getValue(GetBundleArg(1)));
+ if (OBU.Inputs.size() == 3) {
+ APInt Offset = getIntNonPoison(getValue(GetBundleArg(2)));
+ if (!Alignment.isZero() || !Offset.isZero())
+ Alignment = APInt::getOneBitSet(
+ std::max(Alignment.getBitWidth(), Offset.getBitWidth()),
+ std::min(Alignment.countr_zero(), Offset.countr_zero()));
+ }
+ if (!Alignment.isPowerOf2()) {
+ if (!WasOnPtr.address().isZero())
+ reportImmediateUB() << "Assume on nonzero pointer " << WasOn
+ << " with a "
+ "non-power-of-two alignment "
+ << Alignment << '.';
+ break;
+ }
+ if (WasOnPtr.address().countr_zero() < Alignment.logBase2())
+ reportImmediateUB()
+ << "The pointer " << WasOn << " violates align(" << Alignment
+ << ") assumption.";
+ break;
+ }
+ case Attribute::NonNull:
+ if (WasOnPtr.isNullPtr(AS, DL))
+ reportImmediateUB()
+ << "The pointer " << WasOn << " violates nonnull assumption.";
+ break;
+ case Attribute::Dereferenceable:
+ case Attribute::DereferenceableOrNull: {
+ APInt DereferenceableBytes =
+ getIntNonPoison(getValue(GetBundleArg(1)));
+ // Only n > 0 implies that the pointer is dereferenceable.
+ if (DereferenceableBytes.isZero())
+ break;
+ if (violatesDereferenceableBytesAttr(
+ WasOn, DereferenceableBytes.getLimitedValue(),
+ Kind == Attribute::DereferenceableOrNull, AS, DL))
+ reportImmediateUB() << "The pointer " << WasOn << " violates "
+ << (Kind == Attribute::DereferenceableOrNull
+ ? "dereferenceable_or_null("
+ : "dereferenceable(")
+ << DereferenceableBytes << ") assumption.";
+ break;
+ }
+ default:
+ // TODO: handle other operand bundles like separate_storage.
+ break;
+ }
+ }
break;
case BooleanKind::False:
case BooleanKind::Poison:
reportImmediateUB() << "Assume on false or poison condition.";
break;
}
- // TODO: handle llvm.assume with operand bundles
return AnyValue();
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end: {
@@ -924,10 +1086,134 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
return AnyValue();
}
+ /// Handle both poison-generating and UB-implying attributes for parameters
+ /// and return values.
+ void handleAttributes(Type *Ty, AnyValue &V, AttributeSet AttrsAtCallSite,
+ AttributeSet AttrsAtCallee) {
+ if (Ty->isIntOrIntVectorTy()) {
+ if (auto CRAttr = AttrsAtCallSite.getAttribute(Attribute::Range);
+ CRAttr.isValid())
+ applyRangeAttr(V, CRAttr.getRange());
+ if (auto CRAttr = AttrsAtCallee.getAttribute(Attribute::Range);
+ CRAttr.isValid())
+ applyRangeAttr(V, CRAttr.getRange());
+ }
+ if (AttributeFuncs::isNoFPClassCompatibleType(Ty)) {
+ if (auto CRAttr = AttrsAtCallSite.getAttribute(Attribute::NoFPClass);
+ CRAttr.isValid())
+ applyNoFPClassAttr(V, CRAttr.getNoFPClass());
+ if (auto CRAttr = AttrsAtCallee.getAttribute(Attribute::NoFPClass);
+ CRAttr.isValid())
+ applyNoFPClassAttr(V, CRAttr.getNoFPClass());
+ }
+ if (Ty->isPointerTy()) {
+ if (AttrsAtCallSite.hasAttribute(Attribute::NonNull) ||
+ AttrsAtCallee.hasAttribute(Attribute::NonNull))
+ applyNonNullAttr(V, Ty->getPointerAddressSpace(), DL);
+ }
+ if (Ty->isPtrOrPtrVectorTy()) {
+ if (MaybeAlign Align = AttrsAtCallSite.getAlignment())
+ applyAlignAttr(V, *Align);
+ if (MaybeAlign Align = AttrsAtCallee.getAlignment())
+ applyAlignAttr(V, *Align);
+ }
+ if ((AttrsAtCallSite.hasAttribute(Attribute::NoUndef) ||
+ AttrsAtCallee.hasAttribute(Attribute::NoUndef)) &&
+ violatesNoUndefAttr(V)) {
+ reportImmediateUB() << "The value " << V
+ << " violates noundef attribute.";
+ return;
+ }
+ if (Ty->isPointerTy()) {
+ unsigned AS = Ty->getPointerAddressSpace();
+ if (uint64_t DereferenceableBytes =
+ std::max(AttrsAtCallSite.getDereferenceableBytes(),
+ AttrsAtCallee.getDereferenceableBytes())) {
+ if (violatesDereferenceableBytesAttr(V, DereferenceableBytes,
+ /*OrNull=*/false, AS, DL))
+ reportImmediateUB()
+ << "The value " << V << " violates dereferenceable("
+ << DereferenceableBytes << ") attribute.";
+ } else if (uint64_t DereferenceableOrNullBytes =
+ std::max(AttrsAtCallSite.getDereferenceableOrNullBytes(),
+ AttrsAtCallee.getDereferenceableOrNullBytes())) {
+ if (violatesDereferenceableBytesAttr(V, DereferenceableOrNullBytes,
+ /*OrNull=*/true, AS, DL))
+ reportImmediateUB() << "The value " << V
+ << " violates "
+ "dereferenceable_or_null("
+ << DereferenceableOrNullBytes << ") attribute.";
+ }
+ }
+ }
+
+ /// Handle both poison-generating and UB-implying metadata on instructions.
+ void handleMetadata(Type *Ty, AnyValue &V, Instruction &I) {
+ auto ExtractFirstIntOperand = [](const MDNode *Node) {
+ return mdconst::extract<ConstantInt>(Node->getOperand(0))->getZExtValue();
+ };
+
+ if (Ty->isIntOrIntVectorTy()) {
+ if (MDNode *Ranges = I.getMetadata(LLVMContext::MD_range)) {
+ SmallVector<ConstantRange> RangeList;
+ for (uint32_t I = 0; I < Ranges->getNumOperands(); I += 2) {
+ RangeList.emplace_back(
+ mdconst::extract<ConstantInt>(Ranges->getOperand(I))->getValue(),
+ mdconst::extract<ConstantInt>(Ranges->getOperand(I + 1))
+ ->getValue());
+ }
+ forEachScalarValue(V, [&](AnyValue &Scalar) {
+ if (!Scalar.isInteger())
+ return;
+ for (auto &CR : RangeList)
+ if (CR.contains(Scalar.asInteger()))
+ return;
+ Scalar = AnyValue::poison();
+ });
+ }
+ }
+ if (AttributeFuncs::isNoFPClassCompatibleType(Ty)) {
+ if (const MDNode *NoFPClass = I.getMetadata(LLVMContext::MD_nofpclass)) {
+ applyNoFPClassAttr(
+ V, static_cast<FPClassTest>(ExtractFirstIntOperand(NoFPClass)));
+ }
+ }
+ if (Ty->isPointerTy()) {
+ if (I.hasMetadata(LLVMContext::MD_nonnull))
+ applyNonNullAttr(V, Ty->getPointerAddressSpace(), DL);
+ // Unlike align attributes, !align is only defined for pointer types.
+ if (const MDNode *Alignment = I.getMetadata(LLVMContext::MD_align))
+ applyAlignAttr(V, Align(ExtractFirstIntOperand(Alignment)));
+ }
+ if (I.hasMetadata(LLVMContext::MD_noundef) && violatesNoUndefAttr(V)) {
+ reportImmediateUB() << "The value " << V
+ << " violates !noundef metadata.";
+ return;
+ }
+ if (Ty->isPointerTy()) {
+ unsigned AS = Ty->getPointerAddressSpace();
+ if (const MDNode *DereferenceableBytes =
+ I.getMetadata(LLVMContext::MD_dereferenceable)) {
+ uint64_t Bytes = ExtractFirstIntOperand(DereferenceableBytes);
+ if (violatesDereferenceableBytesAttr(V, Bytes,
+ /*OrNull=*/false, AS, DL))
+ reportImmediateUB()
+ << "The value " << V << " violates !dereferenceable !{i64 "
+ << Bytes << "} metadata.";
+ } else if (const MDNode *DereferenceableOrNullBytes =
+ I.getMetadata(LLVMContext::MD_dereferenceable_or_null)) {
+ uint64_t Bytes = ExtractFirstIntOperand(DereferenceableOrNullBytes);
+ if (violatesDereferenceableBytesAttr(V, Bytes,
+ /*OrNull=*/true, AS, DL))
+ reportImmediateUB()
+ << "The value " << V << " violates !dereferenceable_or_null!{i64 "
+ << Bytes << "} metadata.";
+ }
+ }
+ }
+
void enterCall(CallBase &CB) {
Function *Callee = CB.getCalledFunction();
- // TODO: handle parameter attributes (Attributes from known callee should be
- // applied if available).
// TODO: handle byval/initializes
auto &CalleeArgs = CurrentFrame->CalleeArgs;
assert(CalleeArgs.empty() &&
@@ -973,6 +1259,19 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
assert(
Callee->getFunctionType() == CB.getFunctionType() &&
"Expected the callee function type to match the call site signature.");
+
+ // Handle parameter attributes (Attributes from resolved callee should be
+ // applied if available).
+ 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);
+ handleAttributes(ArgTy, ArgVal, AttrsAtCallSite, AttrsAtCallee);
+ }
+
CurrentFrame->ResolvedCallee = Callee;
if (Callee->isIntrinsic()) {
CurrentFrame->CalleeRetVal = callIntrinsic(CB, CalleeArgs);
@@ -1395,7 +1694,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
auto RetVal =
load(getValue(LI.getPointerOperand()), LI.getAlign(), LI.getType());
// TODO: track volatile loads
- // TODO: handle metadata
+ // TODO: Check undef bits when !noundef is set.
+ handleMetadata(LI.getType(), RetVal, LI);
setResult(LI, std::move(RetVal));
}
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index a1e58e4e57f47..c68b223d2d65a 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -125,7 +125,7 @@ AnyValue Library::executeFree(ArrayRef<AnyValue> Args) {
auto &Ptr = PtrVal.asPointer();
// no-op when free is called with a null pointer.
- if (Ptr.address().isZero())
+ if (Ptr.isNullPtr(/*AS=*/0, DL))
return AnyValue();
MemoryObject *Obj = Ptr.getMemoryObject();
diff --git a/llvm/tools/llubi/lib/Value.cpp b/llvm/tools/llubi/lib/Value.cpp
index f685e86efee20..82bf0f7b6eb22 100644
--- a/llvm/tools/llubi/lib/Value.cpp
+++ b/llvm/tools/llubi/lib/Value.cpp
@@ -31,8 +31,12 @@ void Pointer::print(raw_ostream &OS) const {
OS << "]";
}
-AnyValue Pointer::null(unsigned BitWidth) {
- return AnyValue(Pointer(nullptr, APInt::getZero(BitWidth)));
+AnyValue Pointer::null(unsigned AS, const DataLayout &DL) {
+ return AnyValue(Pointer(nullptr, DL.getNullPtrValue(AS)));
+}
+
+bool Pointer::isNullPtr(unsigned AS, const DataLayout &DL) const {
+ return Address == DL.getNullPtrValue(AS);
}
void AnyValue::print(raw_ostream &OS) const {
@@ -250,8 +254,7 @@ AnyValue AnyValue::getNullValue(Context &Ctx, Type *Ty) {
if (Ty->isFloatingPointTy())
return AnyValue(APFloat::getZero(Ty->getFltSemantics()));
if (Ty->isPointerTy())
- return Pointer::null(
- Ctx.getDataLayout().getPointerSizeInBits(Ty->getPointerAddressSpace()));
+ return Pointer::null(Ty->getPointerAddressSpace(), Ctx.getDataLayout());
if (auto *VecTy = dyn_cast<VectorType>(Ty)) {
uint32_t NumElements = Ctx.getEVL(VecTy->getElementCount());
return AnyValue(std::vector<AnyValue>(
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index b4686160ea8b8..dfaf5f23a15b0 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -12,6 +12,7 @@
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Type.h"
#include "llvm/Support/raw_ostream.h"
@@ -102,7 +103,8 @@ class Pointer {
Pointer getWithNewAddr(const APInt &NewAddr) const {
return Pointer(Obj, NewAddr);
}
- static AnyValue null(unsigned BitWidth);
+ static AnyValue null(unsigned AS, const DataLayout &DL);
+ bool isNullPtr(unsigned AS, const DataLayout &DL) const;
void print(raw_ostream &OS) const;
const APInt &address() const { return Address; }
MemoryObject *getMemoryObject() const { return Obj.get(); }
More information about the llvm-commits
mailing list