[llvm] e86750b - [llubi] Add basic support for icmp, terminators and function calls (#181393)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Feb 15 09:19:00 PST 2026
Author: Yingwei Zheng
Date: 2026-02-16T01:18:56+08:00
New Revision: e86750b29fa0ff207cd43213d66dabe565417638
URL: https://github.com/llvm/llvm-project/commit/e86750b29fa0ff207cd43213d66dabe565417638
DIFF: https://github.com/llvm/llvm-project/commit/e86750b29fa0ff207cd43213d66dabe565417638.diff
LOG: [llubi] Add basic support for icmp, terminators and function calls (#181393)
Note: icmp with integers is also introduced in this patch, as it is
needed to compute branch conditions.
Added:
llvm/test/tools/llubi/assume_false.ll
llvm/test/tools/llubi/assume_poison.ll
llvm/test/tools/llubi/br_poison.ll
llvm/test/tools/llubi/call_mismatched_signature.ll
llvm/test/tools/llubi/call_poison.ll
llvm/test/tools/llubi/controlflow.ll
llvm/test/tools/llubi/indirectbr_invalid.ll
llvm/test/tools/llubi/indirectbr_poison.ll
llvm/test/tools/llubi/infinite_loop.ll
llvm/test/tools/llubi/invoke_poison.ll
llvm/test/tools/llubi/stack_overflow.ll
llvm/test/tools/llubi/switch_poison.ll
llvm/test/tools/llubi/unreachable.ll
Modified:
llvm/test/tools/llubi/int_arith.ll
llvm/tools/llubi/lib/Context.cpp
llvm/tools/llubi/lib/Context.h
llvm/tools/llubi/lib/Interpreter.cpp
llvm/tools/llubi/lib/Value.h
llvm/tools/llubi/llubi.cpp
Removed:
################################################################################
diff --git a/llvm/test/tools/llubi/assume_false.ll b/llvm/test/tools/llubi/assume_false.ll
new file mode 100644
index 0000000000000..d621db1db97be
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_false.ll
@@ -0,0 +1,10 @@
+; 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 false)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Assume on false or poison condition.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/assume_poison.ll b/llvm/test/tools/llubi/assume_poison.ll
new file mode 100644
index 0000000000000..c178c19f2d221
--- /dev/null
+++ b/llvm/test/tools/llubi/assume_poison.ll
@@ -0,0 +1,10 @@
+; 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 poison)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Assume on false or poison condition.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/br_poison.ll b/llvm/test/tools/llubi/br_poison.ll
new file mode 100644
index 0000000000000..dcad8d0584767
--- /dev/null
+++ b/llvm/test/tools/llubi/br_poison.ll
@@ -0,0 +1,13 @@
+; 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() {
+entry:
+ br i1 poison, label %exit, label %exit
+
+exit:
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Branch on poison condition.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/call_mismatched_signature.ll b/llvm/test/tools/llubi/call_mismatched_signature.ll
new file mode 100644
index 0000000000000..9c65dd231826c
--- /dev/null
+++ b/llvm/test/tools/llubi/call_mismatched_signature.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 i32 @foo() {
+ ret i32 42
+}
+
+define void @main() {
+ call void @foo()
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Indirect call through a function pointer with mismatched signature.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/call_poison.ll b/llvm/test/tools/llubi/call_poison.ll
new file mode 100644
index 0000000000000..a9dec9e0603f1
--- /dev/null
+++ b/llvm/test/tools/llubi/call_poison.ll
@@ -0,0 +1,11 @@
+; 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() {
+entry:
+ call void poison()
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Indirect call through poison function pointer.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/controlflow.ll b/llvm/test/tools/llubi/controlflow.ll
new file mode 100644
index 0000000000000..7b14d90b688d9
--- /dev/null
+++ b/llvm/test/tools/llubi/controlflow.ll
@@ -0,0 +1,430 @@
+; 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 @factorial_iterative(i32 %n) {
+entry:
+ br label %loop.body
+
+loop.body:
+ %i = phi i32 [ 1, %entry ], [ %i.next, %loop.body ]
+ %acc = phi i32 [ 1, %entry ], [ %acc.next, %loop.body ]
+ %acc.next = mul i32 %acc, %i
+ %i.next = add i32 %i, 1
+ %cond = icmp eq i32 %i, %n
+ br i1 %cond, label %loop.end, label %loop.body
+
+loop.end:
+ ret i32 %acc.next
+}
+
+define i32 @factorial_recursive(i32 %x) {
+entry:
+ %cond = icmp eq i32 %x, 0
+ br i1 %cond, label %base.case, label %recursive.case
+
+base.case:
+ ret i32 1
+
+recursive.case:
+ %x.prev = add i32 %x, -1
+ %recursive.result = call i32 @factorial_recursive(i32 %x.prev)
+ %result = mul i32 %x, %recursive.result
+ ret i32 %result
+}
+
+define i32 @get_n(i32 %x) {
+ %x.next = add i32 %x, 1
+ ret i32 %x.next
+}
+
+define i32 @fib(ptr %self, i32 %n) {
+ %cond = icmp ugt i32 %n, 1
+ br i1 %cond, label %if.then, label %if.else
+
+if.then:
+ %sub1 = sub i32 %n, 1
+ %sub2 = sub i32 %n, 2
+ %res1 = call i32 @fib(ptr %self, i32 %sub1)
+ %res2 = call i32 @fib(ptr %self, i32 %sub2)
+ %result = add i32 %res1, %res2
+ ret i32 %result
+
+if.else:
+ ret i32 1
+}
+
+define i32 @fib_iterative(i32 %n) {
+entry:
+ br label %loop.body
+
+loop.body:
+ %phi = phi i32 [ %n, %entry ], [ %sub1, %loop.body ]
+ ; Make sure %fib2 is not updated before %fib1.
+ %fib2 = phi i32 [ 1, %entry ], [ %add, %loop.body ]
+ %fib1 = phi i32 [ 1, %entry ], [ %fib2, %loop.body ]
+ %sub1 = sub i32 %phi, 1
+ %add = add i32 %fib1, %fib2
+ %loop.cond = icmp ugt i32 %phi, 1
+ br i1 %loop.cond, label %loop.body, label %exit
+
+exit:
+ ret i32 %fib2
+}
+
+define i32 @main() {
+ ; params & retval
+ %n = call i32 @get_n(i32 4)
+ ; loops
+ %result_iterative = call i32 @factorial_iterative(i32 %n)
+ ; recursion
+ %result_recursive = call i32 @factorial_recursive(i32 %n)
+ ; intrinsics
+ %cmp = icmp eq i32 %result_iterative, %result_recursive
+ call void @llvm.assume(i1 %cmp)
+ ; switch -> case destination
+ switch i32 %result_iterative, label %exit [
+ i32 120, label %next1
+ ]
+
+next1:
+ ; switch -> default destination
+ switch i32 %result_recursive, label %next2 [
+ i32 0, label %exit
+ ]
+
+next2:
+ ; call blackbox
+ call void asm sideeffect "", ""()
+ ; invoke blackbox
+ invoke void asm sideeffect "", ""() to label %next3 unwind label %cleanup
+
+next3:
+ ; callbr blackbox
+ callbr void asm sideeffect "", ""() to label %next4 []
+
+next4:
+ ; invoke
+ %res = invoke i32 @get_n(i32 0) to label %next5 unwind label %cleanup
+
+next5:
+ ; indirectbr
+ indirectbr ptr blockaddress(@main, %exit), [ label %exit ]
+
+cleanup:
+ landingpad {} cleanup
+ ret i32 1
+
+exit:
+ ; indirect call
+ %fib5_v1 = call i32 @fib(ptr @fib, i32 5)
+ %fib5_v2 = call i32 @fib_iterative(i32 5)
+ %test = icmp ne i32 %fib5_v1, %fib5_v2
+ %ret = zext i1 %test to i32
+ ret i32 %ret
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Entering function: get_n
+; CHECK-NEXT: i32 %x = i32 4
+; CHECK-NEXT: %x.next = add i32 %x, 1 => i32 5
+; CHECK-NEXT: ret i32 %x.next
+; CHECK-NEXT: Exiting function: get_n
+; CHECK-NEXT: %n = call i32 @get_n(i32 4) => i32 5
+; CHECK-NEXT: Entering function: factorial_iterative
+; CHECK-NEXT: i32 %n = i32 5
+; CHECK-NEXT: br label %loop.body jump to %loop.body
+; CHECK-NEXT: %i = phi i32 [ 1, %entry ], [ %i.next, %loop.body ] => i32 1
+; CHECK-NEXT: %acc = phi i32 [ 1, %entry ], [ %acc.next, %loop.body ] => i32 1
+; CHECK-NEXT: %acc.next = mul i32 %acc, %i => i32 1
+; CHECK-NEXT: %i.next = add i32 %i, 1 => i32 2
+; CHECK-NEXT: %cond = icmp eq i32 %i, %n => F
+; CHECK-NEXT: br i1 %cond, label %loop.end, label %loop.body jump to %loop.body
+; CHECK-NEXT: %i = phi i32 [ 1, %entry ], [ %i.next, %loop.body ] => i32 2
+; CHECK-NEXT: %acc = phi i32 [ 1, %entry ], [ %acc.next, %loop.body ] => i32 1
+; CHECK-NEXT: %acc.next = mul i32 %acc, %i => i32 2
+; CHECK-NEXT: %i.next = add i32 %i, 1 => i32 3
+; CHECK-NEXT: %cond = icmp eq i32 %i, %n => F
+; CHECK-NEXT: br i1 %cond, label %loop.end, label %loop.body jump to %loop.body
+; CHECK-NEXT: %i = phi i32 [ 1, %entry ], [ %i.next, %loop.body ] => i32 3
+; CHECK-NEXT: %acc = phi i32 [ 1, %entry ], [ %acc.next, %loop.body ] => i32 2
+; CHECK-NEXT: %acc.next = mul i32 %acc, %i => i32 6
+; CHECK-NEXT: %i.next = add i32 %i, 1 => i32 4
+; CHECK-NEXT: %cond = icmp eq i32 %i, %n => F
+; CHECK-NEXT: br i1 %cond, label %loop.end, label %loop.body jump to %loop.body
+; CHECK-NEXT: %i = phi i32 [ 1, %entry ], [ %i.next, %loop.body ] => i32 4
+; CHECK-NEXT: %acc = phi i32 [ 1, %entry ], [ %acc.next, %loop.body ] => i32 6
+; CHECK-NEXT: %acc.next = mul i32 %acc, %i => i32 24
+; CHECK-NEXT: %i.next = add i32 %i, 1 => i32 5
+; CHECK-NEXT: %cond = icmp eq i32 %i, %n => F
+; CHECK-NEXT: br i1 %cond, label %loop.end, label %loop.body jump to %loop.body
+; CHECK-NEXT: %i = phi i32 [ 1, %entry ], [ %i.next, %loop.body ] => i32 5
+; CHECK-NEXT: %acc = phi i32 [ 1, %entry ], [ %acc.next, %loop.body ] => i32 24
+; CHECK-NEXT: %acc.next = mul i32 %acc, %i => i32 120
+; CHECK-NEXT: %i.next = add i32 %i, 1 => i32 6
+; CHECK-NEXT: %cond = icmp eq i32 %i, %n => T
+; CHECK-NEXT: br i1 %cond, label %loop.end, label %loop.body jump to %loop.end
+; CHECK-NEXT: ret i32 %acc.next
+; CHECK-NEXT: Exiting function: factorial_iterative
+; CHECK-NEXT: %result_iterative = call i32 @factorial_iterative(i32 %n) => i32 120
+; CHECK-NEXT: Entering function: factorial_recursive
+; CHECK-NEXT: i32 %x = i32 5
+; CHECK-NEXT: %cond = icmp eq i32 %x, 0 => F
+; CHECK-NEXT: br i1 %cond, label %base.case, label %recursive.case jump to %recursive.case
+; CHECK-NEXT: %x.prev = add i32 %x, -1 => i32 4
+; CHECK-NEXT: Entering function: factorial_recursive
+; CHECK-NEXT: i32 %x = i32 4
+; CHECK-NEXT: %cond = icmp eq i32 %x, 0 => F
+; CHECK-NEXT: br i1 %cond, label %base.case, label %recursive.case jump to %recursive.case
+; CHECK-NEXT: %x.prev = add i32 %x, -1 => i32 3
+; CHECK-NEXT: Entering function: factorial_recursive
+; CHECK-NEXT: i32 %x = i32 3
+; CHECK-NEXT: %cond = icmp eq i32 %x, 0 => F
+; CHECK-NEXT: br i1 %cond, label %base.case, label %recursive.case jump to %recursive.case
+; CHECK-NEXT: %x.prev = add i32 %x, -1 => i32 2
+; CHECK-NEXT: Entering function: factorial_recursive
+; CHECK-NEXT: i32 %x = i32 2
+; CHECK-NEXT: %cond = icmp eq i32 %x, 0 => F
+; CHECK-NEXT: br i1 %cond, label %base.case, label %recursive.case jump to %recursive.case
+; CHECK-NEXT: %x.prev = add i32 %x, -1 => i32 1
+; CHECK-NEXT: Entering function: factorial_recursive
+; CHECK-NEXT: i32 %x = i32 1
+; CHECK-NEXT: %cond = icmp eq i32 %x, 0 => F
+; CHECK-NEXT: br i1 %cond, label %base.case, label %recursive.case jump to %recursive.case
+; CHECK-NEXT: %x.prev = add i32 %x, -1 => i32 0
+; CHECK-NEXT: Entering function: factorial_recursive
+; CHECK-NEXT: i32 %x = i32 0
+; CHECK-NEXT: %cond = icmp eq i32 %x, 0 => T
+; CHECK-NEXT: br i1 %cond, label %base.case, label %recursive.case jump to %base.case
+; CHECK-NEXT: ret i32 1
+; CHECK-NEXT: Exiting function: factorial_recursive
+; CHECK-NEXT: %recursive.result = call i32 @factorial_recursive(i32 %x.prev) => i32 1
+; CHECK-NEXT: %result = mul i32 %x, %recursive.result => i32 1
+; CHECK-NEXT: ret i32 %result
+; CHECK-NEXT: Exiting function: factorial_recursive
+; CHECK-NEXT: %recursive.result = call i32 @factorial_recursive(i32 %x.prev) => i32 1
+; CHECK-NEXT: %result = mul i32 %x, %recursive.result => i32 2
+; CHECK-NEXT: ret i32 %result
+; CHECK-NEXT: Exiting function: factorial_recursive
+; CHECK-NEXT: %recursive.result = call i32 @factorial_recursive(i32 %x.prev) => i32 2
+; CHECK-NEXT: %result = mul i32 %x, %recursive.result => i32 6
+; CHECK-NEXT: ret i32 %result
+; CHECK-NEXT: Exiting function: factorial_recursive
+; CHECK-NEXT: %recursive.result = call i32 @factorial_recursive(i32 %x.prev) => i32 6
+; CHECK-NEXT: %result = mul i32 %x, %recursive.result => i32 24
+; CHECK-NEXT: ret i32 %result
+; CHECK-NEXT: Exiting function: factorial_recursive
+; CHECK-NEXT: %recursive.result = call i32 @factorial_recursive(i32 %x.prev) => i32 24
+; CHECK-NEXT: %result = mul i32 %x, %recursive.result => i32 120
+; CHECK-NEXT: ret i32 %result
+; CHECK-NEXT: Exiting function: factorial_recursive
+; CHECK-NEXT: %result_recursive = call i32 @factorial_recursive(i32 %n) => i32 120
+; CHECK-NEXT: %cmp = icmp eq i32 %result_iterative, %result_recursive => T
+; CHECK-NEXT: call void @llvm.assume(i1 %cmp)
+; CHECK-NEXT: switch i32 %result_iterative, label %exit [
+; CHECK-NEXT: i32 120, label %next1
+; CHECK-NEXT: ] jump to %next1
+; CHECK-NEXT: switch i32 %result_recursive, label %next2 [
+; CHECK-NEXT: i32 0, label %exit
+; CHECK-NEXT: ] jump to %next2
+; CHECK-NEXT: call void asm sideeffect "", ""()
+; CHECK-NEXT: invoke void asm sideeffect "", ""()
+; CHECK-NEXT: to label %next3 unwind label %cleanup
+; CHECK-NEXT: invoke void asm sideeffect "", ""()
+; CHECK-NEXT: to label %next3 unwind label %cleanup jump to %next3
+; CHECK-NEXT: callbr void asm sideeffect "", ""()
+; CHECK-NEXT: to label %next4 [] jump to %next4
+; CHECK-NEXT: Entering function: get_n
+; CHECK-NEXT: i32 %x = i32 0
+; CHECK-NEXT: %x.next = add i32 %x, 1 => i32 1
+; CHECK-NEXT: ret i32 %x.next
+; CHECK-NEXT: Exiting function: get_n
+; CHECK-NEXT: %res = invoke i32 @get_n(i32 0)
+; CHECK-NEXT: to label %next5 unwind label %cleanup => i32 1
+; CHECK-NEXT: %res = invoke i32 @get_n(i32 0)
+; CHECK-NEXT: to label %next5 unwind label %cleanup jump to %next5
+; CHECK-NEXT: indirectbr ptr blockaddress(@main, %exit), [label %exit] jump to %exit
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 5
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 4
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 3
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 4
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 3
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 2
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 3
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 2
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 1
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 2
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 1
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 0
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 1
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => F
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.else
+; CHECK-NEXT: ret i32 1
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res1 = call i32 @fib(ptr %self, i32 %sub1) => i32 1
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 0
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => F
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.else
+; CHECK-NEXT: ret i32 1
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res2 = call i32 @fib(ptr %self, i32 %sub2) => i32 1
+; CHECK-NEXT: %result = add i32 %res1, %res2 => i32 2
+; CHECK-NEXT: ret i32 %result
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res1 = call i32 @fib(ptr %self, i32 %sub1) => i32 2
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 1
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => F
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.else
+; CHECK-NEXT: ret i32 1
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res2 = call i32 @fib(ptr %self, i32 %sub2) => i32 1
+; CHECK-NEXT: %result = add i32 %res1, %res2 => i32 3
+; CHECK-NEXT: ret i32 %result
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res1 = call i32 @fib(ptr %self, i32 %sub1) => i32 3
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 2
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 1
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 0
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 1
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => F
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.else
+; CHECK-NEXT: ret i32 1
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res1 = call i32 @fib(ptr %self, i32 %sub1) => i32 1
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 0
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => F
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.else
+; CHECK-NEXT: ret i32 1
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res2 = call i32 @fib(ptr %self, i32 %sub2) => i32 1
+; CHECK-NEXT: %result = add i32 %res1, %res2 => i32 2
+; CHECK-NEXT: ret i32 %result
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res2 = call i32 @fib(ptr %self, i32 %sub2) => i32 2
+; CHECK-NEXT: %result = add i32 %res1, %res2 => i32 5
+; CHECK-NEXT: ret i32 %result
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res1 = call i32 @fib(ptr %self, i32 %sub1) => i32 5
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 3
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 2
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 1
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 2
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 1
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 0
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 1
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => F
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.else
+; CHECK-NEXT: ret i32 1
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res1 = call i32 @fib(ptr %self, i32 %sub1) => i32 1
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 0
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => F
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.else
+; CHECK-NEXT: ret i32 1
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res2 = call i32 @fib(ptr %self, i32 %sub2) => i32 1
+; CHECK-NEXT: %result = add i32 %res1, %res2 => i32 2
+; CHECK-NEXT: ret i32 %result
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res1 = call i32 @fib(ptr %self, i32 %sub1) => i32 2
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: ptr %self = ptr 0x8 [fib]
+; CHECK-NEXT: i32 %n = i32 1
+; CHECK-NEXT: %cond = icmp ugt i32 %n, 1 => F
+; CHECK-NEXT: br i1 %cond, label %if.then, label %if.else jump to %if.else
+; CHECK-NEXT: ret i32 1
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res2 = call i32 @fib(ptr %self, i32 %sub2) => i32 1
+; CHECK-NEXT: %result = add i32 %res1, %res2 => i32 3
+; CHECK-NEXT: ret i32 %result
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res2 = call i32 @fib(ptr %self, i32 %sub2) => i32 3
+; CHECK-NEXT: %result = add i32 %res1, %res2 => i32 8
+; CHECK-NEXT: ret i32 %result
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %fib5_v1 = call i32 @fib(ptr @fib, i32 5) => i32 8
+; CHECK-NEXT: Entering function: fib_iterative
+; CHECK-NEXT: i32 %n = i32 5
+; CHECK-NEXT: br label %loop.body jump to %loop.body
+; CHECK-NEXT: %phi = phi i32 [ %n, %entry ], [ %sub1, %loop.body ] => i32 5
+; CHECK-NEXT: %fib2 = phi i32 [ 1, %entry ], [ %add, %loop.body ] => i32 1
+; CHECK-NEXT: %fib1 = phi i32 [ 1, %entry ], [ %fib2, %loop.body ] => i32 1
+; CHECK-NEXT: %sub1 = sub i32 %phi, 1 => i32 4
+; CHECK-NEXT: %add = add i32 %fib1, %fib2 => i32 2
+; CHECK-NEXT: %loop.cond = icmp ugt i32 %phi, 1 => T
+; CHECK-NEXT: br i1 %loop.cond, label %loop.body, label %exit jump to %loop.body
+; CHECK-NEXT: %phi = phi i32 [ %n, %entry ], [ %sub1, %loop.body ] => i32 4
+; CHECK-NEXT: %fib2 = phi i32 [ 1, %entry ], [ %add, %loop.body ] => i32 2
+; CHECK-NEXT: %fib1 = phi i32 [ 1, %entry ], [ %fib2, %loop.body ] => i32 1
+; CHECK-NEXT: %sub1 = sub i32 %phi, 1 => i32 3
+; CHECK-NEXT: %add = add i32 %fib1, %fib2 => i32 3
+; CHECK-NEXT: %loop.cond = icmp ugt i32 %phi, 1 => T
+; CHECK-NEXT: br i1 %loop.cond, label %loop.body, label %exit jump to %loop.body
+; CHECK-NEXT: %phi = phi i32 [ %n, %entry ], [ %sub1, %loop.body ] => i32 3
+; CHECK-NEXT: %fib2 = phi i32 [ 1, %entry ], [ %add, %loop.body ] => i32 3
+; CHECK-NEXT: %fib1 = phi i32 [ 1, %entry ], [ %fib2, %loop.body ] => i32 2
+; CHECK-NEXT: %sub1 = sub i32 %phi, 1 => i32 2
+; CHECK-NEXT: %add = add i32 %fib1, %fib2 => i32 5
+; CHECK-NEXT: %loop.cond = icmp ugt i32 %phi, 1 => T
+; CHECK-NEXT: br i1 %loop.cond, label %loop.body, label %exit jump to %loop.body
+; CHECK-NEXT: %phi = phi i32 [ %n, %entry ], [ %sub1, %loop.body ] => i32 2
+; CHECK-NEXT: %fib2 = phi i32 [ 1, %entry ], [ %add, %loop.body ] => i32 5
+; CHECK-NEXT: %fib1 = phi i32 [ 1, %entry ], [ %fib2, %loop.body ] => i32 3
+; CHECK-NEXT: %sub1 = sub i32 %phi, 1 => i32 1
+; CHECK-NEXT: %add = add i32 %fib1, %fib2 => i32 8
+; CHECK-NEXT: %loop.cond = icmp ugt i32 %phi, 1 => T
+; CHECK-NEXT: br i1 %loop.cond, label %loop.body, label %exit jump to %loop.body
+; CHECK-NEXT: %phi = phi i32 [ %n, %entry ], [ %sub1, %loop.body ] => i32 1
+; CHECK-NEXT: %fib2 = phi i32 [ 1, %entry ], [ %add, %loop.body ] => i32 8
+; CHECK-NEXT: %fib1 = phi i32 [ 1, %entry ], [ %fib2, %loop.body ] => i32 5
+; CHECK-NEXT: %sub1 = sub i32 %phi, 1 => i32 0
+; CHECK-NEXT: %add = add i32 %fib1, %fib2 => i32 13
+; CHECK-NEXT: %loop.cond = icmp ugt i32 %phi, 1 => F
+; CHECK-NEXT: br i1 %loop.cond, label %loop.body, label %exit jump to %exit
+; CHECK-NEXT: ret i32 %fib2
+; CHECK-NEXT: Exiting function: fib_iterative
+; CHECK-NEXT: %fib5_v2 = call i32 @fib_iterative(i32 5) => i32 8
+; CHECK-NEXT: %test = icmp ne i32 %fib5_v1, %fib5_v2 => F
+; CHECK-NEXT: %ret = zext i1 %test to i32 => i32 0
+; CHECK-NEXT: ret i32 %ret
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/indirectbr_invalid.ll b/llvm/test/tools/llubi/indirectbr_invalid.ll
new file mode 100644
index 0000000000000..9ffb26b0aebbb
--- /dev/null
+++ b/llvm/test/tools/llubi/indirectbr_invalid.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() {
+entry:
+ indirectbr ptr blockaddress(@main, %bb2), [label %exit]
+
+bb2:
+ br label %exit
+
+exit:
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Indirect branch on unlisted target BB.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/indirectbr_poison.ll b/llvm/test/tools/llubi/indirectbr_poison.ll
new file mode 100644
index 0000000000000..076aa79c50a52
--- /dev/null
+++ b/llvm/test/tools/llubi/indirectbr_poison.ll
@@ -0,0 +1,13 @@
+; 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() {
+entry:
+ indirectbr ptr poison, [label %exit]
+
+exit:
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Indirect branch on poison.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/infinite_loop.ll b/llvm/test/tools/llubi/infinite_loop.ll
new file mode 100644
index 0000000000000..bc0fda540b758
--- /dev/null
+++ b/llvm/test/tools/llubi/infinite_loop.ll
@@ -0,0 +1,23 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose --max-steps 10 < %s 2>&1 | FileCheck %s
+
+define void @main() {
+entry:
+ br label %loop
+
+loop:
+ br label %loop
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: br label %loop jump to %loop
+; CHECK-NEXT: br label %loop jump to %loop
+; CHECK-NEXT: br label %loop jump to %loop
+; CHECK-NEXT: br label %loop jump to %loop
+; CHECK-NEXT: br label %loop jump to %loop
+; CHECK-NEXT: br label %loop jump to %loop
+; CHECK-NEXT: br label %loop jump to %loop
+; CHECK-NEXT: br label %loop jump to %loop
+; CHECK-NEXT: br label %loop jump to %loop
+; CHECK-NEXT: br label %loop jump to %loop
+; CHECK-NEXT: Error: Exceeded maximum number of execution steps.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/int_arith.ll b/llvm/test/tools/llubi/int_arith.ll
index db16d0c9cf2c5..53d2c1af730ba 100644
--- a/llvm/test/tools/llubi/int_arith.ll
+++ b/llvm/test/tools/llubi/int_arith.ll
@@ -79,6 +79,11 @@ define void @main() {
%select_vec2 = select i1 false, <2 x i32> splat(i32 10), <2 x i32> splat(i32 20)
%select_struct = select i1 false, {i32, [2 x i1], { <2 x i16> }} zeroinitializer, {i32, [2 x i1], { <2 x i16> }} {i32 0, [2 x i1] [i1 true, i1 poison],{ <2 x i16> } { <2 x i16> <i16 1, i16 2> }}
+ %icmp = icmp eq i32 1, 2
+ %icmp_poison = icmp eq i32 poison, 0
+ %icmp_samgsign = icmp samesign ult i32 1, 0
+ %icmp_samesign_poison = icmp samesign ult i32 1, -1
+
ret void
}
; CHECK: Entering function: main
@@ -143,5 +148,9 @@ define void @main() {
; CHECK-NEXT: %select_vec1 = select <3 x i1> <i1 true, i1 false, i1 poison>, <3 x i32> splat (i32 10), <3 x i32> splat (i32 20) => { i32 10, i32 20, poison }
; CHECK-NEXT: %select_vec2 = select i1 false, <2 x i32> splat (i32 10), <2 x i32> splat (i32 20) => { i32 20, i32 20 }
; CHECK-NEXT: %select_struct = select i1 false, { i32, [2 x i1], { <2 x i16> } } zeroinitializer, { i32, [2 x i1], { <2 x i16> } } { i32 0, [2 x i1] [i1 true, i1 poison], { <2 x i16> } { <2 x i16> <i16 1, i16 2> } } => { i32 0, { T, poison }, { { i16 1, i16 2 } } }
+; CHECK-NEXT: %icmp = icmp eq i32 1, 2 => F
+; CHECK-NEXT: %icmp_poison = icmp eq i32 poison, 0 => poison
+; CHECK-NEXT: %icmp_samgsign = icmp samesign ult i32 1, 0 => F
+; CHECK-NEXT: %icmp_samesign_poison = icmp samesign ult i32 1, -1 => poison
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/invoke_poison.ll b/llvm/test/tools/llubi/invoke_poison.ll
new file mode 100644
index 0000000000000..709f3e96b8a28
--- /dev/null
+++ b/llvm/test/tools/llubi/invoke_poison.ll
@@ -0,0 +1,17 @@
+; 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() {
+entry:
+ invoke void poison() to label %exit unwind label %cleanup
+
+cleanup:
+ landingpad {} cleanup
+ ret void
+
+exit:
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Indirect call through poison function pointer.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/stack_overflow.ll b/llvm/test/tools/llubi/stack_overflow.ll
new file mode 100644
index 0000000000000..a241d176ad696
--- /dev/null
+++ b/llvm/test/tools/llubi/stack_overflow.ll
@@ -0,0 +1,107 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose --max-stack-depth 10 < %s 2>&1 | FileCheck %s
+
+define i32 @fib(i32 %n) {
+entry:
+ %cmp = icmp ugt i32 %n, 1
+ br i1 %cmp, label %if.then, label %if.else
+
+if.then:
+ %sub1 = sub i32 %n, 1
+ %sub2 = sub i32 %n, 2
+ %call1 = call i32 @fib(i32 %sub1)
+ %call2 = call i32 @fib(i32 %sub2)
+ %add = add i32 %call1, %call2
+ ret i32 %add
+
+if.else:
+ ret i32 1
+}
+
+define void @main() {
+entry:
+ %res1 = call i32 @fib(i32 2)
+ %res2 = call i32 @fib(i32 50)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: i32 %n = i32 2
+; CHECK-NEXT: %cmp = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cmp, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 1
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 0
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: i32 %n = i32 1
+; CHECK-NEXT: %cmp = icmp ugt i32 %n, 1 => F
+; CHECK-NEXT: br i1 %cmp, label %if.then, label %if.else jump to %if.else
+; CHECK-NEXT: ret i32 1
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %call1 = call i32 @fib(i32 %sub1) => i32 1
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: i32 %n = i32 0
+; CHECK-NEXT: %cmp = icmp ugt i32 %n, 1 => F
+; CHECK-NEXT: br i1 %cmp, label %if.then, label %if.else jump to %if.else
+; CHECK-NEXT: ret i32 1
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %call2 = call i32 @fib(i32 %sub2) => i32 1
+; CHECK-NEXT: %add = add i32 %call1, %call2 => i32 2
+; CHECK-NEXT: ret i32 %add
+; CHECK-NEXT: Exiting function: fib
+; CHECK-NEXT: %res1 = call i32 @fib(i32 2) => i32 2
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: i32 %n = i32 50
+; CHECK-NEXT: %cmp = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cmp, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 49
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 48
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: i32 %n = i32 49
+; CHECK-NEXT: %cmp = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cmp, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 48
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 47
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: i32 %n = i32 48
+; CHECK-NEXT: %cmp = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cmp, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 47
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 46
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: i32 %n = i32 47
+; CHECK-NEXT: %cmp = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cmp, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 46
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 45
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: i32 %n = i32 46
+; CHECK-NEXT: %cmp = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cmp, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 45
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 44
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: i32 %n = i32 45
+; CHECK-NEXT: %cmp = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cmp, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 44
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 43
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: i32 %n = i32 44
+; CHECK-NEXT: %cmp = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cmp, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 43
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 42
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: i32 %n = i32 43
+; CHECK-NEXT: %cmp = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cmp, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 42
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 41
+; CHECK-NEXT: Entering function: fib
+; CHECK-NEXT: i32 %n = i32 42
+; CHECK-NEXT: %cmp = icmp ugt i32 %n, 1 => T
+; CHECK-NEXT: br i1 %cmp, label %if.then, label %if.else jump to %if.then
+; CHECK-NEXT: %sub1 = sub i32 %n, 1 => i32 41
+; CHECK-NEXT: %sub2 = sub i32 %n, 2 => i32 40
+; CHECK-NEXT: Error: Maximum stack depth exceeded.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/switch_poison.ll b/llvm/test/tools/llubi/switch_poison.ll
new file mode 100644
index 0000000000000..32316a467063a
--- /dev/null
+++ b/llvm/test/tools/llubi/switch_poison.ll
@@ -0,0 +1,13 @@
+; 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() {
+entry:
+ switch i32 poison, label %exit []
+
+exit:
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Switch on poison condition.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/unreachable.ll b/llvm/test/tools/llubi/unreachable.ll
new file mode 100644
index 0000000000000..c24a02eb5eb86
--- /dev/null
+++ b/llvm/test/tools/llubi/unreachable.ll
@@ -0,0 +1,9 @@
+; 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() {
+ unreachable
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Unreachable code.
+; 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 8e720d85d5ebc..9e3647be00988 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -21,6 +21,37 @@ Context::Context(Module &M)
Context::~Context() = default;
+bool Context::initGlobalValues() {
+ // Register all function and block targets that may be used by indirect calls
+ // and branches.
+ for (Function &F : M) {
+ if (F.hasAddressTaken()) {
+ // TODO: Use precise alignment for function pointers if it is necessary.
+ auto FuncObj = allocate(0, F.getPointerAlignment(DL).value(), F.getName(),
+ DL.getProgramAddressSpace(), MemInitKind::Zeroed);
+ if (!FuncObj)
+ return false;
+ ValidFuncTargets.try_emplace(FuncObj->getAddress(),
+ std::make_pair(&F, FuncObj));
+ FuncAddrMap.try_emplace(&F, deriveFromMemoryObject(FuncObj));
+ }
+
+ for (BasicBlock &BB : F) {
+ if (!BB.hasAddressTaken())
+ continue;
+ auto BlockObj = allocate(0, 1, BB.getName(), DL.getProgramAddressSpace(),
+ MemInitKind::Zeroed);
+ if (!BlockObj)
+ return false;
+ ValidBlockTargets.try_emplace(BlockObj->getAddress(),
+ std::make_pair(&BB, BlockObj));
+ BlockAddrMap.try_emplace(&BB, deriveFromMemoryObject(BlockObj));
+ }
+ }
+ // TODO: initialize global variables.
+ return true;
+}
+
AnyValue Context::getConstantValueImpl(Constant *C) {
if (isa<PoisonValue>(C))
return AnyValue::getPoisonValue(*this, C->getType());
@@ -51,6 +82,12 @@ AnyValue Context::getConstantValueImpl(Constant *C) {
return std::move(Elts);
}
+ if (auto *BA = dyn_cast<BlockAddress>(C))
+ return BlockAddrMap.at(BA->getBasicBlock());
+
+ if (auto *F = dyn_cast<Function>(C))
+ return FuncAddrMap.at(F);
+
llvm_unreachable("Unrecognized constant");
}
@@ -85,14 +122,17 @@ IntrusiveRefCntPtr<MemoryObject> Context::allocate(uint64_t Size,
uint64_t Align,
StringRef Name, unsigned AS,
MemInitKind InitKind) {
- if (MaxMem != 0 && SaturatingAdd(UsedMem, Size) >= MaxMem)
+ // Even if the memory object is zero-sized, it still occupies a byte to obtain
+ // a unique address.
+ uint64_t AllocateSize = std::max(Size, (uint64_t)1);
+ if (MaxMem != 0 && SaturatingAdd(UsedMem, AllocateSize) >= MaxMem)
return nullptr;
uint64_t AlignedAddr = alignTo(AllocationBase, Align);
auto MemObj =
makeIntrusiveRefCnt<MemoryObject>(AlignedAddr, Size, Name, AS, InitKind);
MemoryObjects[AlignedAddr] = MemObj;
- AllocationBase = AlignedAddr + Size;
- UsedMem += Size;
+ AllocationBase = AlignedAddr + AllocateSize;
+ UsedMem += AllocateSize;
return MemObj;
}
@@ -100,7 +140,7 @@ bool Context::free(uint64_t Address) {
auto It = MemoryObjects.find(Address);
if (It == MemoryObjects.end())
return false;
- UsedMem -= It->second->getSize();
+ UsedMem -= std::max(It->second->getSize(), (uint64_t)1);
It->second->markAsFreed();
MemoryObjects.erase(It);
return true;
@@ -114,6 +154,25 @@ Pointer Context::deriveFromMemoryObject(IntrusiveRefCntPtr<MemoryObject> Obj) {
/*Offset=*/0);
}
+Function *Context::getTargetFunction(const Pointer &Ptr) {
+ if (Ptr.address().getActiveBits() > 64)
+ return nullptr;
+ auto It = ValidFuncTargets.find(Ptr.address().getZExtValue());
+ if (It == ValidFuncTargets.end())
+ return nullptr;
+ // TODO: check the provenance of pointer.
+ return It->second.first;
+}
+BasicBlock *Context::getTargetBlock(const Pointer &Ptr) {
+ if (Ptr.address().getActiveBits() > 64)
+ return nullptr;
+ auto It = ValidBlockTargets.find(Ptr.address().getZExtValue());
+ if (It == ValidBlockTargets.end())
+ return nullptr;
+ // TODO: check the provenance of pointer.
+ return It->second.first;
+}
+
void MemoryObject::markAsFreed() {
State = MemoryObjectState::Freed;
Bytes.clear();
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index a0153752b4404..29621943c800e 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -90,6 +90,7 @@ class EventHandler {
virtual bool onInstructionExecuted(Instruction &I, const AnyValue &Result) {
return true;
}
+ virtual void onError(StringRef Msg) {}
virtual void onUnrecognizedInstruction(Instruction &I) {}
virtual void onImmediateUB(StringRef Msg) {}
virtual bool onBBJump(Instruction &I, BasicBlock &To) { return true; }
@@ -133,6 +134,12 @@ class Context {
// Constants
// Use std::map to avoid iterator/reference invalidation.
std::map<Constant *, AnyValue> ConstCache;
+ DenseMap<Function *, Pointer> FuncAddrMap;
+ DenseMap<BasicBlock *, Pointer> BlockAddrMap;
+ DenseMap<uint64_t, std::pair<Function *, IntrusiveRefCntPtr<MemoryObject>>>
+ ValidFuncTargets;
+ DenseMap<uint64_t, std::pair<BasicBlock *, IntrusiveRefCntPtr<MemoryObject>>>
+ ValidBlockTargets;
AnyValue getConstantValueImpl(Constant *C);
// TODO: errno and fpenv
@@ -168,10 +175,18 @@ class Context {
StringRef Name, unsigned AS,
MemInitKind InitKind);
bool free(uint64_t Address);
- // Derive a pointer from a memory object with offset 0.
- // Please use Pointer's interface for further manipulations.
+ /// Derive a pointer from a memory object with offset 0.
+ /// Please use Pointer's interface for further manipulations.
Pointer deriveFromMemoryObject(IntrusiveRefCntPtr<MemoryObject> Obj);
+ Function *getTargetFunction(const Pointer &Ptr);
+ BasicBlock *getTargetBlock(const Pointer &Ptr);
+
+ /// Initialize global variables and function/block objects. This function
+ /// should be called before executing any function. Returns false if the
+ /// initialization fails (e.g., the memory limit is exceeded during
+ /// initialization).
+ bool initGlobalValues();
/// Execute the function \p F with arguments \p Args, and store the return
/// value in \p RetVal if the function is not void.
/// Returns true if the function executed successfully. False indicates an
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index d4acb0ffcf489..9cc4adc0b4310 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -12,6 +12,7 @@
#include "Context.h"
#include "Value.h"
+#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/InstVisitor.h"
#include "llvm/IR/Operator.h"
#include "llvm/Support/Allocator.h"
@@ -58,6 +59,7 @@ struct Frame {
DenseMap<Value *, AnyValue> ValueMap;
// Reserved for in-flight subroutines.
+ Function *ResolvedCallee = nullptr;
SmallVector<AnyValue> CalleeArgs;
AnyValue CalleeRetVal;
@@ -133,6 +135,14 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
Handler.onImmediateUB(Msg);
}
+ void reportError(StringRef Msg) {
+ // Check if we have already reported an error message.
+ if (!Status)
+ return;
+ Status = false;
+ Handler.onError(Msg);
+ }
+
const AnyValue &getValue(Value *V) {
if (auto *C = dyn_cast<Constant>(V))
return Ctx.getConstantValue(C);
@@ -204,6 +214,38 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
});
}
+ void jumpTo(Instruction &Terminator, BasicBlock *DestBB) {
+ if (!Handler.onBBJump(Terminator, *DestBB)) {
+ Status = false;
+ return;
+ }
+ BasicBlock *From = CurrentFrame->BB;
+ CurrentFrame->BB = DestBB;
+ CurrentFrame->PC = DestBB->begin();
+ // Update PHI nodes in batch to avoid the interference between PHI nodes.
+ // We need to store the incoming values into a temporary buffer.
+ // Otherwise, the incoming value may be overwritten before it is
+ // used by other PHI nodes.
+ SmallVector<std::pair<PHINode *, AnyValue>> IncomingValues;
+ PHINode *PHI = nullptr;
+ while ((PHI = dyn_cast<PHINode>(CurrentFrame->PC))) {
+ Value *Incoming = PHI->getIncomingValueForBlock(From);
+ // TODO: handle fast-math flags.
+ IncomingValues.emplace_back(PHI, getValue(Incoming));
+ ++CurrentFrame->PC;
+ }
+ for (auto &[K, V] : IncomingValues)
+ setResult(*K, std::move(V));
+ }
+
+ /// Helper function to determine whether an inline asm is a no-op, which is
+ /// used to implement black_box style optimization blockers.
+ bool isNoopInlineAsm(Value *V, Type *RetTy) {
+ if (auto *Asm = dyn_cast<InlineAsm>(V))
+ return Asm->getAsmString().empty() && RetTy->isVoidTy();
+ return false;
+ }
+
public:
InstExecutor(Context &C, EventHandler &H, Function &F,
ArrayRef<AnyValue> Args, AnyValue &RetVal)
@@ -219,6 +261,199 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
Status &= Handler.onInstructionExecuted(RI, None);
}
+ void visitBranchInst(BranchInst &BI) {
+ if (BI.isConditional()) {
+ switch (getValue(BI.getCondition()).asBoolean()) {
+ case BooleanKind::True:
+ jumpTo(BI, BI.getSuccessor(0));
+ return;
+ case BooleanKind::False:
+ jumpTo(BI, BI.getSuccessor(1));
+ return;
+ case BooleanKind::Poison:
+ reportImmediateUB("Branch on poison condition.");
+ return;
+ }
+ }
+ jumpTo(BI, BI.getSuccessor(0));
+ }
+
+ void visitSwitchInst(SwitchInst &SI) {
+ auto &Cond = getValue(SI.getCondition());
+ if (Cond.isPoison()) {
+ reportImmediateUB("Switch on poison condition.");
+ return;
+ }
+ for (auto &Case : SI.cases()) {
+ if (Case.getCaseValue()->getValue() == Cond.asInteger()) {
+ jumpTo(SI, Case.getCaseSuccessor());
+ return;
+ }
+ }
+ jumpTo(SI, SI.getDefaultDest());
+ }
+
+ void visitUnreachableInst(UnreachableInst &) {
+ reportImmediateUB("Unreachable code.");
+ }
+
+ void visitCallBrInst(CallBrInst &CI) {
+ if (isNoopInlineAsm(CI.getCalledOperand(), CI.getType())) {
+ jumpTo(CI, CI.getDefaultDest());
+ return;
+ }
+
+ Handler.onUnrecognizedInstruction(CI);
+ Status = false;
+ }
+
+ void visitIndirectBrInst(IndirectBrInst &IBI) {
+ auto &Target = getValue(IBI.getAddress());
+ if (Target.isPoison()) {
+ reportImmediateUB("Indirect branch on poison.");
+ return;
+ }
+ if (BasicBlock *DestBB = Ctx.getTargetBlock(Target.asPointer())) {
+ if (any_of(IBI.successors(),
+ [DestBB](BasicBlock *Succ) { return Succ == DestBB; }))
+ jumpTo(IBI, DestBB);
+ else
+ reportImmediateUB("Indirect branch on unlisted target BB.");
+
+ return;
+ }
+ reportImmediateUB("Indirect branch on invalid target BB.");
+ }
+
+ 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;
+ setResult(CB, std::move(RetVal));
+
+ if (auto *II = dyn_cast<InvokeInst>(&CB))
+ jumpTo(*II, II->getNormalDest());
+ else if (CurrentFrame->State == FrameState::Pending)
+ ++CurrentFrame->PC;
+ }
+
+ AnyValue callIntrinsic(CallBase &CB) {
+ Intrinsic::ID IID = CB.getIntrinsicID();
+ switch (IID) {
+ case Intrinsic::assume:
+ switch (getValue(CB.getArgOperand(0)).asBoolean()) {
+ case BooleanKind::True:
+ break;
+ case BooleanKind::False:
+ case BooleanKind::Poison:
+ reportImmediateUB("Assume on false or poison condition.");
+ break;
+ }
+ // TODO: handle llvm.assume with operand bundles
+ return AnyValue();
+ default:
+ Handler.onUnrecognizedInstruction(CB);
+ Status = false;
+ return AnyValue();
+ }
+ }
+
+ AnyValue callLibFunc(CallBase &CB, Function *ResolvedCallee) {
+ LibFunc LF;
+ // Respect nobuiltin attributes on call site.
+ if (CB.isNoBuiltin() ||
+ !CurrentFrame->TLI.getLibFunc(*ResolvedCallee, LF)) {
+ Handler.onUnrecognizedInstruction(CB);
+ Status = false;
+ return AnyValue();
+ }
+
+ Handler.onUnrecognizedInstruction(CB);
+ Status = false;
+ return AnyValue();
+ }
+
+ 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() &&
+ "Forgot to call returnFromCallee before entering a new call.");
+ for (Value *Arg : CB.args())
+ CalleeArgs.push_back(getValue(Arg));
+
+ if (!Callee) {
+ Value *CalledOperand = CB.getCalledOperand();
+ if (isNoopInlineAsm(CalledOperand, CB.getType())) {
+ CurrentFrame->ResolvedCallee = nullptr;
+ returnFromCallee();
+ return;
+ }
+
+ if (isa<InlineAsm>(CalledOperand)) {
+ Handler.onUnrecognizedInstruction(CB);
+ Status = false;
+ return;
+ }
+
+ auto &CalleeVal = getValue(CalledOperand);
+ if (CalleeVal.isPoison()) {
+ reportImmediateUB("Indirect call through poison function pointer.");
+ return;
+ }
+ Callee = Ctx.getTargetFunction(CalleeVal.asPointer());
+ if (!Callee) {
+ reportImmediateUB("Indirect call through invalid function pointer.");
+ return;
+ }
+ if (Callee->getFunctionType() != CB.getFunctionType()) {
+ reportImmediateUB("Indirect call through a function pointer with "
+ "mismatched signature.");
+ return;
+ }
+ }
+
+ assert(Callee && "Expected a resolved callee function.");
+ assert(
+ Callee->getFunctionType() == CB.getFunctionType() &&
+ "Expected the callee function type to match the call site signature.");
+ CurrentFrame->ResolvedCallee = Callee;
+ if (Callee->isIntrinsic()) {
+ CurrentFrame->CalleeRetVal = callIntrinsic(CB);
+ returnFromCallee();
+ return;
+ } else if (Callee->isDeclaration()) {
+ CurrentFrame->CalleeRetVal = callLibFunc(CB, Callee);
+ returnFromCallee();
+ return;
+ } else {
+ uint32_t MaxStackDepth = Ctx.getMaxStackDepth();
+ if (MaxStackDepth && CallStack.size() >= MaxStackDepth) {
+ reportError("Maximum stack depth exceeded.");
+ return;
+ }
+ assert(!Callee->empty() && "Expected a defined function.");
+ // Suspend the current frame and push the callee frame onto the stack.
+ ArrayRef<AnyValue> Args = CurrentFrame->CalleeArgs;
+ AnyValue &RetVal = CurrentFrame->CalleeRetVal;
+ CurrentFrame->State = FrameState::Pending;
+ CallStack.emplace_back(*Callee, &CB, CurrentFrame, Args, RetVal,
+ Ctx.getTLIImpl());
+ }
+ }
+
+ void visitCallInst(CallInst &CI) { enterCall(CI); }
+
+ void visitInvokeInst(InvokeInst &II) {
+ // TODO: handle exceptions
+ enterCall(II);
+ }
+
void visitAdd(BinaryOperator &I) {
visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
return addNoWrap(LHS, RHS, I.hasNoSignedWrap(), I.hasNoUnsignedWrap());
@@ -427,6 +662,20 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
});
}
+ void visitICmpInst(ICmpInst &I) {
+ visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+ if (LHS.isPoison() || RHS.isPoison())
+ return AnyValue::poison();
+ // TODO: handle pointer comparison.
+ const APInt &LHSVal = LHS.asInteger();
+ const APInt &RHSVal = RHS.asInteger();
+ if (I.hasSameSign() && LHSVal.isNonNegative() != RHSVal.isNonNegative())
+ return AnyValue::poison();
+ return AnyValue::boolean(
+ ICmpInst::compare(LHSVal, RHSVal, I.getPredicate()));
+ });
+ }
+
void visitSelect(SelectInst &SI) {
// TODO: handle fast-math flags.
if (SI.getCondition()->getType()->isIntegerTy(1)) {
@@ -481,10 +730,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
CurrentFrame = &Top;
if (Top.State == FrameState::Entry) {
Handler.onFunctionEntry(Top.Func, Top.Args, Top.CallSite);
- // TODO: Handle arg attributes
} else {
assert(Top.State == FrameState::Pending &&
"Expected to return from a callee.");
+ returnFromCallee();
}
Top.State = FrameState::Running;
@@ -493,7 +742,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
assert(Top.State == FrameState::Running &&
"Expected to be in running state.");
if (MaxSteps != 0 && Steps >= MaxSteps) {
- reportImmediateUB("Exceeded maximum number of execution steps.");
+ reportError("Exceeded maximum number of execution steps.");
break;
}
++Steps;
@@ -520,7 +769,6 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
if (Top.State == FrameState::Exit) {
assert((Top.Func.getReturnType()->isVoidTy() || !Top.RetVal.isNone()) &&
"Expected return value to be set on function exit.");
- // TODO:Handle retval attributes
Handler.onFunctionExit(Top.Func, Top.RetVal);
CallStack.pop_back();
} else {
diff --git a/llvm/tools/llubi/lib/Value.h b/llvm/tools/llubi/lib/Value.h
index 278a569b1edda..22bb074c7b3c4 100644
--- a/llvm/tools/llubi/lib/Value.h
+++ b/llvm/tools/llubi/lib/Value.h
@@ -109,6 +109,7 @@ class [[nodiscard]] AnyValue {
void print(raw_ostream &OS) const;
static AnyValue poison() { return AnyValue(PoisonTag{}); }
+ static AnyValue boolean(bool Val) { return AnyValue(APInt(1, Val)); }
static AnyValue getPoisonValue(Context &Ctx, Type *Ty);
static AnyValue getNullValue(Context &Ctx, Type *Ty);
diff --git a/llvm/tools/llubi/llubi.cpp b/llvm/tools/llubi/llubi.cpp
index 67ab01eca89fe..1d2d4dc050b5d 100644
--- a/llvm/tools/llubi/llubi.cpp
+++ b/llvm/tools/llubi/llubi.cpp
@@ -91,9 +91,12 @@ class VerboseEventHandler : public ubi::EventHandler {
errs() << "Immediate UB detected: " << Msg << '\n';
}
+ void onError(StringRef Msg) override { errs() << "Error: " << Msg << '\n'; }
+
bool onBBJump(Instruction &I, BasicBlock &To) override {
errs() << I << " jump to ";
To.printAsOperand(errs(), /*PrintType=*/false);
+ errs() << '\n';
return true;
}
@@ -162,6 +165,12 @@ int main(int argc, char **argv) {
Ctx.setMaxSteps(MaxSteps);
Ctx.setMaxStackDepth(MaxStackDepth);
+ if (!Ctx.initGlobalValues()) {
+ WithColor::error() << "Failed to initialize global values (e.g., the "
+ "memory limit may be too low).\n";
+ return 1;
+ }
+
// Call the main function from M as if its signature were:
// int main (int argc, char **argv)
// using the contents of Args to determine argc & argv
More information about the llvm-commits
mailing list