[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