[llvm] [llubi] Add basic support for terminators (PR #181393)

Yingwei Zheng via llvm-commits llvm-commits at lists.llvm.org
Fri Feb 13 10:08:31 PST 2026


https://github.com/dtcxzyw created https://github.com/llvm/llvm-project/pull/181393

Note: icmp with integers is also introduced in this patch, as it is needed to compute branch conditions.


>From 6fab0df26f4ea6abb08f9966c0073fec9b593511 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Sat, 14 Feb 2026 02:05:44 +0800
Subject: [PATCH] [llubi] Add basic support for terminators

---
 llvm/test/tools/llubi/br_poison.ll          |  13 +
 llvm/test/tools/llubi/call_poison.ll        |  11 +
 llvm/test/tools/llubi/controlflow.ll        | 370 ++++++++++++
 llvm/test/tools/llubi/indirectbr_invalid.ll |  16 +
 llvm/test/tools/llubi/indirectbr_poison.ll  |  13 +
 llvm/test/tools/llubi/infinite_loop.ll      |  23 +
 llvm/test/tools/llubi/int_arith.ll          |   9 +
 llvm/test/tools/llubi/invoke_poison.ll      |  17 +
 llvm/test/tools/llubi/main.ll               |   2 +-
 llvm/test/tools/llubi/poison.ll             |   2 +-
 llvm/test/tools/llubi/stack_overflow.ll     | 107 ++++
 llvm/test/tools/llubi/switch_poison.ll      |  13 +
 llvm/test/tools/llubi/unreachable.ll        |   9 +
 llvm/tools/llubi/lib/Context.cpp            |  58 +-
 llvm/tools/llubi/lib/Context.h              |  13 +-
 llvm/tools/llubi/lib/Interpreter.cpp        | 637 +++++++++++++-------
 llvm/tools/llubi/lib/Value.h                |   1 +
 llvm/tools/llubi/llubi.cpp                  |   1 +
 18 files changed, 1097 insertions(+), 218 deletions(-)
 create mode 100644 llvm/test/tools/llubi/br_poison.ll
 create mode 100644 llvm/test/tools/llubi/call_poison.ll
 create mode 100644 llvm/test/tools/llubi/controlflow.ll
 create mode 100644 llvm/test/tools/llubi/indirectbr_invalid.ll
 create mode 100644 llvm/test/tools/llubi/indirectbr_poison.ll
 create mode 100644 llvm/test/tools/llubi/infinite_loop.ll
 create mode 100644 llvm/test/tools/llubi/invoke_poison.ll
 create mode 100644 llvm/test/tools/llubi/stack_overflow.ll
 create mode 100644 llvm/test/tools/llubi/switch_poison.ll
 create mode 100644 llvm/test/tools/llubi/unreachable.ll

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_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..834d84f31ebbf
--- /dev/null
+++ b/llvm/test/tools/llubi/controlflow.ll
@@ -0,0 +1,370 @@
+; 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 @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 = call i32 @fib(ptr @fib, i32 5)
+  %test = icmp ne i32 %fib5, 8
+  %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 0xF [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 0xF [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 0xF [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 0xF [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 0xF [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 0xF [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 0xF [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 0xF [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 0xF [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 0xF [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 0xF [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 0xF [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 0xF [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 0xF [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 0xF [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 = call i32 @fib(ptr @fib, i32 5) => i32 8
+; CHECK-NEXT:   %test = icmp ne i32 %fib5, 8 => 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..d0e9aa1945acd
--- /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: Immediate UB detected: 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/main.ll b/llvm/test/tools/llubi/main.ll
index d1e91312314ea..3cb0918efb7ad 100644
--- a/llvm/test/tools/llubi/main.ll
+++ b/llvm/test/tools/llubi/main.ll
@@ -7,6 +7,6 @@ define i32 @main(i32 %argc, ptr %argv) {
 
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   i32 %argc = i32 1
-; CHECK-NEXT:   ptr %argv = ptr 0x8 [argv]
+; CHECK-NEXT:   ptr %argv = ptr 0x10 [argv]
 ; CHECK-NEXT:   ret i32 0
 ; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/poison.ll b/llvm/test/tools/llubi/poison.ll
index 9d98b7f75e868..1da2fe2b2cae7 100644
--- a/llvm/test/tools/llubi/poison.ll
+++ b/llvm/test/tools/llubi/poison.ll
@@ -6,7 +6,7 @@ define i32 @main(i32 %argc, ptr %argv) {
 }
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   i32 %argc = i32 1
-; CHECK-NEXT:   ptr %argv = ptr 0x8 [argv]
+; CHECK-NEXT:   ptr %argv = ptr 0x10 [argv]
 ; CHECK-NEXT:   ret i32 poison
 ; CHECK-NEXT: Exiting function: main
 ; CHECK-NEXT: error: Execution of function 'main' resulted in poison return value.
diff --git a/llvm/test/tools/llubi/stack_overflow.ll b/llvm/test/tools/llubi/stack_overflow.ll
new file mode 100644
index 0000000000000..d9d9a15d1fcf7
--- /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: Immediate UB detected: 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..d95e658323d9f 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -17,7 +17,29 @@ namespace llvm::ubi {
 
 Context::Context(Module &M)
     : Ctx(M.getContext()), M(M), DL(M.getDataLayout()),
-      TLIImpl(M.getTargetTriple()) {}
+      TLIImpl(M.getTargetTriple()) {
+  // Register all valid function and block targets.
+  for (Function &F : M) {
+    auto FuncObj = allocate(0, F.getPointerAlignment(DL).value(), F.getName(),
+                            DL.getProgramAddressSpace(), MemInitKind::Zeroed);
+    assert(FuncObj && "Failed to allocate memory for function object.");
+    ValidFuncTargets.try_emplace(FuncObj->getAddress(),
+                                 std::make_pair(&F, FuncObj));
+    FuncAddrMap.try_emplace(&F, deriveFromMemoryObject(FuncObj));
+
+    for (BasicBlock &BB : F) {
+      // The entry block is not an valid block target.
+      if (&BB == &F.getEntryBlock())
+        continue;
+      auto BlockObj = allocate(0, 1, BB.getName(), DL.getProgramAddressSpace(),
+                               MemInitKind::Zeroed);
+      assert(BlockObj && "Failed to allocate memory for block object.");
+      ValidBlockTargets.try_emplace(BlockObj->getAddress(),
+                                    std::make_pair(&BB, BlockObj));
+      BlockAddrMap.try_emplace(&BB, deriveFromMemoryObject(BlockObj));
+    }
+  }
+}
 
 Context::~Context() = default;
 
@@ -51,6 +73,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 +113,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 +131,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 +145,23 @@ 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;
+  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;
+  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..90561a8e70693 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -133,6 +133,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 +174,13 @@ 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);
+
   /// 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 ba4119d7b970a..3f3bfe86ebcd9 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;
 
@@ -116,7 +118,7 @@ static AnyValue mulNoWrap(const APInt &LHS, const APInt &RHS, bool HasNSW,
 /// visit* methods return true on success, false on error.
 /// Unlike the Context class that manages the global state,
 /// InstExecutor only maintains the state for call frames.
-class InstExecutor : public InstVisitor<InstExecutor, bool> {
+class InstExecutor : public InstVisitor<InstExecutor, void> {
   Context &Ctx;
   EventHandler &Handler;
   std::list<Frame> CallStack;
@@ -140,11 +142,10 @@ class InstExecutor : public InstVisitor<InstExecutor, bool> {
     return CurrentFrame->ValueMap.at(V);
   }
 
-  bool setResult(Instruction &I, AnyValue V) {
+  void setResult(Instruction &I, AnyValue V) {
     if (Status)
-      Handler.onInstructionExecuted(I, V);
+      Status &= Handler.onInstructionExecuted(I, V);
     CurrentFrame->ValueMap.insert_or_assign(&I, std::move(V));
-    return true;
   }
 
   AnyValue computeUnOp(Type *Ty, const AnyValue &Operand,
@@ -160,15 +161,14 @@ class InstExecutor : public InstVisitor<InstExecutor, bool> {
     return ScalarFn(Operand);
   }
 
-  bool visitUnOp(Instruction &I,
+  void visitUnOp(Instruction &I,
                  function_ref<AnyValue(const AnyValue &)> ScalarFn) {
-    return setResult(
-        I, computeUnOp(I.getType(), getValue(I.getOperand(0)), ScalarFn));
+    setResult(I, computeUnOp(I.getType(), getValue(I.getOperand(0)), ScalarFn));
   }
 
-  bool visitIntUnOp(Instruction &I,
+  void visitIntUnOp(Instruction &I,
                     function_ref<AnyValue(const APInt &)> ScalarFn) {
-    return visitUnOp(I, [&](const AnyValue &Operand) -> AnyValue {
+    visitUnOp(I, [&](const AnyValue &Operand) -> AnyValue {
       if (Operand.isPoison())
         return AnyValue::poison();
       return ScalarFn(Operand.asInteger());
@@ -190,22 +190,54 @@ class InstExecutor : public InstVisitor<InstExecutor, bool> {
     return ScalarFn(LHS, RHS);
   }
 
-  bool visitBinOp(
+  void visitBinOp(
       Instruction &I,
       function_ref<AnyValue(const AnyValue &, const AnyValue &)> ScalarFn) {
-    return setResult(I, computeBinOp(I.getType(), getValue(I.getOperand(0)),
-                                     getValue(I.getOperand(1)), ScalarFn));
+    setResult(I, computeBinOp(I.getType(), getValue(I.getOperand(0)),
+                              getValue(I.getOperand(1)), ScalarFn));
   }
 
-  bool
+  void
   visitIntBinOp(Instruction &I,
                 function_ref<AnyValue(const APInt &, const APInt &)> ScalarFn) {
-    return visitBinOp(
-        I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
-          if (LHS.isPoison() || RHS.isPoison())
-            return AnyValue::poison();
-          return ScalarFn(LHS.asInteger(), RHS.asInteger());
-        });
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+      if (LHS.isPoison() || RHS.isPoison())
+        return AnyValue::poison();
+      return ScalarFn(LHS.asInteger(), RHS.asInteger());
+    });
+  }
+
+  void jumpTo(Instruction &Terminator, BasicBlock *DestBB) {
+    if (!Handler.onBBJump(Terminator, *DestBB))
+      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);
+      // Bail out on the self-referential value.
+      if (Incoming != PHI) {
+        // 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:
@@ -216,148 +248,325 @@ class InstExecutor : public InstVisitor<InstExecutor, bool> {
                            RetVal, Ctx.getTLIImpl());
   }
 
-  bool visitReturnInst(ReturnInst &RI) {
+  void visitReturnInst(ReturnInst &RI) {
     if (auto *RV = RI.getReturnValue())
       CurrentFrame->RetVal = getValue(RV);
     CurrentFrame->State = FrameState::Exit;
-    return Handler.onInstructionExecuted(RI, None);
+    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();
+    }
   }
 
-  bool visitAdd(BinaryOperator &I) {
-    return visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
+  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;
+    CalleeArgs.clear();
+    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;
+      } else 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;
+      }
+    }
+
+    assert(Callee && "Expected a resolved callee function.");
+    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) {
+        reportImmediateUB("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());
     });
   }
 
-  bool visitSub(BinaryOperator &I) {
-    return visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
+  void visitSub(BinaryOperator &I) {
+    visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
       return subNoWrap(LHS, RHS, I.hasNoSignedWrap(), I.hasNoUnsignedWrap());
     });
   }
 
-  bool visitMul(BinaryOperator &I) {
-    return visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
+  void visitMul(BinaryOperator &I) {
+    visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) {
       return mulNoWrap(LHS, RHS, I.hasNoSignedWrap(), I.hasNoUnsignedWrap());
     });
   }
 
-  bool visitSDiv(BinaryOperator &I) {
-    return visitBinOp(
-        I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
-          // Priority: Immediate UB > poison > normal value
-          if (RHS.isPoison()) {
-            reportImmediateUB("Division by zero (refine RHS to 0).");
-            return AnyValue::poison();
-          }
-          const APInt &RHSVal = RHS.asInteger();
-          if (RHSVal.isZero()) {
-            reportImmediateUB("Division by zero.");
-            return AnyValue::poison();
-          }
-          if (LHS.isPoison()) {
-            if (RHSVal.isAllOnes())
-              reportImmediateUB(
-                  "Signed division overflow (refine LHS to INT_MIN).");
-            return AnyValue::poison();
-          }
-          const APInt &LHSVal = LHS.asInteger();
-          if (LHSVal.isMinSignedValue() && RHSVal.isAllOnes()) {
-            reportImmediateUB("Signed division overflow.");
-            return AnyValue::poison();
-          }
-
-          if (I.isExact()) {
-            APInt Q, R;
-            APInt::sdivrem(LHSVal, RHSVal, Q, R);
-            if (!R.isZero())
-              return AnyValue::poison();
-            return Q;
-          } else {
-            return LHSVal.sdiv(RHSVal);
-          }
-        });
-  }
-
-  bool visitSRem(BinaryOperator &I) {
-    return visitBinOp(
-        I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
-          // Priority: Immediate UB > poison > normal value
-          if (RHS.isPoison()) {
-            reportImmediateUB("Division by zero (refine RHS to 0).");
-            return AnyValue::poison();
-          }
-          const APInt &RHSVal = RHS.asInteger();
-          if (RHSVal.isZero()) {
-            reportImmediateUB("Division by zero.");
-            return AnyValue::poison();
-          }
-          if (LHS.isPoison()) {
-            if (RHSVal.isAllOnes())
-              reportImmediateUB(
-                  "Signed division overflow (refine LHS to INT_MIN).");
-            return AnyValue::poison();
-          }
-          const APInt &LHSVal = LHS.asInteger();
-          if (LHSVal.isMinSignedValue() && RHSVal.isAllOnes()) {
-            reportImmediateUB("Signed division overflow.");
-            return AnyValue::poison();
-          }
-
-          return LHSVal.srem(RHSVal);
-        });
-  }
-
-  bool visitUDiv(BinaryOperator &I) {
-    return visitBinOp(
-        I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
-          // Priority: Immediate UB > poison > normal value
-          if (RHS.isPoison()) {
-            reportImmediateUB("Division by zero (refine RHS to 0).");
-            return AnyValue::poison();
-          }
-          const APInt &RHSVal = RHS.asInteger();
-          if (RHSVal.isZero()) {
-            reportImmediateUB("Division by zero.");
-            return AnyValue::poison();
-          }
-          if (LHS.isPoison())
-            return AnyValue::poison();
-          const APInt &LHSVal = LHS.asInteger();
-
-          if (I.isExact()) {
-            APInt Q, R;
-            APInt::udivrem(LHSVal, RHSVal, Q, R);
-            if (!R.isZero())
-              return AnyValue::poison();
-            return Q;
-          } else {
-            return LHSVal.udiv(RHSVal);
-          }
-        });
-  }
-
-  bool visitURem(BinaryOperator &I) {
-    return visitBinOp(
-        I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
-          // Priority: Immediate UB > poison > normal value
-          if (RHS.isPoison()) {
-            reportImmediateUB("Division by zero (refine RHS to 0).");
-            return AnyValue::poison();
-          }
-          const APInt &RHSVal = RHS.asInteger();
-          if (RHSVal.isZero()) {
-            reportImmediateUB("Division by zero.");
-            return AnyValue::poison();
-          }
-          if (LHS.isPoison())
-            return AnyValue::poison();
-          const APInt &LHSVal = LHS.asInteger();
-          return LHSVal.urem(RHSVal);
-        });
-  }
-
-  bool visitTruncInst(TruncInst &Trunc) {
-    return visitIntUnOp(Trunc, [&](const APInt &Operand) -> AnyValue {
+  void visitSDiv(BinaryOperator &I) {
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+      // Priority: Immediate UB > poison > normal value
+      if (RHS.isPoison()) {
+        reportImmediateUB("Division by zero (refine RHS to 0).");
+        return AnyValue::poison();
+      }
+      const APInt &RHSVal = RHS.asInteger();
+      if (RHSVal.isZero()) {
+        reportImmediateUB("Division by zero.");
+        return AnyValue::poison();
+      }
+      if (LHS.isPoison()) {
+        if (RHSVal.isAllOnes())
+          reportImmediateUB(
+              "Signed division overflow (refine LHS to INT_MIN).");
+        return AnyValue::poison();
+      }
+      const APInt &LHSVal = LHS.asInteger();
+      if (LHSVal.isMinSignedValue() && RHSVal.isAllOnes()) {
+        reportImmediateUB("Signed division overflow.");
+        return AnyValue::poison();
+      }
+
+      if (I.isExact()) {
+        APInt Q, R;
+        APInt::sdivrem(LHSVal, RHSVal, Q, R);
+        if (!R.isZero())
+          return AnyValue::poison();
+        return Q;
+      } else {
+        return LHSVal.sdiv(RHSVal);
+      }
+    });
+  }
+
+  void visitSRem(BinaryOperator &I) {
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+      // Priority: Immediate UB > poison > normal value
+      if (RHS.isPoison()) {
+        reportImmediateUB("Division by zero (refine RHS to 0).");
+        return AnyValue::poison();
+      }
+      const APInt &RHSVal = RHS.asInteger();
+      if (RHSVal.isZero()) {
+        reportImmediateUB("Division by zero.");
+        return AnyValue::poison();
+      }
+      if (LHS.isPoison()) {
+        if (RHSVal.isAllOnes())
+          reportImmediateUB(
+              "Signed division overflow (refine LHS to INT_MIN).");
+        return AnyValue::poison();
+      }
+      const APInt &LHSVal = LHS.asInteger();
+      if (LHSVal.isMinSignedValue() && RHSVal.isAllOnes()) {
+        reportImmediateUB("Signed division overflow.");
+        return AnyValue::poison();
+      }
+
+      return LHSVal.srem(RHSVal);
+    });
+  }
+
+  void visitUDiv(BinaryOperator &I) {
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+      // Priority: Immediate UB > poison > normal value
+      if (RHS.isPoison()) {
+        reportImmediateUB("Division by zero (refine RHS to 0).");
+        return AnyValue::poison();
+      }
+      const APInt &RHSVal = RHS.asInteger();
+      if (RHSVal.isZero()) {
+        reportImmediateUB("Division by zero.");
+        return AnyValue::poison();
+      }
+      if (LHS.isPoison())
+        return AnyValue::poison();
+      const APInt &LHSVal = LHS.asInteger();
+
+      if (I.isExact()) {
+        APInt Q, R;
+        APInt::udivrem(LHSVal, RHSVal, Q, R);
+        if (!R.isZero())
+          return AnyValue::poison();
+        return Q;
+      } else {
+        return LHSVal.udiv(RHSVal);
+      }
+    });
+  }
+
+  void visitURem(BinaryOperator &I) {
+    visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+      // Priority: Immediate UB > poison > normal value
+      if (RHS.isPoison()) {
+        reportImmediateUB("Division by zero (refine RHS to 0).");
+        return AnyValue::poison();
+      }
+      const APInt &RHSVal = RHS.asInteger();
+      if (RHSVal.isZero()) {
+        reportImmediateUB("Division by zero.");
+        return AnyValue::poison();
+      }
+      if (LHS.isPoison())
+        return AnyValue::poison();
+      const APInt &LHSVal = LHS.asInteger();
+      return LHSVal.urem(RHSVal);
+    });
+  }
+
+  void visitTruncInst(TruncInst &Trunc) {
+    visitIntUnOp(Trunc, [&](const APInt &Operand) -> AnyValue {
       unsigned DestBW = Trunc.getType()->getScalarSizeInBits();
       if (Trunc.hasNoSignedWrap() && Operand.getSignificantBits() > DestBW)
         return AnyValue::poison();
@@ -367,8 +576,8 @@ class InstExecutor : public InstVisitor<InstExecutor, bool> {
     });
   }
 
-  bool visitZExtInst(ZExtInst &ZExt) {
-    return visitIntUnOp(ZExt, [&](const APInt &Operand) -> AnyValue {
+  void visitZExtInst(ZExtInst &ZExt) {
+    visitIntUnOp(ZExt, [&](const APInt &Operand) -> AnyValue {
       uint32_t DestBW = ZExt.getDestTy()->getScalarSizeInBits();
       if (ZExt.hasNonNeg() && Operand.isNegative())
         return AnyValue::poison();
@@ -376,79 +585,93 @@ class InstExecutor : public InstVisitor<InstExecutor, bool> {
     });
   }
 
-  bool visitSExtInst(SExtInst &SExt) {
-    return visitIntUnOp(SExt, [&](const APInt &Operand) -> AnyValue {
+  void visitSExtInst(SExtInst &SExt) {
+    visitIntUnOp(SExt, [&](const APInt &Operand) -> AnyValue {
       uint32_t DestBW = SExt.getDestTy()->getScalarSizeInBits();
       return Operand.sext(DestBW);
     });
   }
 
-  bool visitAnd(BinaryOperator &I) {
-    return visitIntBinOp(I, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
+  void visitAnd(BinaryOperator &I) {
+    visitIntBinOp(I, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
       return LHS & RHS;
     });
   }
 
-  bool visitXor(BinaryOperator &I) {
-    return visitIntBinOp(I, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
+  void visitXor(BinaryOperator &I) {
+    visitIntBinOp(I, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
       return LHS ^ RHS;
     });
   }
 
-  bool visitOr(BinaryOperator &I) {
-    return visitIntBinOp(
-        I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
-          if (cast<PossiblyDisjointInst>(I).isDisjoint() && LHS.intersects(RHS))
-            return AnyValue::poison();
-          return LHS | RHS;
-        });
-  }
-
-  bool visitShl(BinaryOperator &I) {
-    return visitIntBinOp(
-        I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
-          if (RHS.uge(LHS.getBitWidth()))
-            return AnyValue::poison();
-          if (I.hasNoSignedWrap() && RHS.uge(LHS.getNumSignBits()))
-            return AnyValue::poison();
-          if (I.hasNoUnsignedWrap() && RHS.ugt(LHS.countl_zero()))
-            return AnyValue::poison();
-          return LHS.shl(RHS);
-        });
-  }
-
-  bool visitLShr(BinaryOperator &I) {
-    return visitIntBinOp(I,
-                         [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
-                           if (RHS.uge(cast<PossiblyExactOperator>(I).isExact()
-                                           ? LHS.countr_zero() + 1
-                                           : LHS.getBitWidth()))
-                             return AnyValue::poison();
-                           return LHS.lshr(RHS);
-                         });
-  }
-
-  bool visitAShr(BinaryOperator &I) {
-    return visitIntBinOp(I,
-                         [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
-                           if (RHS.uge(cast<PossiblyExactOperator>(I).isExact()
-                                           ? LHS.countr_zero() + 1
-                                           : LHS.getBitWidth()))
-                             return AnyValue::poison();
-                           return LHS.ashr(RHS);
-                         });
-  }
-
-  bool visitSelect(SelectInst &SI) {
+  void visitOr(BinaryOperator &I) {
+    visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
+      if (cast<PossiblyDisjointInst>(I).isDisjoint() && LHS.intersects(RHS))
+        return AnyValue::poison();
+      return LHS | RHS;
+    });
+  }
+
+  void visitShl(BinaryOperator &I) {
+    visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
+      if (RHS.uge(LHS.getBitWidth()))
+        return AnyValue::poison();
+      if (I.hasNoSignedWrap() && RHS.uge(LHS.getNumSignBits()))
+        return AnyValue::poison();
+      if (I.hasNoUnsignedWrap() && RHS.ugt(LHS.countl_zero()))
+        return AnyValue::poison();
+      return LHS.shl(RHS);
+    });
+  }
+
+  void visitLShr(BinaryOperator &I) {
+    visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
+      if (RHS.uge(cast<PossiblyExactOperator>(I).isExact()
+                      ? LHS.countr_zero() + 1
+                      : LHS.getBitWidth()))
+        return AnyValue::poison();
+      return LHS.lshr(RHS);
+    });
+  }
+
+  void visitAShr(BinaryOperator &I) {
+    visitIntBinOp(I, [&](const APInt &LHS, const APInt &RHS) -> AnyValue {
+      if (RHS.uge(cast<PossiblyExactOperator>(I).isExact()
+                      ? LHS.countr_zero() + 1
+                      : LHS.getBitWidth()))
+        return AnyValue::poison();
+      return LHS.ashr(RHS);
+    });
+  }
+
+  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)) {
       switch (getValue(SI.getCondition()).asBoolean()) {
       case BooleanKind::True:
-        return setResult(SI, getValue(SI.getTrueValue()));
+        setResult(SI, getValue(SI.getTrueValue()));
+        return;
+
       case BooleanKind::False:
-        return setResult(SI, getValue(SI.getFalseValue()));
+        setResult(SI, getValue(SI.getFalseValue()));
+        return;
       case BooleanKind::Poison:
-        return setResult(SI, AnyValue::getPoisonValue(Ctx, SI.getType()));
+        setResult(SI, AnyValue::getPoisonValue(Ctx, SI.getType()));
+        return;
       }
     }
 
@@ -471,12 +694,12 @@ class InstExecutor : public InstVisitor<InstExecutor, bool> {
         break;
       }
     }
-    return setResult(SI, std::move(Res));
+    setResult(SI, std::move(Res));
   }
 
-  bool visitInstruction(Instruction &I) {
+  void visitInstruction(Instruction &I) {
     Handler.onUnrecognizedInstruction(I);
-    return false;
+    Status = false;
   }
 
   /// This function implements the main interpreter loop.
@@ -490,10 +713,10 @@ class InstExecutor : public InstVisitor<InstExecutor, bool> {
       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;
@@ -508,10 +731,7 @@ class InstExecutor : public InstVisitor<InstExecutor, bool> {
         ++Steps;
 
         Instruction &I = *Top.PC;
-        if (!visit(&I)) {
-          Status = false;
-          break;
-        }
+        visit(&I);
         if (!Status)
           break;
 
@@ -532,7 +752,6 @@ class InstExecutor : public InstVisitor<InstExecutor, bool> {
       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..3a452dee34784 100644
--- a/llvm/tools/llubi/llubi.cpp
+++ b/llvm/tools/llubi/llubi.cpp
@@ -94,6 +94,7 @@ class VerboseEventHandler : public ubi::EventHandler {
   bool onBBJump(Instruction &I, BasicBlock &To) override {
     errs() << I << " jump to ";
     To.printAsOperand(errs(), /*PrintType=*/false);
+    errs() << '\n';
     return true;
   }
 



More information about the llvm-commits mailing list