[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