[llvm] f149ab6 - [llubi] Implements common library functions (#190147)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Apr 10 01:05:59 PDT 2026
Author: Zhige Chen
Date: 2026-04-10T08:05:53Z
New Revision: f149ab665a4b4cdd74f89e0689dfd7f376b678e1
URL: https://github.com/llvm/llvm-project/commit/f149ab665a4b4cdd74f89e0689dfd7f376b678e1
DIFF: https://github.com/llvm/llvm-project/commit/f149ab665a4b4cdd74f89e0689dfd7f376b678e1.diff
LOG: [llubi] Implements common library functions (#190147)
This PR implements common library functions, including malloc, calloc,
free, puts, printf, exit, abort, and terminate.
See also #186976.
Added:
llvm/test/tools/llubi/lib_abort.ll
llvm/test/tools/llubi/lib_calloc_large_size.ll
llvm/test/tools/llubi/lib_calloc_size_overflow.ll
llvm/test/tools/llubi/lib_cxx_memory.ll
llvm/test/tools/llubi/lib_cxx_memory_large_size.ll
llvm/test/tools/llubi/lib_double_free.ll
llvm/test/tools/llubi/lib_exit.ll
llvm/test/tools/llubi/lib_free_nullary_pointer.ll
llvm/test/tools/llubi/lib_free_out_of_bound.ll
llvm/test/tools/llubi/lib_free_stack.ll
llvm/test/tools/llubi/lib_io.ll
llvm/test/tools/llubi/lib_malloc_large_size.ll
llvm/test/tools/llubi/lib_memory.ll
llvm/test/tools/llubi/lib_nobuiltin.ll
llvm/test/tools/llubi/lib_poison_argument.ll
llvm/test/tools/llubi/lib_printf_format.ll
llvm/test/tools/llubi/lib_printf_not_enough_argument.ll
llvm/test/tools/llubi/lib_printf_unknown_specifier.ll
llvm/test/tools/llubi/lib_read_nullary_string.ll
llvm/test/tools/llubi/lib_terminate.ll
llvm/test/tools/llubi/lib_uninit_string.ll
llvm/tools/llubi/lib/Library.cpp
llvm/tools/llubi/lib/Library.h
Modified:
llvm/tools/llubi/lib/CMakeLists.txt
llvm/tools/llubi/lib/Context.cpp
llvm/tools/llubi/lib/Context.h
llvm/tools/llubi/lib/ExecutorBase.cpp
llvm/tools/llubi/lib/ExecutorBase.h
llvm/tools/llubi/lib/Interpreter.cpp
llvm/tools/llubi/llubi.cpp
Removed:
################################################################################
diff --git a/llvm/test/tools/llubi/lib_abort.ll b/llvm/test/tools/llubi/lib_abort.ll
new file mode 100644
index 0000000000000..78ed03c6c70ca
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_abort.ll
@@ -0,0 +1,30 @@
+; 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
+
+declare void @abort() noreturn
+declare i32 @puts(ptr)
+
+define i32 @main() {
+entry:
+ %before = alloca [7 x i8]
+ store [7 x i8] c"Before\00", ptr %before
+
+ %after = alloca [6 x i8]
+ store [6 x i8] c"After\00", ptr %after
+
+ %0 = call i32 @puts(ptr %before)
+
+ call void @abort()
+
+ %1 = call i32 @puts(ptr %after)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %before = alloca [7 x i8], align 1 => ptr 0x8 [before]
+; CHECK-NEXT: store [7 x i8] c"Before\00", ptr %before, align 1
+; CHECK-NEXT: %after = alloca [6 x i8], align 1 => ptr 0xF [after]
+; CHECK-NEXT: store [6 x i8] c"After\00", ptr %after, align 1
+; CHECK-NEXT: Before
+; CHECK-NEXT: %0 = call i32 @puts(ptr %before) => i32 1
+; CHECK-NEXT: Program aborted.
diff --git a/llvm/test/tools/llubi/lib_calloc_large_size.ll b/llvm/test/tools/llubi/lib_calloc_large_size.ll
new file mode 100644
index 0000000000000..72b0764185a47
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_calloc_large_size.ll
@@ -0,0 +1,18 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose --max-mem=100 < %s 2>&1 | FileCheck %s
+
+declare ptr @calloc(i64, i64)
+
+define void @main() {
+entry:
+ %ptr_1 = call ptr @calloc(i64 2, i64 25)
+ %ptr_2 = call ptr @calloc(i64 4, i64 25)
+ %ptr_3 = call ptr @calloc(i64 8, i64 25)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %ptr_1 = call ptr @calloc(i64 2, i64 25) => ptr 0x10 [ptr_1]
+; CHECK-NEXT: %ptr_2 = call ptr @calloc(i64 4, i64 25) => ptr 0x0 [dangling]
+; CHECK-NEXT: %ptr_3 = call ptr @calloc(i64 8, i64 25) => ptr 0x0 [dangling]
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/lib_calloc_size_overflow.ll b/llvm/test/tools/llubi/lib_calloc_size_overflow.ll
new file mode 100644
index 0000000000000..ea06f2bde1018
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_calloc_size_overflow.ll
@@ -0,0 +1,14 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+declare ptr @calloc(i64, i64)
+
+define void @main() {
+entry:
+ %ptr = call ptr @calloc(i64 -1, i64 2)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %ptr = call ptr @calloc(i64 -1, i64 2) => ptr 0x0 [dangling]
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/lib_cxx_memory.ll b/llvm/test/tools/llubi/lib_cxx_memory.ll
new file mode 100644
index 0000000000000..fd8e8aca84a3e
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_cxx_memory.ll
@@ -0,0 +1,20 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+declare ptr @_Znwm(i64) ; new(unsigned long)
+declare void @_ZdlPv(ptr) ; delete(void*)
+
+define i32 @main() {
+entry:
+ %ptr = call ptr @_Znwm(i64 8)
+ store i64 42, ptr %ptr
+
+ call void @_ZdlPv(ptr %ptr)
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %ptr = call ptr @_Znwm(i64 8) => ptr 0x10 [ptr]
+; CHECK-NEXT: store i64 42, ptr %ptr, align 4
+; CHECK-NEXT: call void @_ZdlPv(ptr %ptr)
+; CHECK-NEXT: ret i32 0
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/lib_cxx_memory_large_size.ll b/llvm/test/tools/llubi/lib_cxx_memory_large_size.ll
new file mode 100644
index 0000000000000..207bb469c9199
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_cxx_memory_large_size.ll
@@ -0,0 +1,15 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: not llubi --verbose --max-mem=100 < %s 2>&1 | FileCheck %s
+
+declare ptr @_Znwm(i64) ; new(unsigned long)
+
+define i32 @main() {
+ %ptr_1 = call ptr @_Znwm(i64 50)
+ %ptr_2 = call ptr @_Znwm(i64 100)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %ptr_1 = call ptr @_Znwm(i64 50) => ptr 0x10 [ptr_1]
+; CHECK-NEXT: Error: Insufficient heap space.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_double_free.ll b/llvm/test/tools/llubi/lib_double_free.ll
new file mode 100644
index 0000000000000..db0a684e74193
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_double_free.ll
@@ -0,0 +1,21 @@
+; 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
+
+declare ptr @malloc(i64)
+declare void @free(ptr)
+
+define i32 @main() {
+entry:
+ %ptr = call ptr @malloc(i64 4)
+
+ call void @free(ptr %ptr)
+
+ call void @free(ptr %ptr)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %ptr = call ptr @malloc(i64 4) => ptr 0x10 [ptr]
+; CHECK-NEXT: call void @free(ptr %ptr)
+; CHECK-NEXT: Immediate UB detected: double-freeing a memory object.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_exit.ll b/llvm/test/tools/llubi/lib_exit.ll
new file mode 100644
index 0000000000000..7239352f18182
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_exit.ll
@@ -0,0 +1,30 @@
+; 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
+
+declare void @exit(i32) noreturn
+declare i32 @puts(ptr)
+
+define i32 @main() {
+entry:
+ %before = alloca [7 x i8]
+ store [7 x i8] c"Before\00", ptr %before
+
+ %after = alloca [6 x i8]
+ store [6 x i8] c"After\00", ptr %after
+
+ %0 = call i32 @puts(ptr %before)
+
+ call void @exit(i32 42)
+
+ %1 = call i32 @puts(ptr %after)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %before = alloca [7 x i8], align 1 => ptr 0x8 [before]
+; CHECK-NEXT: store [7 x i8] c"Before\00", ptr %before, align 1
+; CHECK-NEXT: %after = alloca [6 x i8], align 1 => ptr 0xF [after]
+; CHECK-NEXT: store [6 x i8] c"After\00", ptr %after, align 1
+; CHECK-NEXT: Before
+; CHECK-NEXT: %0 = call i32 @puts(ptr %before) => i32 1
+; CHECK-NEXT: Program exited with code 42
diff --git a/llvm/test/tools/llubi/lib_free_nullary_pointer.ll b/llvm/test/tools/llubi/lib_free_nullary_pointer.ll
new file mode 100644
index 0000000000000..b487a7b335f60
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_free_nullary_pointer.ll
@@ -0,0 +1,15 @@
+; 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
+
+declare void @free(ptr)
+
+define i32 @main() {
+ %p = getelementptr i8, ptr null, i64 42
+ call void @free(ptr %p)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p = getelementptr i8, ptr null, i64 42 => ptr 0x2A [dangling]
+; CHECK-NEXT: Immediate UB detected: freeing a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_free_out_of_bound.ll b/llvm/test/tools/llubi/lib_free_out_of_bound.ll
new file mode 100644
index 0000000000000..827df76ce3562
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_free_out_of_bound.ll
@@ -0,0 +1,18 @@
+; 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
+
+declare ptr @malloc(i64)
+declare void @free(ptr)
+
+define i32 @main() {
+ %p = call ptr @malloc(i64 4)
+ %p_oob = getelementptr i8, ptr %p, i64 8
+ call void @free(ptr %p_oob)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p = call ptr @malloc(i64 4) => ptr 0x10 [p]
+; CHECK-NEXT: %p_oob = getelementptr i8, ptr %p, i64 8 => ptr 0x18 [p + 8]
+; CHECK-NEXT: Immediate UB detected: freeing a pointer that does not point to the start of an allocation.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_free_stack.ll b/llvm/test/tools/llubi/lib_free_stack.ll
new file mode 100644
index 0000000000000..ca4fc72101a1c
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_free_stack.ll
@@ -0,0 +1,15 @@
+; 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
+
+declare void @free(ptr)
+
+define i32 @main() {
+ %p = alloca i32, i32 1
+ call void @free(ptr %p)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %p = alloca i32, align 4 => ptr 0x8 [p]
+; CHECK-NEXT: Immediate UB detected: freeing a non-heap allocation.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_io.ll b/llvm/test/tools/llubi/lib_io.ll
new file mode 100644
index 0000000000000..157d11942a64c
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_io.ll
@@ -0,0 +1,36 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+declare i32 @printf(ptr, ...)
+declare i32 @puts(ptr)
+
+define i32 @main() {
+entry:
+ %puts.str = alloca [13 x i8]
+ store [13 x i8] c"Hello, puts!\00", ptr %puts.str
+
+ %0 = call i32 @puts(ptr %puts.str)
+
+ %fmt.str = alloca [18 x i8]
+ store [18 x i8] c"Int: %d, Str: %s\0A\00", ptr %fmt.str
+
+ %arg.str = alloca [5 x i8]
+ store [5 x i8] c"test\00", ptr %arg.str
+
+ %1 = call i32 (ptr, ...) @printf(ptr %fmt.str, i32 42, ptr %arg.str)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %puts.str = alloca [13 x i8], align 1 => ptr 0x8 [puts.str]
+; CHECK-NEXT: store [13 x i8] c"Hello, puts!\00", ptr %puts.str, align 1
+; CHECK-NEXT: Hello, puts!
+; CHECK-NEXT: %0 = call i32 @puts(ptr %puts.str) => i32 1
+; CHECK-NEXT: %fmt.str = alloca [18 x i8], align 1 => ptr 0x15 [fmt.str]
+; CHECK-NEXT: store [18 x i8] c"Int: %d, Str: %s\0A\00", ptr %fmt.str, align 1
+; CHECK-NEXT: %arg.str = alloca [5 x i8], align 1 => ptr 0x27 [arg.str]
+; CHECK-NEXT: store [5 x i8] c"test\00", ptr %arg.str, align 1
+; CHECK-NEXT: Int: 42, Str: test
+; CHECK-NEXT: %1 = call i32 (ptr, ...) @printf(ptr %fmt.str, i32 42, ptr %arg.str) => i32 19
+; CHECK-NEXT: ret i32 0
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/lib_malloc_large_size.ll b/llvm/test/tools/llubi/lib_malloc_large_size.ll
new file mode 100644
index 0000000000000..8a1800f53af92
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_malloc_large_size.ll
@@ -0,0 +1,18 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose --max-mem=100 < %s 2>&1 | FileCheck %s
+
+declare ptr @malloc(i64)
+
+define void @main() {
+entry:
+ %ptr_1 = call ptr @malloc(i64 50)
+ %ptr_2 = call ptr @malloc(i64 100)
+ %ptr_3 = call ptr @malloc(i64 200)
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %ptr_1 = call ptr @malloc(i64 50) => ptr 0x10 [ptr_1]
+; CHECK-NEXT: %ptr_2 = call ptr @malloc(i64 100) => ptr 0x0 [dangling]
+; CHECK-NEXT: %ptr_3 = call ptr @malloc(i64 200) => ptr 0x0 [dangling]
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/lib_memory.ll b/llvm/test/tools/llubi/lib_memory.ll
new file mode 100644
index 0000000000000..4677841059a1b
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_memory.ll
@@ -0,0 +1,32 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+declare ptr @malloc(i64)
+declare ptr @calloc(i64, i64)
+declare void @free(ptr)
+
+define i32 @main() {
+entry:
+ %ptr1 = call ptr @malloc(i64 4)
+ store i32 100, ptr %ptr1
+
+ %ptr2 = call ptr @calloc(i64 1, i64 4)
+
+ %val1 = load i32, ptr %ptr1
+ %val2 = load i32, ptr %ptr2
+
+ call void @free(ptr %ptr1)
+ call void @free(ptr %ptr2)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %ptr1 = call ptr @malloc(i64 4) => ptr 0x10 [ptr1]
+; CHECK-NEXT: store i32 100, ptr %ptr1, align 4
+; CHECK-NEXT: %ptr2 = call ptr @calloc(i64 1, i64 4) => ptr 0x20 [ptr2]
+; CHECK-NEXT: %val1 = load i32, ptr %ptr1, align 4 => i32 100
+; CHECK-NEXT: %val2 = load i32, ptr %ptr2, align 4 => i32 0
+; CHECK-NEXT: call void @free(ptr %ptr1)
+; CHECK-NEXT: call void @free(ptr %ptr2)
+; CHECK-NEXT: ret i32 0
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/lib_nobuiltin.ll b/llvm/test/tools/llubi/lib_nobuiltin.ll
new file mode 100644
index 0000000000000..ff448a903123e
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_nobuiltin.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
+
+declare void @exit(i32) noreturn nobuiltin
+
+define i32 @main() {
+ call void @exit(i32 42)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Unrecognized instruction: call void @exit(i32 42)
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_poison_argument.ll b/llvm/test/tools/llubi/lib_poison_argument.ll
new file mode 100644
index 0000000000000..4ebea78faf0d2
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_poison_argument.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
+
+declare i32 @puts(ptr)
+
+define i32 @main() {
+ %1 = call i32 @puts(ptr poison)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Poison argument passed to a library call.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_printf_format.ll b/llvm/test/tools/llubi/lib_printf_format.ll
new file mode 100644
index 0000000000000..e4d5dea1b6a27
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_printf_format.ll
@@ -0,0 +1,81 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+declare i32 @printf(ptr, ...)
+
+define i32 @main() {
+entry:
+ %fmt_int = alloca [36 x i8]
+ store [36 x i8] c"Ints: %d, %i, %u, %o, %x, %X, %05d\0A\00", ptr %fmt_int
+
+ %fmt_len = alloca [35 x i8]
+ store [35 x i8] c"Lengths: %ld, %lld, %hd, %hhu, %c\0A\00", ptr %fmt_len
+
+ %fmt_str_ptr = alloca [18 x i8]
+ store [18 x i8] c"Str: %s, Ptr: %p\0A\00", ptr %fmt_str_ptr
+
+ %fmt_pct = alloca [15 x i8]
+ store [15 x i8] c"Percent: %d%%\0A\00", ptr %fmt_pct
+
+ %dummy_str = alloca [6 x i8]
+ store [6 x i8] c"llubi\00", ptr %dummy_str
+
+ %fmt_float = alloca [20 x i8]
+ store [20 x i8] c"Floats: %f, %e, %g\0A\00", ptr %fmt_float
+
+ %fmt_n = alloca [15 x i8]
+ store [15 x i8] c"Count: %nDone\0A\00", ptr %fmt_n
+
+ %fmt_n_out = alloca [6 x i8]
+ store [6 x i8] c"N=%d\0A\00", ptr %fmt_n_out
+
+ %n_count = alloca i32
+ store i32 0, ptr %n_count
+
+ call i32 (ptr, ...) @printf(ptr %fmt_int, i32 42, i32 -42, i32 255, i32 255, i32 255, i32 255, i32 42)
+ call i32 (ptr, ...) @printf(ptr %fmt_len, i64 123456789, i64 987654321, i32 100, i32 50, i32 65)
+ call i32 (ptr, ...) @printf(ptr %fmt_str_ptr, ptr %dummy_str, ptr %dummy_str)
+ call i32 (ptr, ...) @printf(ptr %fmt_pct, i32 100)
+ call i32 (ptr, ...) @printf(ptr %fmt_float, double 3.14159, double 3.14159, double 3.14159)
+ call i32 (ptr, ...) @printf(ptr %fmt_n, ptr %n_count)
+ %n_loaded = load i32, ptr %n_count
+ call i32 (ptr, ...) @printf(ptr %fmt_n_out, i32 %n_loaded)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %fmt_int = alloca [36 x i8], align 1 => ptr 0x8 [fmt_int]
+; CHECK-NEXT: store [36 x i8] c"Ints: %d, %i, %u, %o, %x, %X, %05d\0A\00", ptr %fmt_int, align 1
+; CHECK-NEXT: %fmt_len = alloca [35 x i8], align 1 => ptr 0x2C [fmt_len]
+; CHECK-NEXT: store [35 x i8] c"Lengths: %ld, %lld, %hd, %hhu, %c\0A\00", ptr %fmt_len, align 1
+; CHECK-NEXT: %fmt_str_ptr = alloca [18 x i8], align 1 => ptr 0x4F [fmt_str_ptr]
+; CHECK-NEXT: store [18 x i8] c"Str: %s, Ptr: %p\0A\00", ptr %fmt_str_ptr, align 1
+; CHECK-NEXT: %fmt_pct = alloca [15 x i8], align 1 => ptr 0x61 [fmt_pct]
+; CHECK-NEXT: store [15 x i8] c"Percent: %d%%\0A\00", ptr %fmt_pct, align 1
+; CHECK-NEXT: %dummy_str = alloca [6 x i8], align 1 => ptr 0x70 [dummy_str]
+; CHECK-NEXT: store [6 x i8] c"llubi\00", ptr %dummy_str, align 1
+; CHECK-NEXT: %fmt_float = alloca [20 x i8], align 1 => ptr 0x76 [fmt_float]
+; CHECK-NEXT: store [20 x i8] c"Floats: %f, %e, %g\0A\00", ptr %fmt_float, align 1
+; CHECK-NEXT: %fmt_n = alloca [15 x i8], align 1 => ptr 0x8A [fmt_n]
+; CHECK-NEXT: store [15 x i8] c"Count: %nDone\0A\00", ptr %fmt_n, align 1
+; CHECK-NEXT: %fmt_n_out = alloca [6 x i8], align 1 => ptr 0x99 [fmt_n_out]
+; CHECK-NEXT: store [6 x i8] c"N=%d\0A\00", ptr %fmt_n_out, align 1
+; CHECK-NEXT: %n_count = alloca i32, align 4 => ptr 0xA0 [n_count]
+; CHECK-NEXT: store i32 0, ptr %n_count, align 4
+; CHECK-NEXT: Ints: 42, -42, 255, 377, ff, FF, 00042
+; CHECK-NEXT: %0 = call i32 (ptr, ...) @printf(ptr %fmt_int, i32 42, i32 -42, i32 255, i32 255, i32 255, i32 255, i32 42) => i32 39
+; CHECK-NEXT: Lengths: 123456789, 987654321, 100, 50, A
+; CHECK-NEXT: %1 = call i32 (ptr, ...) @printf(ptr %fmt_len, i64 123456789, i64 987654321, i32 100, i32 50, i32 65) => i32 42
+; CHECK-NEXT: Str: llubi, Ptr: 0x70
+; CHECK-NEXT: %2 = call i32 (ptr, ...) @printf(ptr %fmt_str_ptr, ptr %dummy_str, ptr %dummy_str) => i32 22
+; CHECK-NEXT: Percent: 100%
+; CHECK-NEXT: %3 = call i32 (ptr, ...) @printf(ptr %fmt_pct, i32 100) => i32 14
+; CHECK-NEXT: Floats: 3.141590, 3.141590e+00, 3.14159
+; CHECK-NEXT: %4 = call i32 (ptr, ...) @printf(ptr %fmt_float, double 3.141590e+00, double 3.141590e+00, double 3.141590e+00) => i32 40
+; CHECK-NEXT: Count: Done
+; CHECK-NEXT: %5 = call i32 (ptr, ...) @printf(ptr %fmt_n, ptr %n_count) => i32 12
+; CHECK-NEXT: %n_loaded = load i32, ptr %n_count, align 4 => i32 7
+; CHECK-NEXT: N=7
+; CHECK-NEXT: %6 = call i32 (ptr, ...) @printf(ptr %fmt_n_out, i32 %n_loaded) => i32 4
+; CHECK-NEXT: ret i32 0
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/lib_printf_not_enough_argument.ll b/llvm/test/tools/llubi/lib_printf_not_enough_argument.ll
new file mode 100644
index 0000000000000..87158213b5e70
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_printf_not_enough_argument.ll
@@ -0,0 +1,18 @@
+; 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
+
+declare i32 @printf(ptr, ...)
+
+define i32 @main() {
+ %fmt = alloca [18 x i8]
+ store [18 x i8] c"Ints: %d, %i, %u\0A\00", ptr %fmt
+
+ call i32 (ptr, ...) @printf(ptr %fmt, i32 42, i32 -42)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %fmt = alloca [18 x i8], align 1 => ptr 0x8 [fmt]
+; CHECK-NEXT: store [18 x i8] c"Ints: %d, %i, %u\0A\00", ptr %fmt, align 1
+; CHECK-NEXT: Immediate UB detected: Not enough arguments provided for the format string.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_printf_unknown_specifier.ll b/llvm/test/tools/llubi/lib_printf_unknown_specifier.ll
new file mode 100644
index 0000000000000..d6c4cb7f90b53
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_printf_unknown_specifier.ll
@@ -0,0 +1,18 @@
+; 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
+
+declare i32 @printf(ptr, ...)
+
+define i32 @main() {
+ %fmt = alloca [4 x i8]
+ store [4 x i8] c"%m\0A\00", ptr %fmt
+
+ call i32 (ptr, ...) @printf(ptr %fmt, i32 0)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %fmt = alloca [4 x i8], align 1 => ptr 0x8 [fmt]
+; CHECK-NEXT: store [4 x i8] c"%m\0A\00", ptr %fmt, align 1
+; CHECK-NEXT: Immediate UB detected: Unknown or unsupported format specifier in printf.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_read_nullary_string.ll b/llvm/test/tools/llubi/lib_read_nullary_string.ll
new file mode 100644
index 0000000000000..2c1e354a3850e
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_read_nullary_string.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
+
+declare i32 @puts(ptr)
+
+define i32 @main() {
+ %1 = call i32 @puts(ptr null)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: Invalid memory access via a pointer with nullary provenance.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_terminate.ll b/llvm/test/tools/llubi/lib_terminate.ll
new file mode 100644
index 0000000000000..8026e9aa24497
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_terminate.ll
@@ -0,0 +1,30 @@
+; 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
+
+declare void @_ZSt9terminatev()
+declare i32 @puts(ptr)
+
+define i32 @main() {
+entry:
+ %before = alloca [7 x i8]
+ store [7 x i8] c"Before\00", ptr %before
+
+ %after = alloca [6 x i8]
+ store [6 x i8] c"After\00", ptr %after
+
+ %0 = call i32 @puts(ptr %before)
+
+ call void @_ZSt9terminatev()
+
+ %1 = call i32 @puts(ptr %after)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %before = alloca [7 x i8], align 1 => ptr 0x8 [before]
+; CHECK-NEXT: store [7 x i8] c"Before\00", ptr %before, align 1
+; CHECK-NEXT: %after = alloca [6 x i8], align 1 => ptr 0xF [after]
+; CHECK-NEXT: store [6 x i8] c"After\00", ptr %after, align 1
+; CHECK-NEXT: Before
+; CHECK-NEXT: %0 = call i32 @puts(ptr %before) => i32 1
+; CHECK-NEXT: Program terminated.
diff --git a/llvm/test/tools/llubi/lib_uninit_string.ll b/llvm/test/tools/llubi/lib_uninit_string.ll
new file mode 100644
index 0000000000000..7274cfdb63363
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_uninit_string.ll
@@ -0,0 +1,18 @@
+; 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
+
+declare ptr @malloc(i64)
+declare i32 @puts(ptr)
+
+define i32 @main() {
+entry:
+ %ptr = call ptr @malloc(i64 10)
+
+ %1 = call i32 @puts(ptr %ptr)
+
+ ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %ptr = call ptr @malloc(i64 10) => ptr 0x10 [ptr]
+; CHECK-NEXT: Immediate UB detected: Read uninitialized or poison memory while parsing C-string.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/tools/llubi/lib/CMakeLists.txt b/llvm/tools/llubi/lib/CMakeLists.txt
index b3c7b60cac50e..1e587834f9dbb 100644
--- a/llvm/tools/llubi/lib/CMakeLists.txt
+++ b/llvm/tools/llubi/lib/CMakeLists.txt
@@ -9,5 +9,6 @@ add_llvm_library(LLVMUBAwareInterpreter
Context.cpp
ExecutorBase.cpp
Interpreter.cpp
+ Library.cpp
Value.cpp
)
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 968ac4f561558..2b195ac38ecfc 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -28,7 +28,8 @@ bool Context::initGlobalValues() {
if (F.hasAddressTaken()) {
// TODO: Use precise alignment for function pointers if it is necessary.
auto FuncObj = allocate(0, F.getPointerAlignment(DL).value(), F.getName(),
- DL.getProgramAddressSpace(), MemInitKind::Zeroed);
+ DL.getProgramAddressSpace(), MemInitKind::Zeroed,
+ MemAllocKind::Global);
if (!FuncObj)
return false;
ValidFuncTargets.try_emplace(FuncObj->getAddress(),
@@ -40,7 +41,7 @@ bool Context::initGlobalValues() {
if (!BB.hasAddressTaken())
continue;
auto BlockObj = allocate(0, 1, BB.getName(), DL.getProgramAddressSpace(),
- MemInitKind::Zeroed);
+ MemInitKind::Zeroed, MemAllocKind::Global);
if (!BlockObj)
return false;
ValidBlockTargets.try_emplace(BlockObj->getAddress(),
@@ -421,10 +422,12 @@ void Context::freeze(AnyValue &Val, Type *Ty) {
MemoryObject::~MemoryObject() = default;
MemoryObject::MemoryObject(uint64_t Addr, uint64_t Size, StringRef Name,
- unsigned AS, MemInitKind InitKind)
+ unsigned AS, MemInitKind InitKind,
+ MemAllocKind AllocKind)
: Address(Addr), Size(Size), Name(Name), AS(AS),
State(InitKind != MemInitKind::Poisoned ? MemoryObjectState::Alive
- : MemoryObjectState::Dead) {
+ : MemoryObjectState::Dead),
+ AllocKind(AllocKind) {
switch (InitKind) {
case MemInitKind::Zeroed:
Bytes.resize(Size, Byte::concrete(0));
@@ -438,29 +441,30 @@ MemoryObject::MemoryObject(uint64_t Addr, uint64_t Size, StringRef Name,
}
}
-IntrusiveRefCntPtr<MemoryObject> Context::allocate(uint64_t Size,
- uint64_t Align,
- StringRef Name, unsigned AS,
- MemInitKind InitKind) {
+IntrusiveRefCntPtr<MemoryObject>
+Context::allocate(uint64_t Size, uint64_t Align, StringRef Name, unsigned AS,
+ MemInitKind InitKind, MemAllocKind AllocKind) {
// 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);
+ auto MemObj = makeIntrusiveRefCnt<MemoryObject>(AlignedAddr, Size, Name, AS,
+ InitKind, AllocKind);
MemoryObjects[AlignedAddr] = MemObj;
AllocationBase = AlignedAddr + AllocateSize;
UsedMem += AllocateSize;
return MemObj;
}
-bool Context::free(uint64_t Address) {
+bool Context::free(const MemoryObject &Obj) {
+ uint64_t Address = Obj.getAddress();
auto It = MemoryObjects.find(Address);
- if (It == MemoryObjects.end())
+ if (It == MemoryObjects.end() || It->second.get() != &Obj)
return false;
- UsedMem -= std::max(It->second->getSize(), (uint64_t)1);
+
+ UsedMem -= std::max(It->second->getSize(), static_cast<uint64_t>(1));
It->second->markAsFreed();
MemoryObjects.erase(It);
return true;
@@ -504,4 +508,26 @@ void MemoryObject::markAsFreed() {
Bytes.clear();
}
+bool MemoryObject::isGlobal() const {
+ return AllocKind == MemAllocKind::Global;
+}
+
+bool MemoryObject::isStackAllocated() const {
+ return AllocKind == MemAllocKind::Stack;
+}
+
+bool MemoryObject::isHeapAllocated() const {
+ switch (AllocKind) {
+ case MemAllocKind::Global:
+ case MemAllocKind::Stack:
+ return false;
+ case MemAllocKind::Malloc:
+ case MemAllocKind::New:
+ case MemAllocKind::NewArray:
+ return true;
+ }
+
+ llvm_unreachable("Unknown MemAllocKind");
+}
+
} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index d1960b270d9bd..9e128076ca17e 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -24,6 +24,14 @@ enum class MemInitKind {
Poisoned,
};
+enum class MemAllocKind {
+ Global,
+ Stack,
+ Malloc,
+ New,
+ NewArray,
+};
+
enum class MemoryObjectState {
// This memory object is accessible.
// Valid transitions:
@@ -47,6 +55,34 @@ enum class UndefValueBehavior {
Zero, // All uses of the undef value yield zero.
};
+struct ProgramExitInfo {
+ enum class ProgramExitKind {
+ // Program exited via a normal return
+ Returned,
+ // Program exited with an interpreter error (UB/Unsupported
+ // instruction/etc.)
+ Failed,
+ // Program exited via a call to exit()
+ Exited,
+ // Program exited via a call to abort()
+ Aborted,
+ // Program exited via a call to terminate()
+ Terminated,
+ };
+
+ ProgramExitKind Kind;
+ uint64_t ExitCode;
+
+ explicit ProgramExitInfo(ProgramExitKind Kind, uint64_t ExitCode)
+ : Kind(Kind), ExitCode(ExitCode) {}
+
+ bool isExitedByLibcall() const {
+ return Kind == ProgramExitKind::Exited ||
+ Kind == ProgramExitKind::Aborted ||
+ Kind == ProgramExitKind::Terminated;
+ }
+};
+
class MemoryObject : public RefCountedBase<MemoryObject> {
uint64_t Address;
uint64_t Size;
@@ -55,11 +91,12 @@ class MemoryObject : public RefCountedBase<MemoryObject> {
unsigned AS;
MemoryObjectState State;
+ MemAllocKind AllocKind;
bool IsConstant = false;
public:
MemoryObject(uint64_t Addr, uint64_t Size, StringRef Name, unsigned AS,
- MemInitKind InitKind);
+ MemInitKind InitKind, MemAllocKind AllocKind);
MemoryObject(const MemoryObject &) = delete;
MemoryObject(MemoryObject &&) = delete;
MemoryObject &operator=(const MemoryObject &) = delete;
@@ -72,6 +109,7 @@ class MemoryObject : public RefCountedBase<MemoryObject> {
unsigned getAddressSpace() const { return AS; }
MemoryObjectState getState() const { return State; }
void setState(MemoryObjectState S) { State = S; }
+ MemAllocKind getAllocKind() const { return AllocKind; }
bool isConstant() const { return IsConstant; }
void setIsConstant(bool C) { IsConstant = C; }
@@ -87,6 +125,10 @@ class MemoryObject : public RefCountedBase<MemoryObject> {
MutableArrayRef<Byte> getBytes() { return Bytes; }
void markAsFreed();
+
+ bool isGlobal() const;
+ bool isStackAllocated() const;
+ bool isHeapAllocated() const;
};
/// An interface for handling events and managing outputs during interpretation.
@@ -110,8 +152,10 @@ class EventHandler {
virtual bool onFunctionExit(Function &F, const AnyValue &RetVal) {
return true;
}
+ virtual void onProgramExit(const ProgramExitInfo &ExitInfo) {}
virtual bool onPrint(StringRef Msg) {
outs() << Msg;
+ outs().flush();
return true;
}
};
@@ -227,8 +271,9 @@ class Context {
const AnyValue &getConstantValue(Constant *C);
IntrusiveRefCntPtr<MemoryObject> allocate(uint64_t Size, uint64_t Align,
StringRef Name, unsigned AS,
- MemInitKind InitKind);
- bool free(uint64_t Address);
+ MemInitKind InitKind,
+ MemAllocKind AllocKind);
+ bool free(const MemoryObject &Obj);
/// Derive a pointer from a memory object with offset 0.
/// Please use Pointer's interface for further manipulations.
Pointer deriveFromMemoryObject(IntrusiveRefCntPtr<MemoryObject> Obj);
@@ -258,10 +303,14 @@ class Context {
bool initGlobalValues();
/// Execute the function \p F with arguments \p Args, and store the return
/// value in \p RetVal if the function is not void.
- /// Returns true if the function executed successfully. False indicates an
- /// error occurred during execution.
- bool runFunction(Function &F, ArrayRef<AnyValue> Args, AnyValue &RetVal,
- EventHandler &Handler);
+ /// Returns a `ProgramExitInfo` indicating how the program finished:
+ /// Kind = Returned: The program executed successfully and returned normally.
+ /// Kind = Failed: The interpreter encountered an error and could not execute
+ /// the program.
+ /// Kind = Exited/Aborted/Terminated: The program ended via an
+ /// explicit call to `exit()`, `abort()`, or `terminate()`.
+ ProgramExitInfo runFunction(Function &F, ArrayRef<AnyValue> Args,
+ AnyValue &RetVal, EventHandler &Handler);
};
} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index ec66e831908c5..f1247cd6bf69e 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -29,18 +29,18 @@ Frame::Frame(Function &F, CallBase *CallSite, Frame *LastFrame,
void ExecutorBase::reportImmediateUB(StringRef Msg) {
// Check if we have already reported an immediate UB.
- if (!Status)
+ if (hasProgramExited())
return;
- Status = false;
+ requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
// TODO: Provide stack trace information.
Handler.onImmediateUB(Msg);
}
void ExecutorBase::reportError(StringRef Msg) {
// Check if we have already reported an error message.
- if (!Status)
+ if (hasProgramExited())
return;
- Status = false;
+ requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
Handler.onError(Msg);
}
@@ -124,4 +124,24 @@ void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
/*IsStore=*/true))
Ctx.store(*MO, *Offset, Val, ValTy);
}
+
+void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
+ uint64_t ExitCode) {
+ ExitInfo.emplace(Kind, ExitCode);
+ Handler.onProgramExit(*ExitInfo);
+}
+
+void ExecutorBase::setFailed() {
+ requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
+}
+
+bool ExecutorBase::hasProgramExited() const { return ExitInfo.has_value(); }
+
+std::optional<ProgramExitInfo> ExecutorBase::getExitInfo() const {
+ return ExitInfo;
+}
+
+unsigned ExecutorBase::getIntSize() const {
+ return CurrentFrame->TLI.getIntSize();
+}
} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index 0f80c6a329058..0fa73d9294e07 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -15,6 +15,7 @@
#include "Context.h"
#include "Value.h"
+#include <optional>
namespace llvm::ubi {
@@ -71,12 +72,11 @@ class ExecutorBase {
protected:
Context &Ctx;
EventHandler &Handler;
- // Used to indicate whether the interpreter should continue execution.
- bool Status;
Frame *CurrentFrame = nullptr;
+ std::optional<ProgramExitInfo> ExitInfo;
ExecutorBase(Context &C, EventHandler &H)
- : Ctx(C), Handler(H), Status(true) {}
+ : Ctx(C), Handler(H), ExitInfo(std::nullopt) {}
~ExecutorBase() = default;
public:
@@ -93,6 +93,15 @@ class ExecutorBase {
AnyValue load(const AnyValue &Ptr, Align Alignment, Type *ValTy);
void store(const AnyValue &Ptr, Align Alignment, const AnyValue &Val,
Type *ValTy);
+
+ void requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
+ uint64_t ExitCode = 0);
+ void setFailed();
+
+ bool hasProgramExited() const;
+ std::optional<ProgramExitInfo> getExitInfo() const;
+
+ unsigned getIntSize() const;
};
} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index e5d15be805e07..1118320c7f254 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -12,6 +12,7 @@
#include "Context.h"
#include "ExecutorBase.h"
+#include "Library.h"
#include "Value.h"
#include "llvm/IR/GetElementPtrTypeIterator.h"
#include "llvm/IR/InlineAsm.h"
@@ -68,6 +69,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
const DataLayout &DL;
std::list<Frame> CallStack;
AnyValue None;
+ Library Lib;
const AnyValue &getValue(Value *V) {
if (auto *C = dyn_cast<Constant>(V))
@@ -76,8 +78,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void setResult(Instruction &I, AnyValue V) {
- if (Status)
- Status &= Handler.onInstructionExecuted(I, V);
+ if (!hasProgramExited() && !Handler.onInstructionExecuted(I, V))
+ setFailed();
CurrentFrame->ValueMap.insert_or_assign(&I, std::move(V));
}
@@ -142,7 +144,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
void jumpTo(Instruction &Terminator, BasicBlock *DestBB) {
if (!Handler.onBBJump(Terminator, *DestBB)) {
- Status = false;
+ setFailed();
return;
}
BasicBlock *From = CurrentFrame->BB;
@@ -257,7 +259,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
public:
InstExecutor(Context &C, EventHandler &H, Function &F,
ArrayRef<AnyValue> Args, AnyValue &RetVal)
- : ExecutorBase(C, H), DL(Ctx.getDataLayout()) {
+ : ExecutorBase(C, H), DL(Ctx.getDataLayout()),
+ Lib(Ctx, Handler, DL, static_cast<ExecutorBase &>(*this)) {
CallStack.emplace_back(F, /*CallSite=*/nullptr, /*LastFrame=*/nullptr, Args,
RetVal, Ctx.getTLIImpl());
}
@@ -266,7 +269,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
if (auto *RV = RI.getReturnValue())
CurrentFrame->RetVal = getValue(RV);
CurrentFrame->State = FrameState::Exit;
- Status &= Handler.onInstructionExecuted(RI, None);
+ if (!Handler.onInstructionExecuted(RI, None))
+ setFailed();
}
void visitUncondBrInst(UncondBrInst &BI) { jumpTo(BI, BI.getSuccessor()); }
@@ -311,7 +315,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
Handler.onUnrecognizedInstruction(CI);
- Status = false;
+ setFailed();
}
void visitIndirectBrInst(IndirectBrInst &IBI) {
@@ -379,23 +383,31 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
default:
Handler.onUnrecognizedInstruction(CB);
- Status = false;
+ setFailed();
return AnyValue();
}
}
- AnyValue callLibFunc(CallBase &CB, Function *ResolvedCallee) {
+ AnyValue callLibFunc(CallBase &CB, Function *ResolvedCallee,
+ ArrayRef<AnyValue> CalleeArgs) {
LibFunc LF;
// Respect nobuiltin attributes on call site.
if (CB.isNoBuiltin() ||
!CurrentFrame->TLI.getLibFunc(*ResolvedCallee, LF)) {
Handler.onUnrecognizedInstruction(CB);
- Status = false;
+ setFailed();
return AnyValue();
}
+ if (auto LibCallRes =
+ Lib.executeLibcall(LF, CB.getName(), CB.getType(), CalleeArgs))
+ return *LibCallRes;
+
+ if (ExitInfo)
+ return AnyValue();
+
Handler.onUnrecognizedInstruction(CB);
- Status = false;
+ setFailed();
return AnyValue();
}
@@ -420,7 +432,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
if (isa<InlineAsm>(CalledOperand)) {
Handler.onUnrecognizedInstruction(CB);
- Status = false;
+ setFailed();
return;
}
@@ -451,7 +463,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
returnFromCallee();
return;
} else if (Callee->isDeclaration()) {
- CurrentFrame->CalleeRetVal = callLibFunc(CB, Callee);
+ CurrentFrame->CalleeRetVal = callLibFunc(CB, Callee, CalleeArgs);
returnFromCallee();
return;
} else {
@@ -766,7 +778,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
auto Obj = Ctx.allocate(AllocSize, AI.getPointerAlignment(DL).value(),
AI.getName(), AI.getAddressSpace(),
IsInitiallyDead ? MemInitKind::Poisoned
- : MemInitKind::Uninitialized);
+ : MemInitKind::Uninitialized,
+ MemAllocKind::Stack);
if (!Obj) {
reportError("Insufficient stack space.");
return;
@@ -873,13 +886,13 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
// TODO: track volatile stores
// TODO: handle metadata
store(Ptr, SI.getAlign(), Val, SI.getValueOperand()->getType());
- if (Status)
- Status &= Handler.onInstructionExecuted(SI, AnyValue());
+ if (!hasProgramExited() && !Handler.onInstructionExecuted(SI, AnyValue()))
+ setFailed();
}
void visitInstruction(Instruction &I) {
Handler.onUnrecognizedInstruction(I);
- Status = false;
+ setFailed();
}
void visitExtractValueInst(ExtractValueInst &EVI) {
@@ -966,10 +979,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
/// This function implements the main interpreter loop.
/// It handles function calls in a non-recursive manner to avoid stack
/// overflows.
- bool runMainLoop() {
+ ProgramExitInfo runMainLoop() {
uint32_t MaxSteps = Ctx.getMaxSteps();
uint32_t Steps = 0;
- while (Status && !CallStack.empty()) {
+ while (!hasProgramExited() && !CallStack.empty()) {
Frame &Top = CallStack.back();
CurrentFrame = &Top;
if (Top.State == FrameState::Entry) {
@@ -982,7 +995,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
Top.State = FrameState::Running;
// Interpreter loop inside a function
- while (Status) {
+ while (!hasProgramExited()) {
assert(Top.State == FrameState::Running &&
"Expected to be in running state.");
if (MaxSteps != 0 && Steps >= MaxSteps) {
@@ -993,7 +1006,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
Instruction &I = *Top.PC;
visit(&I);
- if (!Status)
+ if (hasProgramExited())
break;
// A function call or return has occurred.
@@ -1007,7 +1020,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
++Top.PC;
}
- if (!Status)
+ if (hasProgramExited())
break;
if (Top.State == FrameState::Exit) {
@@ -1016,19 +1029,21 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
Handler.onFunctionExit(Top.Func, Top.RetVal);
// Free stack objects allocated in this frame.
for (auto &Obj : Top.Allocas)
- Ctx.free(Obj->getAddress());
+ Ctx.free(*Obj);
CallStack.pop_back();
} else {
assert(Top.State == FrameState::Pending &&
"Expected to enter a callee.");
}
}
- return Status;
+ if (!hasProgramExited())
+ requestProgramExit(ProgramExitInfo::ProgramExitKind::Returned);
+ return *getExitInfo();
}
};
-bool Context::runFunction(Function &F, ArrayRef<AnyValue> Args,
- AnyValue &RetVal, EventHandler &Handler) {
+ProgramExitInfo Context::runFunction(Function &F, ArrayRef<AnyValue> Args,
+ AnyValue &RetVal, EventHandler &Handler) {
InstExecutor Executor(*this, Handler, F, Args, RetVal);
return Executor.runMainLoop();
}
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
new file mode 100644
index 0000000000000..b0b5e3eb2c919
--- /dev/null
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -0,0 +1,362 @@
+//===- Library.cpp - Library calls for llubi ------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements common libcalls for llubi.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Library.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Analysis/TargetLibraryInfo.h"
+#include "llvm/IR/InstrTypes.h"
+#include "llvm/Support/Format.h"
+#include "llvm/Support/raw_ostream.h"
+
+namespace llvm::ubi {
+
+static uint64_t getMaxAlign(const DataLayout &DL) {
+ // Return an alignment of 16 for 64-bit platforms, and 8 for 32-bit ones.
+ return DL.getPointerABIAlignment(0).value() >= 8 ? 16 : 8;
+}
+
+Library::Library(Context &Ctx, EventHandler &Handler, const DataLayout &DL,
+ ExecutorBase &Executor)
+ : Ctx(Ctx), Handler(Handler), DL(DL), Executor(Executor) {}
+
+std::optional<std::string> Library::readStringFromMemory(const Pointer &Ptr) {
+ auto *MO = Ptr.getMemoryObject();
+ if (!MO) {
+ Executor.reportImmediateUB(
+ "Invalid memory access via a pointer with nullary "
+ "provenance.");
+ return std::nullopt;
+ }
+
+ std::string Result;
+ const APInt &Address = Ptr.address();
+ uint64_t Offset = 0;
+
+ while (true) {
+ auto ValidOffset =
+ Executor.verifyMemAccess(*MO, Address + Offset, 1, Align(1), false);
+ if (!ValidOffset)
+ return std::nullopt;
+
+ Byte B = (*MO)[*ValidOffset];
+ if (B.ConcreteMask != 0xFF) {
+ Executor.reportImmediateUB("Read uninitialized or poison memory while "
+ "parsing C-string.");
+ return std::nullopt;
+ }
+
+ if (B.Value == 0)
+ break;
+
+ Result.push_back(static_cast<char>(B.Value));
+ ++Offset;
+ }
+
+ return Result;
+}
+
+AnyValue Library::executeMalloc(StringRef Name, Type *Type,
+ ArrayRef<AnyValue> Args,
+ MemAllocKind AllocKind) {
+ assert(AllocKind == MemAllocKind::Malloc || AllocKind == MemAllocKind::New ||
+ AllocKind == MemAllocKind::NewArray &&
+ "Unexpected MemAllocKind for malloc()/new/new[]");
+
+ const auto &SizeVal = Args[0];
+
+ const uint64_t AllocSize = SizeVal.asInteger().getZExtValue();
+
+ const IntrusiveRefCntPtr<MemoryObject> Obj =
+ Ctx.allocate(AllocSize, getMaxAlign(DL), Name, 0,
+ MemInitKind::Uninitialized, AllocKind);
+
+ if (!Obj) {
+ if (AllocKind == MemAllocKind::New || AllocKind == MemAllocKind::NewArray) {
+ // FIXME: As llubi doesn't support stack unwinding yet, we report an error
+ // when new/new[] fails.
+ Executor.reportError("Insufficient heap space.");
+ return AnyValue::poison();
+ }
+ return AnyValue::getNullValue(Ctx, Type);
+ }
+
+ return Ctx.deriveFromMemoryObject(Obj);
+}
+
+AnyValue Library::executeCalloc(StringRef Name, Type *Type,
+ ArrayRef<AnyValue> Args,
+ MemAllocKind AllocKind) {
+ assert(AllocKind == MemAllocKind::Malloc &&
+ "Unexpected MemAllocKind for calloc()");
+
+ const auto &CountVal = Args[0];
+ const auto &SizeVal = Args[1];
+
+ const APInt &Count = CountVal.asInteger();
+ const APInt &Size = SizeVal.asInteger();
+
+ bool Overflow = false;
+ const APInt AllocSize = Count.umul_ov(Size, Overflow);
+ if (Overflow)
+ return AnyValue::getNullValue(Ctx, Type);
+
+ const IntrusiveRefCntPtr<MemoryObject> Obj =
+ Ctx.allocate(AllocSize.getLimitedValue(), getMaxAlign(DL), Name, 0,
+ MemInitKind::Zeroed, AllocKind);
+
+ if (!Obj)
+ return AnyValue::getNullValue(Ctx, Type);
+
+ return Ctx.deriveFromMemoryObject(Obj);
+}
+
+AnyValue Library::executeFree(ArrayRef<AnyValue> Args) {
+ const auto &PtrVal = Args[0];
+
+ auto &Ptr = PtrVal.asPointer();
+ // no-op when free is called with a null pointer.
+ if (Ptr.address().isZero())
+ return AnyValue();
+
+ MemoryObject *Obj = Ptr.getMemoryObject();
+ if (!Obj) {
+ Executor.reportImmediateUB("freeing a pointer with nullary provenance.");
+ return AnyValue::poison();
+ }
+
+ if (const uint64_t Address = Ptr.address().getZExtValue();
+ Address != Obj->getAddress()) {
+ Executor.reportImmediateUB(
+ "freeing a pointer that does not point to the start of an allocation.");
+ return AnyValue::poison();
+ }
+
+ if (Obj->getState() == MemoryObjectState::Freed) {
+ Executor.reportImmediateUB("double-freeing a memory object.");
+ return AnyValue::poison();
+ }
+
+ if (!Obj->isHeapAllocated()) {
+ Executor.reportImmediateUB("freeing a non-heap allocation.");
+ return AnyValue::poison();
+ }
+
+ // Currently we don't check for cases where a memory allocated with C
+ // allocation family (malloc, calloc, etc.) is freed with a
diff erent free
+ // function comes from a
diff erent family (C++ delete, etc.)
+
+ if (!Ctx.free(*Obj)) {
+ Executor.reportImmediateUB("freeing an invalid pointer.");
+ return AnyValue::poison();
+ }
+
+ return AnyValue();
+}
+
+AnyValue Library::executePuts(ArrayRef<AnyValue> Args) {
+ const auto &PtrVal = Args[0];
+
+ const auto StrOpt = readStringFromMemory(PtrVal.asPointer());
+ if (!StrOpt)
+ return AnyValue::poison();
+
+ Handler.onPrint(*StrOpt + "\n");
+ return AnyValue(APInt(Executor.getIntSize(), 1));
+}
+
+AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
+ const auto &FormatPtrVal = Args[0];
+
+ const auto FormatStrOpt = readStringFromMemory(FormatPtrVal.asPointer());
+ if (!FormatStrOpt)
+ return AnyValue::poison();
+
+ const std::string &FormatStr = *FormatStrOpt;
+ std::string Output;
+ raw_string_ostream OS(Output);
+ unsigned ArgIndex = 1; // Start from 1 since 0 is the format string.
+
+ for (unsigned I = 0; I < FormatStr.size();) {
+ if (FormatStr[I] != '%') {
+ OS << FormatStr[I++];
+ continue;
+ }
+
+ const size_t Start = I++;
+ if (I < FormatStr.size() && FormatStr[I] == '%') {
+ OS << '%';
+ ++I;
+ continue;
+ }
+
+ while (I < FormatStr.size() &&
+ StringRef("-= #0123456789").contains(FormatStr[I]))
+ ++I;
+
+ while (I < FormatStr.size() && StringRef("hljzt").contains(FormatStr[I]))
+ ++I;
+
+ if (I >= FormatStr.size()) {
+ Executor.reportImmediateUB(
+ "Invalid format string in printf: missing conversion "
+ "specifier.");
+ return AnyValue::poison();
+ }
+
+ char Specifier = FormatStr[I++];
+ std::string CleanChunk = FormatStr.substr(Start, I - Start - 1);
+ CleanChunk.erase(
+ llvm::remove_if(CleanChunk,
+ [](char C) { return StringRef("hljzt").contains(C); }),
+ CleanChunk.end());
+
+ if (ArgIndex >= Args.size()) {
+ Executor.reportImmediateUB(
+ "Not enough arguments provided for the format string.");
+ return AnyValue::poison();
+ }
+
+ const auto &Arg = Args[ArgIndex++];
+ if (Arg.isPoison()) {
+ Executor.reportImmediateUB("Poison argument passed to printf.");
+ return AnyValue::poison();
+ }
+
+ switch (Specifier) {
+ case 'd':
+ case 'i': {
+ std::string HostFmt = CleanChunk + "ll" + Specifier;
+ OS << format(HostFmt.c_str(),
+ static_cast<long long>(Arg.asInteger().getSExtValue()));
+ break;
+ }
+ case 'u':
+ case 'o':
+ case 'x':
+ case 'X':
+ case 'c': {
+ // FIXME: The format specifiers "b" and "B" are not implemented here
+ // since currently MSVC doesn't support it.
+ std::string HostFmt = CleanChunk + "ll" + Specifier;
+ OS << format(HostFmt.c_str(), static_cast<unsigned long long>(
+ Arg.asInteger().getZExtValue()));
+ break;
+ }
+ case 'f':
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ case 'a':
+ case 'A': {
+ std::string HostFmt = CleanChunk + Specifier;
+ OS << format(HostFmt.c_str(), Arg.asFloat().convertToDouble());
+ break;
+ }
+ case 'n': {
+ OS.flush();
+ Executor.store(Arg, Align(4), AnyValue(APInt(32, Output.size())),
+ Type::getInt32Ty(Ctx.getContext()));
+ break;
+ }
+ case 'p': {
+ std::string HostFmt = CleanChunk + "llx";
+ OS << "0x"
+ << format(HostFmt.c_str(),
+ static_cast<unsigned long long>(
+ Arg.asPointer().address().getZExtValue()));
+ break;
+ }
+ case 's': {
+ auto StrOpt = readStringFromMemory(Arg.asPointer());
+ if (!StrOpt)
+ return AnyValue::poison();
+ std::string HostFmt = CleanChunk + "s";
+ OS << format(HostFmt.c_str(), StrOpt->c_str());
+ break;
+ }
+ default:
+ Executor.reportImmediateUB(
+ "Unknown or unsupported format specifier in printf.");
+ return AnyValue::poison();
+ }
+ }
+
+ OS.flush();
+ Handler.onPrint(Output);
+ return AnyValue(APInt(Executor.getIntSize(), Output.size()));
+}
+
+AnyValue Library::executeExit(ArrayRef<AnyValue> Args) {
+ const auto &RetCodeVal = Args[0];
+
+ Executor.requestProgramExit(ProgramExitInfo::ProgramExitKind::Exited,
+ RetCodeVal.asInteger().getZExtValue());
+ return AnyValue();
+}
+
+AnyValue Library::executeAbort() {
+ Executor.requestProgramExit(ProgramExitInfo::ProgramExitKind::Aborted);
+ return AnyValue();
+}
+
+AnyValue Library::executeTerminate() {
+ Executor.requestProgramExit(ProgramExitInfo::ProgramExitKind::Terminated);
+ return AnyValue();
+}
+
+std::optional<AnyValue> Library::executeLibcall(LibFunc LF, StringRef Name,
+ Type *Type,
+ ArrayRef<AnyValue> Args) {
+ for (const AnyValue &Arg : Args) {
+ if (Arg.isPoison()) {
+ Executor.reportImmediateUB("Poison argument passed to a library call.");
+ return AnyValue::poison();
+ }
+ }
+
+ switch (LF) {
+ case LibFunc_malloc:
+ return executeMalloc(Name, Type, Args, MemAllocKind::Malloc);
+ case LibFunc_Znwm:
+ return executeMalloc(Name, Type, Args, MemAllocKind::New);
+ case LibFunc_Znam:
+ return executeMalloc(Name, Type, Args, MemAllocKind::NewArray);
+
+ case LibFunc_calloc:
+ return executeCalloc(Name, Type, Args, MemAllocKind::Malloc);
+
+ case LibFunc_free:
+ case LibFunc_ZdaPv:
+ case LibFunc_ZdlPv:
+ return executeFree(Args);
+
+ case LibFunc_puts:
+ return executePuts(Args);
+
+ case LibFunc_printf:
+ return executePrintf(Args);
+
+ case LibFunc_exit:
+ return executeExit(Args);
+
+ case LibFunc_abort:
+ return executeAbort();
+
+ case LibFunc_terminate:
+ return executeTerminate();
+
+ default:
+ return std::nullopt;
+ }
+}
+} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/Library.h b/llvm/tools/llubi/lib/Library.h
new file mode 100644
index 0000000000000..604415d545dd0
--- /dev/null
+++ b/llvm/tools/llubi/lib/Library.h
@@ -0,0 +1,56 @@
+//===--- Library.h - Library calls for llubi ------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file declares common libcalls for llubi.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TOOLS_LLUBI_LIBRARY_H
+#define LLVM_TOOLS_LLUBI_LIBRARY_H
+
+#include "Context.h"
+#include "ExecutorBase.h"
+#include "Value.h"
+#include <optional>
+#include <string>
+
+namespace llvm::ubi {
+
+class Library {
+ Context &Ctx;
+ EventHandler &Handler;
+ const DataLayout &DL;
+ ExecutorBase &Executor;
+
+ std::optional<std::string> readStringFromMemory(const Pointer &Ptr);
+
+ AnyValue executeMalloc(StringRef Name, Type *Type, ArrayRef<AnyValue> Args,
+ MemAllocKind AllocKind);
+ AnyValue executeCalloc(StringRef Name, Type *Type, ArrayRef<AnyValue> Args,
+ MemAllocKind AllocKind);
+ AnyValue executeFree(ArrayRef<AnyValue> Args);
+ AnyValue executePuts(ArrayRef<AnyValue> Args);
+ AnyValue executePrintf(ArrayRef<AnyValue> Args);
+ AnyValue executeExit(ArrayRef<AnyValue> Args);
+ AnyValue executeAbort();
+ AnyValue executeTerminate();
+
+public:
+ Library(Context &Ctx, EventHandler &Handler, const DataLayout &DL,
+ ExecutorBase &Executor);
+
+ /// Simulates a libcall. Returns std::nullopt if an unsupported LibFunc is
+ /// passed. Note that the caller is responsible for ensuring the types and
+ /// number of the arguments are correct.
+ std::optional<AnyValue> executeLibcall(LibFunc LF, StringRef Name, Type *Type,
+ ArrayRef<AnyValue> Args);
+};
+
+} // namespace llvm::ubi
+
+#endif // LLVM_TOOLS_LLUBI_LIBRARY_H
diff --git a/llvm/tools/llubi/llubi.cpp b/llvm/tools/llubi/llubi.cpp
index de76a7e64c27b..0ec2e236049dd 100644
--- a/llvm/tools/llubi/llubi.cpp
+++ b/llvm/tools/llubi/llubi.cpp
@@ -131,6 +131,26 @@ class VerboseEventHandler : public ubi::EventHandler {
return true;
}
+ void onProgramExit(const ubi::ProgramExitInfo &Info) override {
+ switch (Info.Kind) {
+ case ubi::ProgramExitInfo::ProgramExitKind::Returned:
+ return;
+ case ubi::ProgramExitInfo::ProgramExitKind::Failed:
+ return;
+ case ubi::ProgramExitInfo::ProgramExitKind::Exited:
+ errs() << "Program exited with code " << Info.ExitCode << '\n';
+ return;
+ case ubi::ProgramExitInfo::ProgramExitKind::Aborted:
+ errs() << "Program aborted.\n";
+ return;
+ case ubi::ProgramExitInfo::ProgramExitKind::Terminated:
+ errs() << "Program terminated.\n";
+ return;
+ }
+
+ llvm_unreachable("Unknown ProgramExitKind");
+ }
+
void onUnrecognizedInstruction(Instruction &I) override {
errs() << "Unrecognized instruction: " << I << '\n';
}
@@ -207,7 +227,8 @@ int main(int argc, char **argv) {
uint32_t PtrSize = Ctx.getDataLayout().getPointerSize();
uint64_t PtrsSize = PtrSize * (InputArgv.size() + 1);
auto ArgvPtrsMem = Ctx.allocate(PtrsSize, 8, "argv",
- /*AS=*/0, ubi::MemInitKind::Zeroed);
+ /*AS=*/0, ubi::MemInitKind::Zeroed,
+ ubi::MemAllocKind::Global);
if (!ArgvPtrsMem) {
WithColor::error() << "Failed to allocate memory for argv pointers.\n";
return 1;
@@ -215,7 +236,8 @@ int main(int argc, char **argv) {
for (const auto &[Idx, Arg] : enumerate(InputArgv)) {
uint64_t Size = Arg.length() + 1;
auto ArgvStrMem = Ctx.allocate(Size, 8, "argv_str",
- /*AS=*/0, ubi::MemInitKind::Zeroed);
+ /*AS=*/0, ubi::MemInitKind::Zeroed,
+ ubi::MemAllocKind::Global);
if (!ArgvStrMem) {
WithColor::error() << "Failed to allocate memory for argv strings.\n";
return 1;
@@ -240,24 +262,33 @@ int main(int argc, char **argv) {
ubi::EventHandler NoopHandler;
VerboseEventHandler VerboseHandler;
ubi::AnyValue RetVal;
- if (!Ctx.runFunction(*EntryFn, Args, RetVal,
- Verbose ? VerboseHandler : NoopHandler)) {
+ ubi::ProgramExitInfo ExitInfo = Ctx.runFunction(
+ *EntryFn, Args, RetVal, Verbose ? VerboseHandler : NoopHandler);
+ switch (ExitInfo.Kind) {
+ case ubi::ProgramExitInfo::ProgramExitKind::Failed:
WithColor::error() << "Execution of function '" << EntryFunc
<< "' failed.\n";
return 1;
- }
-
- // If the function returns an integer, return that as the exit code.
- if (EntryFn->getReturnType()->isIntegerTy()) {
- assert(!RetVal.isNone() && "Expected a return value from entry function");
- if (RetVal.isPoison()) {
- WithColor::error() << "Execution of function '" << EntryFunc
- << "' resulted in poison return value.\n";
- return 1;
+ case ubi::ProgramExitInfo::ProgramExitKind::Aborted:
+ case ubi::ProgramExitInfo::ProgramExitKind::Terminated:
+ return 134;
+ case ubi::ProgramExitInfo::ProgramExitKind::Exited:
+ return static_cast<int>(ExitInfo.ExitCode & 0xFF);
+ case ubi::ProgramExitInfo::ProgramExitKind::Returned:
+ // If the function returns an integer, return that as the exit code.
+ if (EntryFn->getReturnType()->isIntegerTy()) {
+ assert(!RetVal.isNone() && "Expected a return value from entry function");
+ if (RetVal.isPoison()) {
+ WithColor::error() << "Execution of function '" << EntryFunc
+ << "' resulted in poison return value.\n";
+ return 1;
+ }
+ APInt Result = RetVal.asInteger();
+ return (int)Result.extractBitsAsZExtValue(
+ std::min(Result.getBitWidth(), 8U), 0);
}
- APInt Result = RetVal.asInteger();
- return (int)Result.extractBitsAsZExtValue(
- std::min(Result.getBitWidth(), 8U), 0);
+ return 0;
}
- return 0;
+
+ llvm_unreachable("Unknown ProgramExitKind");
}
More information about the llvm-commits
mailing list