[llvm] [llubi] Implements common library functions (PR #190147)

Zhige Chen via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 9 21:48:51 PDT 2026


https://github.com/nofe1248 updated https://github.com/llvm/llvm-project/pull/190147

>From d97a702403a3102adc2321baf0e1c7098e7a3152 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 2 Apr 2026 18:40:05 +0800
Subject: [PATCH 01/21] [llubi] Implements common library functions

---
 llvm/test/tools/llubi/lib_abort.ll         |  30 ++
 llvm/test/tools/llubi/lib_cxx_memory.ll    |  20 ++
 llvm/test/tools/llubi/lib_double_free.ll   |  21 ++
 llvm/test/tools/llubi/lib_exit.ll          |  30 ++
 llvm/test/tools/llubi/lib_io.ll            |  36 +++
 llvm/test/tools/llubi/lib_memory.ll        |  32 ++
 llvm/test/tools/llubi/lib_printf_format.ll |  58 ++++
 llvm/test/tools/llubi/lib_terminate.ll     |  30 ++
 llvm/test/tools/llubi/lib_uninit_string.ll |  18 ++
 llvm/tools/llubi/lib/CMakeLists.txt        |   1 +
 llvm/tools/llubi/lib/Context.h             |  41 ++-
 llvm/tools/llubi/lib/ExecutorBase.cpp      |  16 +-
 llvm/tools/llubi/lib/ExecutorBase.h        |  14 +-
 llvm/tools/llubi/lib/Interpreter.cpp       |  89 ++++--
 llvm/tools/llubi/lib/Library.cpp           | 348 +++++++++++++++++++++
 llvm/tools/llubi/lib/Library.h             |  55 ++++
 llvm/tools/llubi/llubi.cpp                 |  42 ++-
 17 files changed, 835 insertions(+), 46 deletions(-)
 create mode 100644 llvm/test/tools/llubi/lib_abort.ll
 create mode 100644 llvm/test/tools/llubi/lib_cxx_memory.ll
 create mode 100644 llvm/test/tools/llubi/lib_double_free.ll
 create mode 100644 llvm/test/tools/llubi/lib_exit.ll
 create mode 100644 llvm/test/tools/llubi/lib_io.ll
 create mode 100644 llvm/test/tools/llubi/lib_memory.ll
 create mode 100644 llvm/test/tools/llubi/lib_printf_format.ll
 create mode 100644 llvm/test/tools/llubi/lib_terminate.ll
 create mode 100644 llvm/test/tools/llubi/lib_uninit_string.ll
 create mode 100644 llvm/tools/llubi/lib/Library.cpp
 create mode 100644 llvm/tools/llubi/lib/Library.h

diff --git a/llvm/test/tools/llubi/lib_abort.ll b/llvm/test/tools/llubi/lib_abort.ll
new file mode 100644
index 0000000000000..84327fdc99c5d
--- /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:   %0 = call i32 @puts(ptr %before) => i32 1
+; CHECK-NEXT: Program aborted.
+; CHECK-NEXT: Before
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_double_free.ll b/llvm/test/tools/llubi/lib_double_free.ll
new file mode 100644
index 0000000000000..2441d69f6628a
--- /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: freeing an invalid, unallocated, or already freed pointer.
+; 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..d6a7037c50043
--- /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:   %0 = call i32 @puts(ptr %before) => i32 1
+; CHECK-NEXT: Program exited with code 42
+; CHECK-NEXT: Before
diff --git a/llvm/test/tools/llubi/lib_io.ll b/llvm/test/tools/llubi/lib_io.ll
new file mode 100644
index 0000000000000..5b5c861f5d237
--- /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:   %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:   %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
+; CHECK-NEXT: Hello, puts!
+; CHECK-NEXT: Int: 42, Str: test
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_printf_format.ll b/llvm/test/tools/llubi/lib_printf_format.ll
new file mode 100644
index 0000000000000..24cc5f2bd2b40
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_printf_format.ll
@@ -0,0 +1,58 @@
+; 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
+
+  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)
+
+  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:   %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:   %1 = call i32 (ptr, ...) @printf(ptr %fmt_len, i64 123456789, i64 987654321, i32 100, i32 50, i32 65) => i32 42
+; CHECK-NEXT:   %2 = call i32 (ptr, ...) @printf(ptr %fmt_str_ptr, ptr %dummy_str, ptr %dummy_str) => i32 22
+; CHECK-NEXT:   %3 = call i32 (ptr, ...) @printf(ptr %fmt_pct, i32 100) => i32 14
+; 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:   ret i32 0
+; CHECK-NEXT: Exiting function: main
+; CHECK-NEXT: Ints: 42, -42, 255, 377, ff, FF, 00042
+; CHECK-NEXT: Lengths: 123456789, 987654321, 100, 50, A
+; CHECK-NEXT: Str: llubi, Ptr: 0x70
+; CHECK-NEXT: Percent: 100%
+; CHECK-NEXT: Floats: 3.141590, 3.141590e+00, 3.14159
diff --git a/llvm/test/tools/llubi/lib_terminate.ll b/llvm/test/tools/llubi/lib_terminate.ll
new file mode 100644
index 0000000000000..6d7821584e1a4
--- /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:   %0 = call i32 @puts(ptr %before) => i32 1
+; CHECK-NEXT: Program terminated.
+; CHECK-NEXT: Before
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.h b/llvm/tools/llubi/lib/Context.h
index d1960b270d9bd..06aff25f8e46d 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -47,6 +47,34 @@ enum class UndefValueBehavior {
   Zero,             // All uses of the undef value yield zero.
 };
 
+struct ProgramExitInfo {
+  enum class ProgramExitKind {
+    Invalid,
+    // 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 = ProgramExitKind::Invalid;
+  uint64_t ExitCode = 0;
+
+  explicit operator bool() const { return Kind != ProgramExitKind::Invalid; }
+
+  bool isExitedByLibcall() const {
+    return Kind == ProgramExitKind::Exited ||
+           Kind == ProgramExitKind::Aborted ||
+           Kind == ProgramExitKind::Terminated;
+  }
+};
+
 class MemoryObject : public RefCountedBase<MemoryObject> {
   uint64_t Address;
   uint64_t Size;
@@ -110,6 +138,7 @@ class EventHandler {
   virtual bool onFunctionExit(Function &F, const AnyValue &RetVal) {
     return true;
   }
+  virtual bool onProgramExit(const ProgramExitInfo &ExitInfo) { return true; }
   virtual bool onPrint(StringRef Msg) {
     outs() << Msg;
     return true;
@@ -257,13 +286,15 @@ class Context {
   /// initialization).
   bool initGlobalValues();
   /// Execute the function \p F with arguments \p Args, and store the return
-  /// value in \p RetVal if the function is not void.
-  /// Returns true if the function executed successfully. False indicates an
-  /// error occurred during execution.
+  /// value in \p RetVal if the function is not void. The exit information is
+  /// store in \p ExitInfo.
+  /// Returns true if the function executed successfully without calls to
+  /// exit()/abort()/terminate(). False indicates an error occurred during
+  /// execution.
   bool runFunction(Function &F, ArrayRef<AnyValue> Args, AnyValue &RetVal,
-                   EventHandler &Handler);
+                   EventHandler &Handler, ProgramExitInfo &ExitInfo);
 };
 
 } // namespace llvm::ubi
 
-#endif
+#endif
\ No newline at end of file
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index ec66e831908c5..d546c80e17aad 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -124,4 +124,18 @@ void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
                           /*IsStore=*/true))
     Ctx.store(*MO, *Offset, Val, ValTy);
 }
-} // namespace llvm::ubi
+
+void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
+uint64_t ExitCode)  {
+  if (Kind == ProgramExitInfo::ProgramExitKind::Invalid)
+    llvm_unreachable("Invalid program exit kind");
+  Status = false;
+  ExitInfo.Kind = Kind;
+  ExitInfo.ExitCode = ExitCode;
+  Handler.onProgramExit(ExitInfo);
+}
+
+bool ExecutorBase::getExecutionStatus() const { return Status; }
+
+ProgramExitInfo ExecutorBase::getExitInfo() const { return ExitInfo; }
+} // namespace llvm::ubi
\ No newline at end of file
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index 0f80c6a329058..e400fb95d02f7 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -71,10 +71,14 @@ class ExecutorBase {
 protected:
   Context &Ctx;
   EventHandler &Handler;
+  Frame *CurrentFrame = nullptr;
+  ProgramExitInfo ExitInfo;
+
+private:
   // Used to indicate whether the interpreter should continue execution.
   bool Status;
-  Frame *CurrentFrame = nullptr;
 
+protected:
   ExecutorBase(Context &C, EventHandler &H)
       : Ctx(C), Handler(H), Status(true) {}
   ~ExecutorBase() = default;
@@ -93,8 +97,14 @@ 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);
+
+  bool getExecutionStatus() const;
+  ProgramExitInfo getExitInfo() const;
 };
 
 } // namespace llvm::ubi
 
-#endif // LLVM_TOOLS_LLUBI_EXECUTORBASE_H
+#endif // LLVM_TOOLS_LLUBI_EXECUTORBASE_H
\ No newline at end of file
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index e5d15be805e07..c72ca1d0842b4 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"
@@ -76,8 +77,9 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   }
 
   void setResult(Instruction &I, AnyValue V) {
-    if (Status)
-      Status &= Handler.onInstructionExecuted(I, V);
+    if (getExecutionStatus())
+      if (!Handler.onInstructionExecuted(I, V))
+        requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
     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;
+      requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
       return;
     }
     BasicBlock *From = CurrentFrame->BB;
@@ -266,23 +268,26 @@ 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 (getExecutionStatus())
+      if (!Handler.onInstructionExecuted(RI, None))
+        requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
   }
 
-  void visitUncondBrInst(UncondBrInst &BI) { jumpTo(BI, BI.getSuccessor()); }
-
-  void visitCondBrInst(CondBrInst &BI) {
-    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;
+  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) {
@@ -311,7 +316,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     }
 
     Handler.onUnrecognizedInstruction(CI);
-    Status = false;
+    requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
   }
 
   void visitIndirectBrInst(IndirectBrInst &IBI) {
@@ -379,7 +384,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     }
     default:
       Handler.onUnrecognizedInstruction(CB);
-      Status = false;
+      requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
       return AnyValue();
     }
   }
@@ -390,12 +395,26 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     if (CB.isNoBuiltin() ||
         !CurrentFrame->TLI.getLibFunc(*ResolvedCallee, LF)) {
       Handler.onUnrecognizedInstruction(CB);
-      Status = false;
+      requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
       return AnyValue();
     }
 
+    Library Lib(Ctx, Handler, DL, static_cast<ExecutorBase &>(*this));
+
+    SmallVector<AnyValue, 8> Args;
+    for (const auto &Arg : CB.args()) {
+      Args.push_back(getValue(Arg));
+    }
+
+    if (auto LibCallRes =
+            Lib.executeLibcall(LF, CB.getName(), CB.getType(), Args))
+      return *LibCallRes;
+
+    if (ExitInfo)
+      return AnyValue();
+
     Handler.onUnrecognizedInstruction(CB);
-    Status = false;
+    requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
     return AnyValue();
   }
 
@@ -420,7 +439,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
 
       if (isa<InlineAsm>(CalledOperand)) {
         Handler.onUnrecognizedInstruction(CB);
-        Status = false;
+        requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
         return;
       }
 
@@ -873,13 +892,14 @@ 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 (getExecutionStatus())
+      if (!Handler.onInstructionExecuted(SI, AnyValue()))
+        requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
   }
 
   void visitInstruction(Instruction &I) {
     Handler.onUnrecognizedInstruction(I);
-    Status = false;
+    requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
   }
 
   void visitExtractValueInst(ExtractValueInst &EVI) {
@@ -969,7 +989,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   bool runMainLoop() {
     uint32_t MaxSteps = Ctx.getMaxSteps();
     uint32_t Steps = 0;
-    while (Status && !CallStack.empty()) {
+    while (getExecutionStatus() && !CallStack.empty()) {
       Frame &Top = CallStack.back();
       CurrentFrame = &Top;
       if (Top.State == FrameState::Entry) {
@@ -982,7 +1002,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
 
       Top.State = FrameState::Running;
       // Interpreter loop inside a function
-      while (Status) {
+      while (getExecutionStatus()) {
         assert(Top.State == FrameState::Running &&
                "Expected to be in running state.");
         if (MaxSteps != 0 && Steps >= MaxSteps) {
@@ -993,7 +1013,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
 
         Instruction &I = *Top.PC;
         visit(&I);
-        if (!Status)
+        if (!getExecutionStatus())
           break;
 
         // A function call or return has occurred.
@@ -1007,7 +1027,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
           ++Top.PC;
       }
 
-      if (!Status)
+      if (!getExecutionStatus())
         break;
 
       if (Top.State == FrameState::Exit) {
@@ -1023,14 +1043,17 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
                "Expected to enter a callee.");
       }
     }
-    return Status;
+    return getExecutionStatus();
   }
 };
 
 bool Context::runFunction(Function &F, ArrayRef<AnyValue> Args,
-                          AnyValue &RetVal, EventHandler &Handler) {
+                          AnyValue &RetVal, EventHandler &Handler,
+                          ProgramExitInfo &ExitInfo) {
   InstExecutor Executor(*this, Handler, F, Args, RetVal);
-  return Executor.runMainLoop();
+  bool Result = Executor.runMainLoop();
+  ExitInfo = Executor.getExitInfo();
+  return Result;
 }
 
-} // namespace llvm::ubi
+} // namespace llvm::ubi
\ No newline at end of file
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
new file mode 100644
index 0000000000000..8f79d14671250
--- /dev/null
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -0,0 +1,348 @@
+//===- 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/Analysis/TargetLibraryInfo.h"
+#include "llvm/IR/InstrTypes.h"
+
+namespace llvm::ubi {
+
+static uint64_t getMaxAlignT(const DataLayout &DL) {
+  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 uint64_t Address = Ptr.address().getZExtValue();
+  uint64_t Offset = 0;
+
+  while (true) {
+    auto ValidOffset = Executor.verifyMemAccess(
+        *MO, APInt(DL.getPointerSizeInBits(0), 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) {
+  const auto &SizeVal = Args[0];
+  if (SizeVal.isPoison()) {
+    Executor.reportImmediateUB("malloc() called with a poison size.");
+    return AnyValue::poison();
+  }
+
+  const uint64_t AllocSize = SizeVal.asInteger().getZExtValue();
+  const uint64_t MaxAlign = getMaxAlignT(DL);
+
+  const auto Obj =
+      Ctx.allocate(AllocSize, MaxAlign, Name, 0, MemInitKind::Uninitialized);
+
+  if (!Obj)
+    return AnyValue::getNullValue(Ctx, Type);
+
+  return Ctx.deriveFromMemoryObject(Obj);
+}
+
+AnyValue Library::executeCalloc(StringRef Name, Type *Type,
+                                ArrayRef<AnyValue> Args) {
+  const auto &CountVal = Args[0];
+  const auto &SizeVal = Args[1];
+
+  if (CountVal.isPoison()) {
+    Executor.reportImmediateUB("calloc() called with a poison count.");
+    return AnyValue::poison();
+  }
+  if (SizeVal.isPoison()) {
+    Executor.reportImmediateUB("calloc() called with a poison size.");
+    return AnyValue::poison();
+  }
+
+  const uint64_t Count = CountVal.asInteger().getZExtValue();
+  const uint64_t Size = SizeVal.asInteger().getZExtValue();
+
+  bool Overflow;
+  const uint64_t AllocSize = SaturatingMultiply(Count, Size, &Overflow);
+  if (Overflow) {
+    return AnyValue::getNullValue(Ctx, Type);
+  }
+
+  const uint64_t MaxAlign = getMaxAlignT(DL);
+
+  // TODO: Figure out how to name the allocation
+  const auto Obj =
+      Ctx.allocate(AllocSize, MaxAlign, Name, 0, MemInitKind::Zeroed);
+
+  if (!Obj) {
+    return AnyValue::getNullValue(Ctx, Type);
+  }
+
+  return Ctx.deriveFromMemoryObject(Obj);
+}
+
+AnyValue Library::executeFree(StringRef Name, Type *Type,
+                              ArrayRef<AnyValue> Args) {
+  const auto &PtrVal = Args[0];
+  if (PtrVal.isPoison()) {
+    Executor.reportImmediateUB("free() called with a poison pointer.");
+    return AnyValue::poison();
+  }
+
+  auto &Ptr = PtrVal.asPointer();
+  if (Ptr.address().isZero()) {
+    // no-op when free is called with a null pointer.
+    return AnyValue();
+  }
+
+  if (!Ctx.free(Ptr.address().getZExtValue())) {
+    Executor.reportImmediateUB(
+        "freeing an invalid, unallocated, or already freed pointer.");
+    return AnyValue::poison();
+  }
+
+  return AnyValue();
+}
+
+AnyValue Library::executePuts(StringRef Name, Type *Type,
+                              ArrayRef<AnyValue> Args) {
+  const auto &PtrVal = Args[0];
+  if (PtrVal.isPoison()) {
+    Executor.reportImmediateUB("puts called with a poison pointer.");
+    return AnyValue::poison();
+  }
+
+  const auto StrOpt = readStringFromMemory(PtrVal.asPointer());
+  if (!StrOpt) {
+    return AnyValue::poison();
+  }
+
+  Handler.onPrint(*StrOpt + "\n");
+  return AnyValue(APInt(32, 1));
+}
+
+AnyValue Library::executePrintf(StringRef Name, Type *Type,
+                                ArrayRef<AnyValue> Args) {
+  const auto &FormatPtrVal = Args[0];
+  if (FormatPtrVal.isPoison()) {
+    Executor.reportImmediateUB(
+        "printf called with a poison format string pointer.");
+    return AnyValue::poison();
+  }
+
+  const auto FormatStrOpt = readStringFromMemory(FormatPtrVal.asPointer());
+  if (!FormatStrOpt) {
+    return AnyValue::poison();
+  }
+
+  const std::string FormatStr = *FormatStrOpt;
+  std::string Output;
+  unsigned ArgIndex = 1; // Start from 1 since 0 is the format string.
+
+  for (size_t i = 0; i < FormatStr.size();) {
+    if (FormatStr[i] != '%') {
+      Output.push_back(FormatStr[i++]);
+      continue;
+    }
+
+    const size_t Start = i++;
+    if (i < FormatStr.size() && FormatStr[i] == '%') {
+      Output.push_back('%');
+      ++i;
+      continue;
+    }
+
+    while (i < FormatStr.size() && strchr("-= #0123456789", FormatStr[i])) {
+      ++i;
+    }
+
+    while (i < FormatStr.size() && strchr("hljzt", 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(std::remove_if(CleanChunk.begin(), CleanChunk.end(),
+                                    [](char c) { return strchr("hljzt", 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();
+    }
+
+    char Buf[1024];
+    switch (Specifier) {
+    case 'd':
+    case 'i': {
+      std::string HostFmt = CleanChunk + "ll" + Specifier;
+      snprintf(Buf, sizeof(Buf), HostFmt.c_str(),
+               static_cast<long long>(Arg.asInteger().getSExtValue()));
+      Output += Buf;
+      break;
+    }
+    case 'u':
+    case 'o':
+    case 'x':
+    case 'X':
+    case 'c': {
+      std::string HostFmt = CleanChunk + "ll" + Specifier;
+      snprintf(Buf, sizeof(Buf), HostFmt.c_str(),
+               static_cast<unsigned long long>(Arg.asInteger().getZExtValue()));
+      Output += Buf;
+      break;
+    }
+    case 'f':
+    case 'e':
+    case 'E':
+    case 'g':
+    case 'G': {
+      std::string HostFmt = CleanChunk + Specifier;
+      snprintf(Buf, sizeof(Buf), HostFmt.c_str(),
+               Arg.asFloat().convertToDouble());
+      Output += Buf;
+      break;
+    }
+    case 'p': {
+      std::string HostFmt = CleanChunk + "llx";
+      snprintf(Buf, sizeof(Buf), HostFmt.c_str(),
+               static_cast<unsigned long long>(
+                   Arg.asPointer().address().getZExtValue()));
+      Output += "0x";
+      Output += Buf;
+      break;
+    }
+    case 's': {
+      auto StrOpt = readStringFromMemory(Arg.asPointer());
+      if (!StrOpt)
+        return AnyValue::poison();
+      std::string HostFmt = CleanChunk + "s";
+      snprintf(Buf, sizeof(Buf), HostFmt.c_str(), StrOpt->c_str());
+      Output += Buf;
+      break;
+    }
+    default:
+      Executor.reportImmediateUB("Unknown format specifier in printf.");
+      return AnyValue::poison();
+    }
+  }
+
+  Handler.onPrint(Output);
+  return AnyValue(APInt(32, Output.size()));
+}
+
+AnyValue Library::executeExit(StringRef Name, Type *Type,
+                              ArrayRef<AnyValue> Args) {
+  const auto &RetCodeVal = Args[0];
+
+  if (RetCodeVal.isPoison()) {
+    Executor.reportImmediateUB("exit() called with a poison exit code.");
+    return AnyValue::poison();
+  }
+
+  Executor.requestProgramExit(ProgramExitInfo::ProgramExitKind::Exited,
+                              RetCodeVal.asInteger().getZExtValue());
+  return AnyValue();
+}
+
+AnyValue Library::executeAbort(StringRef Name, Type *Type,
+                               ArrayRef<AnyValue> Args) {
+  Executor.requestProgramExit(ProgramExitInfo::ProgramExitKind::Aborted);
+  return AnyValue();
+}
+
+AnyValue Library::executeTerminate(StringRef Name, Type *Type,
+                                   ArrayRef<AnyValue> Args) {
+  Executor.requestProgramExit(ProgramExitInfo::ProgramExitKind::Terminated);
+  return AnyValue();
+}
+
+std::optional<AnyValue> Library::executeLibcall(LibFunc LF, StringRef Name,
+                                                Type *Type,
+                                                ArrayRef<AnyValue> Args) {
+  switch (LF) {
+  case LibFunc_malloc:
+  case LibFunc_Znwm:
+  case LibFunc_Znam:
+    return executeMalloc(Name, Type, Args);
+
+  case LibFunc_calloc:
+    return executeCalloc(Name, Type, Args);
+
+  case LibFunc_free:
+  case LibFunc_ZdaPv:
+  case LibFunc_ZdlPv:
+    return executeFree(Name, Type, Args);
+
+  case LibFunc_puts:
+    return executePuts(Name, Type, Args);
+
+  case LibFunc_printf:
+    return executePrintf(Name, Type, Args);
+
+  case LibFunc_exit:
+    return executeExit(Name, Type, Args);
+
+  case LibFunc_abort:
+    return executeAbort(Name, Type, Args);
+
+  case LibFunc_terminate:
+    return executeTerminate(Name, Type, Args);
+
+  default:
+    return std::nullopt;
+  }
+}
+} // namespace llvm::ubi
\ No newline at end of file
diff --git a/llvm/tools/llubi/lib/Library.h b/llvm/tools/llubi/lib/Library.h
new file mode 100644
index 0000000000000..765c5f56616b0
--- /dev/null
+++ b/llvm/tools/llubi/lib/Library.h
@@ -0,0 +1,55 @@
+//===--- 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 implements 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);
+  AnyValue executeCalloc(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
+  AnyValue executeFree(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
+  AnyValue executePuts(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
+  AnyValue executePrintf(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
+  AnyValue executeExit(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
+  AnyValue executeAbort(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
+  AnyValue executeTerminate(StringRef Name, Type *Type,
+                            ArrayRef<AnyValue> Args);
+
+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
\ No newline at end of file
diff --git a/llvm/tools/llubi/llubi.cpp b/llvm/tools/llubi/llubi.cpp
index de76a7e64c27b..929489dab23b4 100644
--- a/llvm/tools/llubi/llubi.cpp
+++ b/llvm/tools/llubi/llubi.cpp
@@ -131,6 +131,26 @@ class VerboseEventHandler : public ubi::EventHandler {
     return true;
   }
 
+  bool onProgramExit(const ubi::ProgramExitInfo &Info) override {
+    switch (Info.Kind) {
+    case ubi::ProgramExitInfo::ProgramExitKind::Returned:
+      return true;
+    case ubi::ProgramExitInfo::ProgramExitKind::Failed:
+      return true;
+    case ubi::ProgramExitInfo::ProgramExitKind::Exited:
+      errs() << "Program exited with code " << Info.ExitCode << '\n';
+      return true;
+    case ubi::ProgramExitInfo::ProgramExitKind::Aborted:
+      errs() << "Program aborted.\n";
+      return true;
+    case ubi::ProgramExitInfo::ProgramExitKind::Terminated:
+      errs() << "Program terminated.\n";
+      return true;
+    default:
+      llvm_unreachable("Unknown ProgramExitKind");
+    }
+  }
+
   void onUnrecognizedInstruction(Instruction &I) override {
     errs() << "Unrecognized instruction: " << I << '\n';
   }
@@ -240,11 +260,23 @@ int main(int argc, char **argv) {
   ubi::EventHandler NoopHandler;
   VerboseEventHandler VerboseHandler;
   ubi::AnyValue RetVal;
+  ubi::ProgramExitInfo ExitInfo;
   if (!Ctx.runFunction(*EntryFn, Args, RetVal,
-                       Verbose ? VerboseHandler : NoopHandler)) {
-    WithColor::error() << "Execution of function '" << EntryFunc
-                       << "' failed.\n";
-    return 1;
+                       Verbose ? VerboseHandler : NoopHandler, ExitInfo)) {
+    if (!ExitInfo.isExitedByLibcall()) {
+      WithColor::error() << "Execution of function '" << EntryFunc
+                         << "' failed.\n";
+      return 1;
+    }
+    switch (ExitInfo.Kind) {
+    case ubi::ProgramExitInfo::ProgramExitKind::Exited:
+      return static_cast<int>(ExitInfo.ExitCode & 0xFF);
+    case ubi::ProgramExitInfo::ProgramExitKind::Aborted:
+    case ubi::ProgramExitInfo::ProgramExitKind::Terminated:
+      return 1;
+    default:
+      llvm_unreachable("Unexpected returned kind for ProgramExited status");
+    }
   }
 
   // If the function returns an integer, return that as the exit code.
@@ -260,4 +292,4 @@ int main(int argc, char **argv) {
         std::min(Result.getBitWidth(), 8U), 0);
   }
   return 0;
-}
+}
\ No newline at end of file

>From cc0625c7d0592d6f144cc1de44340204089739c5 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 2 Apr 2026 18:45:01 +0800
Subject: [PATCH 02/21] [llubi] Small format fix to ExecutorBase.cpp

---
 llvm/tools/llubi/lib/ExecutorBase.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index d546c80e17aad..8b86d7037926d 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -126,7 +126,7 @@ void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
 }
 
 void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
-uint64_t ExitCode)  {
+                                      uint64_t ExitCode) {
   if (Kind == ProgramExitInfo::ProgramExitKind::Invalid)
     llvm_unreachable("Invalid program exit kind");
   Status = false;

>From bb355c303b8cf08d4491bc0509b97de90e993bfa Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Fri, 3 Apr 2026 14:32:26 +0800
Subject: [PATCH 03/21] [llubi] Format fixes

---
 llvm/tools/llubi/lib/Context.h        |  2 +-
 llvm/tools/llubi/lib/ExecutorBase.cpp |  2 +-
 llvm/tools/llubi/lib/ExecutorBase.h   |  2 +-
 llvm/tools/llubi/lib/Interpreter.cpp  | 29 +++++++++++++--------------
 llvm/tools/llubi/lib/Library.cpp      |  2 +-
 llvm/tools/llubi/lib/Library.h        |  2 +-
 llvm/tools/llubi/llubi.cpp            |  2 +-
 7 files changed, 20 insertions(+), 21 deletions(-)

diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 06aff25f8e46d..0b848bb548c2d 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -297,4 +297,4 @@ class Context {
 
 } // namespace llvm::ubi
 
-#endif
\ No newline at end of file
+#endif
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index 8b86d7037926d..e340ac8c1e1f4 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -138,4 +138,4 @@ void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
 bool ExecutorBase::getExecutionStatus() const { return Status; }
 
 ProgramExitInfo ExecutorBase::getExitInfo() const { return ExitInfo; }
-} // namespace llvm::ubi
\ No newline at end of file
+} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index e400fb95d02f7..b5db5cd6fea44 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -107,4 +107,4 @@ class ExecutorBase {
 
 } // namespace llvm::ubi
 
-#endif // LLVM_TOOLS_LLUBI_EXECUTORBASE_H
\ No newline at end of file
+#endif // LLVM_TOOLS_LLUBI_EXECUTORBASE_H
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index c72ca1d0842b4..9b4ea25982904 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -273,21 +273,20 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
         requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
   }
 
-  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;
-      }
+  void visitUncondBrInst(UncondBrInst &BI) { jumpTo(BI, BI.getSuccessor()); }
+
+  void visitCondBrInst(CondBrInst &BI) {
+    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) {
@@ -1056,4 +1055,4 @@ bool Context::runFunction(Function &F, ArrayRef<AnyValue> Args,
   return Result;
 }
 
-} // namespace llvm::ubi
\ No newline at end of file
+} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index 8f79d14671250..5e68563dfdd13 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -345,4 +345,4 @@ std::optional<AnyValue> Library::executeLibcall(LibFunc LF, StringRef Name,
     return std::nullopt;
   }
 }
-} // namespace llvm::ubi
\ No newline at end of file
+} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/Library.h b/llvm/tools/llubi/lib/Library.h
index 765c5f56616b0..c4589c60f500a 100644
--- a/llvm/tools/llubi/lib/Library.h
+++ b/llvm/tools/llubi/lib/Library.h
@@ -52,4 +52,4 @@ class Library {
 
 } // namespace llvm::ubi
 
-#endif // LLVM_TOOLS_LLUBI_LIBRARY_H
\ No newline at end of file
+#endif // LLVM_TOOLS_LLUBI_LIBRARY_H
diff --git a/llvm/tools/llubi/llubi.cpp b/llvm/tools/llubi/llubi.cpp
index 929489dab23b4..88c5fe1cdc2e4 100644
--- a/llvm/tools/llubi/llubi.cpp
+++ b/llvm/tools/llubi/llubi.cpp
@@ -292,4 +292,4 @@ int main(int argc, char **argv) {
         std::min(Result.getBitWidth(), 8U), 0);
   }
   return 0;
-}
\ No newline at end of file
+}

>From 2202107236064321b9fddac18d7186dcf6324123 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Fri, 3 Apr 2026 14:38:05 +0800
Subject: [PATCH 04/21] [llubi] Small fixes to libcalls

---
 llvm/tools/llubi/lib/Interpreter.cpp |  6 +--
 llvm/tools/llubi/lib/Library.cpp     | 60 ++++++++++++----------------
 llvm/tools/llubi/lib/Library.h       |  2 +-
 3 files changed, 29 insertions(+), 39 deletions(-)

diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 9b4ea25982904..1c90ceb02b3b4 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -69,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))
@@ -259,7 +260,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());
   }
@@ -398,8 +400,6 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       return AnyValue();
     }
 
-    Library Lib(Ctx, Handler, DL, static_cast<ExecutorBase &>(*this));
-
     SmallVector<AnyValue, 8> Args;
     for (const auto &Arg : CB.args()) {
       Args.push_back(getValue(Arg));
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index 5e68563dfdd13..db778ac8022ab 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -16,7 +16,7 @@
 
 namespace llvm::ubi {
 
-static uint64_t getMaxAlignT(const DataLayout &DL) {
+static uint64_t getMaxAlign(const DataLayout &DL) {
   return DL.getPointerABIAlignment(0).value() >= 8 ? 16 : 8;
 }
 
@@ -41,9 +41,8 @@ std::optional<std::string> Library::readStringFromMemory(const Pointer &Ptr) {
     auto ValidOffset = Executor.verifyMemAccess(
         *MO, APInt(DL.getPointerSizeInBits(0), Address + Offset), 1, Align(1),
         false);
-    if (!ValidOffset) {
+    if (!ValidOffset)
       return std::nullopt;
-    }
 
     Byte B = (*MO)[*ValidOffset];
     if (B.ConcreteMask != 0xFF) {
@@ -52,9 +51,8 @@ std::optional<std::string> Library::readStringFromMemory(const Pointer &Ptr) {
       return std::nullopt;
     }
 
-    if (B.Value == 0) {
+    if (B.Value == 0)
       break;
-    }
 
     Result.push_back(static_cast<char>(B.Value));
     ++Offset;
@@ -72,7 +70,7 @@ AnyValue Library::executeMalloc(StringRef Name, Type *Type,
   }
 
   const uint64_t AllocSize = SizeVal.asInteger().getZExtValue();
-  const uint64_t MaxAlign = getMaxAlignT(DL);
+  const uint64_t MaxAlign = getMaxAlign(DL);
 
   const auto Obj =
       Ctx.allocate(AllocSize, MaxAlign, Name, 0, MemInitKind::Uninitialized);
@@ -102,19 +100,16 @@ AnyValue Library::executeCalloc(StringRef Name, Type *Type,
 
   bool Overflow;
   const uint64_t AllocSize = SaturatingMultiply(Count, Size, &Overflow);
-  if (Overflow) {
+  if (Overflow)
     return AnyValue::getNullValue(Ctx, Type);
-  }
 
-  const uint64_t MaxAlign = getMaxAlignT(DL);
+  const uint64_t MaxAlign = getMaxAlign(DL);
 
-  // TODO: Figure out how to name the allocation
   const auto Obj =
       Ctx.allocate(AllocSize, MaxAlign, Name, 0, MemInitKind::Zeroed);
 
-  if (!Obj) {
+  if (!Obj)
     return AnyValue::getNullValue(Ctx, Type);
-  }
 
   return Ctx.deriveFromMemoryObject(Obj);
 }
@@ -128,10 +123,9 @@ AnyValue Library::executeFree(StringRef Name, Type *Type,
   }
 
   auto &Ptr = PtrVal.asPointer();
-  if (Ptr.address().isZero()) {
-    // no-op when free is called with a null pointer.
+  // no-op when free is called with a null pointer.
+  if (Ptr.address().isZero())
     return AnyValue();
-  }
 
   if (!Ctx.free(Ptr.address().getZExtValue())) {
     Executor.reportImmediateUB(
@@ -151,9 +145,8 @@ AnyValue Library::executePuts(StringRef Name, Type *Type,
   }
 
   const auto StrOpt = readStringFromMemory(PtrVal.asPointer());
-  if (!StrOpt) {
+  if (!StrOpt)
     return AnyValue::poison();
-  }
 
   Handler.onPrint(*StrOpt + "\n");
   return AnyValue(APInt(32, 1));
@@ -169,44 +162,41 @@ AnyValue Library::executePrintf(StringRef Name, Type *Type,
   }
 
   const auto FormatStrOpt = readStringFromMemory(FormatPtrVal.asPointer());
-  if (!FormatStrOpt) {
+  if (!FormatStrOpt)
     return AnyValue::poison();
-  }
 
-  const std::string FormatStr = *FormatStrOpt;
+  const std::string &FormatStr = *FormatStrOpt;
   std::string Output;
   unsigned ArgIndex = 1; // Start from 1 since 0 is the format string.
 
-  for (size_t i = 0; i < FormatStr.size();) {
-    if (FormatStr[i] != '%') {
-      Output.push_back(FormatStr[i++]);
+  for (unsigned I = 0; I < FormatStr.size(); ) {
+    if (FormatStr[I] != '%') {
+      Output.push_back(FormatStr[I++]);
       continue;
     }
 
-    const size_t Start = i++;
-    if (i < FormatStr.size() && FormatStr[i] == '%') {
+    const size_t Start = I++;
+    if (I < FormatStr.size() && FormatStr[I] == '%') {
       Output.push_back('%');
-      ++i;
+      ++I;
       continue;
     }
 
-    while (i < FormatStr.size() && strchr("-= #0123456789", FormatStr[i])) {
-      ++i;
-    }
+    while (I < FormatStr.size() && strchr("-= #0123456789", FormatStr[I]))
+      ++I;
 
-    while (i < FormatStr.size() && strchr("hljzt", FormatStr[i])) {
-      ++i;
-    }
+    while (I < FormatStr.size() && strchr("hljzt", FormatStr[I]))
+      ++I;
 
-    if (i >= FormatStr.size()) {
+    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);
+    char Specifier = FormatStr[I++];
+    std::string CleanChunk = FormatStr.substr(Start, I - Start - 1);
     CleanChunk.erase(std::remove_if(CleanChunk.begin(), CleanChunk.end(),
                                     [](char c) { return strchr("hljzt", c); }),
                      CleanChunk.end());
diff --git a/llvm/tools/llubi/lib/Library.h b/llvm/tools/llubi/lib/Library.h
index c4589c60f500a..3ff880324b5a7 100644
--- a/llvm/tools/llubi/lib/Library.h
+++ b/llvm/tools/llubi/lib/Library.h
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 //
-// This file implements common libcalls for llubi.
+// This file declares common libcalls for llubi.
 //
 //===----------------------------------------------------------------------===//
 

>From bea3e3e7a4b73a1bde01e1bcadbbd992af201e7a Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Fri, 3 Apr 2026 14:50:53 +0800
Subject: [PATCH 05/21] [llubi] Small fixes to libcalls

---
 llvm/tools/llubi/lib/Interpreter.cpp | 12 ++---
 llvm/tools/llubi/lib/Library.cpp     | 74 +++++++++++-----------------
 2 files changed, 33 insertions(+), 53 deletions(-)

diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 1c90ceb02b3b4..24e1e2cdb0136 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -390,7 +390,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     }
   }
 
-  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() ||
@@ -400,13 +401,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       return AnyValue();
     }
 
-    SmallVector<AnyValue, 8> Args;
-    for (const auto &Arg : CB.args()) {
-      Args.push_back(getValue(Arg));
-    }
-
     if (auto LibCallRes =
-            Lib.executeLibcall(LF, CB.getName(), CB.getType(), Args))
+            Lib.executeLibcall(LF, CB.getName(), CB.getType(), CalleeArgs))
       return *LibCallRes;
 
     if (ExitInfo)
@@ -469,7 +465,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 {
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index db778ac8022ab..5011d01f9335d 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -64,10 +64,6 @@ std::optional<std::string> Library::readStringFromMemory(const Pointer &Ptr) {
 AnyValue Library::executeMalloc(StringRef Name, Type *Type,
                                 ArrayRef<AnyValue> Args) {
   const auto &SizeVal = Args[0];
-  if (SizeVal.isPoison()) {
-    Executor.reportImmediateUB("malloc() called with a poison size.");
-    return AnyValue::poison();
-  }
 
   const uint64_t AllocSize = SizeVal.asInteger().getZExtValue();
   const uint64_t MaxAlign = getMaxAlign(DL);
@@ -86,15 +82,6 @@ AnyValue Library::executeCalloc(StringRef Name, Type *Type,
   const auto &CountVal = Args[0];
   const auto &SizeVal = Args[1];
 
-  if (CountVal.isPoison()) {
-    Executor.reportImmediateUB("calloc() called with a poison count.");
-    return AnyValue::poison();
-  }
-  if (SizeVal.isPoison()) {
-    Executor.reportImmediateUB("calloc() called with a poison size.");
-    return AnyValue::poison();
-  }
-
   const uint64_t Count = CountVal.asInteger().getZExtValue();
   const uint64_t Size = SizeVal.asInteger().getZExtValue();
 
@@ -114,13 +101,10 @@ AnyValue Library::executeCalloc(StringRef Name, Type *Type,
   return Ctx.deriveFromMemoryObject(Obj);
 }
 
-AnyValue Library::executeFree(StringRef Name, Type *Type,
+AnyValue Library::executeFree([[maybe_unused]] StringRef Name,
+                              [[maybe_unused]] Type *Type,
                               ArrayRef<AnyValue> Args) {
   const auto &PtrVal = Args[0];
-  if (PtrVal.isPoison()) {
-    Executor.reportImmediateUB("free() called with a poison pointer.");
-    return AnyValue::poison();
-  }
 
   auto &Ptr = PtrVal.asPointer();
   // no-op when free is called with a null pointer.
@@ -136,13 +120,10 @@ AnyValue Library::executeFree(StringRef Name, Type *Type,
   return AnyValue();
 }
 
-AnyValue Library::executePuts(StringRef Name, Type *Type,
+AnyValue Library::executePuts([[maybe_unused]] StringRef Name,
+                              [[maybe_unused]] Type *Type,
                               ArrayRef<AnyValue> Args) {
   const auto &PtrVal = Args[0];
-  if (PtrVal.isPoison()) {
-    Executor.reportImmediateUB("puts called with a poison pointer.");
-    return AnyValue::poison();
-  }
 
   const auto StrOpt = readStringFromMemory(PtrVal.asPointer());
   if (!StrOpt)
@@ -152,14 +133,10 @@ AnyValue Library::executePuts(StringRef Name, Type *Type,
   return AnyValue(APInt(32, 1));
 }
 
-AnyValue Library::executePrintf(StringRef Name, Type *Type,
+AnyValue Library::executePrintf([[maybe_unused]] StringRef Name,
+                                [[maybe_unused]] Type *Type,
                                 ArrayRef<AnyValue> Args) {
   const auto &FormatPtrVal = Args[0];
-  if (FormatPtrVal.isPoison()) {
-    Executor.reportImmediateUB(
-        "printf called with a poison format string pointer.");
-    return AnyValue::poison();
-  }
 
   const auto FormatStrOpt = readStringFromMemory(FormatPtrVal.asPointer());
   if (!FormatStrOpt)
@@ -169,7 +146,7 @@ AnyValue Library::executePrintf(StringRef Name, Type *Type,
   std::string Output;
   unsigned ArgIndex = 1; // Start from 1 since 0 is the format string.
 
-  for (unsigned I = 0; I < FormatStr.size(); ) {
+  for (unsigned I = 0; I < FormatStr.size();) {
     if (FormatStr[I] != '%') {
       Output.push_back(FormatStr[I++]);
       continue;
@@ -182,10 +159,11 @@ AnyValue Library::executePrintf(StringRef Name, Type *Type,
       continue;
     }
 
-    while (I < FormatStr.size() && strchr("-= #0123456789", FormatStr[I]))
+    while (I < FormatStr.size() &&
+           StringRef("-= #0123456789").contains(FormatStr[I]))
       ++I;
 
-    while (I < FormatStr.size() && strchr("hljzt", FormatStr[I]))
+    while (I < FormatStr.size() && StringRef("hljzt").contains(FormatStr[I]))
       ++I;
 
     if (I >= FormatStr.size()) {
@@ -197,9 +175,10 @@ AnyValue Library::executePrintf(StringRef Name, Type *Type,
 
     char Specifier = FormatStr[I++];
     std::string CleanChunk = FormatStr.substr(Start, I - Start - 1);
-    CleanChunk.erase(std::remove_if(CleanChunk.begin(), CleanChunk.end(),
-                                    [](char c) { return strchr("hljzt", c); }),
-                     CleanChunk.end());
+    CleanChunk.erase(
+        std::remove_if(CleanChunk.begin(), CleanChunk.end(),
+                       [](char c) { return StringRef("hljzt").contains(c); }),
+        CleanChunk.end());
 
     if (ArgIndex >= Args.size()) {
       Executor.reportImmediateUB(
@@ -273,28 +252,26 @@ AnyValue Library::executePrintf(StringRef Name, Type *Type,
   return AnyValue(APInt(32, Output.size()));
 }
 
-AnyValue Library::executeExit(StringRef Name, Type *Type,
+AnyValue Library::executeExit([[maybe_unused]] StringRef Name,
+                              [[maybe_unused]] Type *Type,
                               ArrayRef<AnyValue> Args) {
   const auto &RetCodeVal = Args[0];
 
-  if (RetCodeVal.isPoison()) {
-    Executor.reportImmediateUB("exit() called with a poison exit code.");
-    return AnyValue::poison();
-  }
-
   Executor.requestProgramExit(ProgramExitInfo::ProgramExitKind::Exited,
                               RetCodeVal.asInteger().getZExtValue());
   return AnyValue();
 }
 
-AnyValue Library::executeAbort(StringRef Name, Type *Type,
-                               ArrayRef<AnyValue> Args) {
+AnyValue Library::executeAbort([[maybe_unused]] StringRef Name,
+                               [[maybe_unused]] Type *Type,
+                               [[maybe_unused]] ArrayRef<AnyValue> Args) {
   Executor.requestProgramExit(ProgramExitInfo::ProgramExitKind::Aborted);
   return AnyValue();
 }
 
-AnyValue Library::executeTerminate(StringRef Name, Type *Type,
-                                   ArrayRef<AnyValue> Args) {
+AnyValue Library::executeTerminate([[maybe_unused]] StringRef Name,
+                                   [[maybe_unused]] Type *Type,
+                                   [[maybe_unused]] ArrayRef<AnyValue> Args) {
   Executor.requestProgramExit(ProgramExitInfo::ProgramExitKind::Terminated);
   return AnyValue();
 }
@@ -302,6 +279,13 @@ AnyValue Library::executeTerminate(StringRef Name, Type *Type,
 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:
   case LibFunc_Znwm:

>From e77b9b03c12e7de1d8be7dadbeb7ec7e300ebded Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Fri, 3 Apr 2026 15:02:41 +0800
Subject: [PATCH 06/21] [llubi] Two new test cases

---
 llvm/test/tools/llubi/lib_nobuiltin.ll           | 13 +++++++++++++
 llvm/test/tools/llubi/lib_read_nullary_string.ll | 16 ++++++++++++++++
 llvm/tools/llubi/lib/Library.cpp                 |  7 ++++---
 3 files changed, 33 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/tools/llubi/lib_nobuiltin.ll
 create mode 100644 llvm/test/tools/llubi/lib_read_nullary_string.ll

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_read_nullary_string.ll b/llvm/test/tools/llubi/lib_read_nullary_string.ll
new file mode 100644
index 0000000000000..a9d489d6bc55b
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_read_nullary_string.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
+
+declare i32 @puts(ptr)
+
+define i32 @main() {
+  %puts.str = inttoptr i64 0 to ptr
+
+  %1 = call i32 @puts(ptr %puts.str)
+
+  ret i32 0
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %puts.str = inttoptr i64 0 to ptr => ptr 0x0 [dangling]
+; 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/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index 5011d01f9335d..29597cce4f202 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -17,6 +17,7 @@
 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;
 }
 
@@ -65,7 +66,7 @@ AnyValue Library::executeMalloc(StringRef Name, Type *Type,
                                 ArrayRef<AnyValue> Args) {
   const auto &SizeVal = Args[0];
 
-  const uint64_t AllocSize = SizeVal.asInteger().getZExtValue();
+  const uint64_t AllocSize = SizeVal.asInteger().getLimitedValue();
   const uint64_t MaxAlign = getMaxAlign(DL);
 
   const auto Obj =
@@ -82,8 +83,8 @@ AnyValue Library::executeCalloc(StringRef Name, Type *Type,
   const auto &CountVal = Args[0];
   const auto &SizeVal = Args[1];
 
-  const uint64_t Count = CountVal.asInteger().getZExtValue();
-  const uint64_t Size = SizeVal.asInteger().getZExtValue();
+  const uint64_t Count = CountVal.asInteger().getLimitedValue();
+  const uint64_t Size = SizeVal.asInteger().getLimitedValue();
 
   bool Overflow;
   const uint64_t AllocSize = SaturatingMultiply(Count, Size, &Overflow);

>From 4ddffcbc0a4efdc12f96689a9fd451ef60d3a358 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Fri, 3 Apr 2026 15:07:12 +0800
Subject: [PATCH 07/21] [llubi] Small fixes to libcalls

---
 llvm/tools/llubi/lib/Library.cpp | 36 ++++++++++++++------------------
 1 file changed, 16 insertions(+), 20 deletions(-)

diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index 29597cce4f202..e81c2aac25862 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -13,6 +13,8 @@
 #include "Library.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 {
 
@@ -86,7 +88,7 @@ AnyValue Library::executeCalloc(StringRef Name, Type *Type,
   const uint64_t Count = CountVal.asInteger().getLimitedValue();
   const uint64_t Size = SizeVal.asInteger().getLimitedValue();
 
-  bool Overflow;
+  bool Overflow = false;
   const uint64_t AllocSize = SaturatingMultiply(Count, Size, &Overflow);
   if (Overflow)
     return AnyValue::getNullValue(Ctx, Type);
@@ -145,17 +147,18 @@ AnyValue Library::executePrintf([[maybe_unused]] StringRef Name,
 
   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] != '%') {
-      Output.push_back(FormatStr[I++]);
+      OS << FormatStr[I++];
       continue;
     }
 
     const size_t Start = I++;
     if (I < FormatStr.size() && FormatStr[I] == '%') {
-      Output.push_back('%');
+      OS << '%';
       ++I;
       continue;
     }
@@ -193,14 +196,12 @@ AnyValue Library::executePrintf([[maybe_unused]] StringRef Name,
       return AnyValue::poison();
     }
 
-    char Buf[1024];
     switch (Specifier) {
     case 'd':
     case 'i': {
       std::string HostFmt = CleanChunk + "ll" + Specifier;
-      snprintf(Buf, sizeof(Buf), HostFmt.c_str(),
-               static_cast<long long>(Arg.asInteger().getSExtValue()));
-      Output += Buf;
+      OS << format(HostFmt.c_str(),
+                   static_cast<long long>(Arg.asInteger().getSExtValue()));
       break;
     }
     case 'u':
@@ -209,9 +210,8 @@ AnyValue Library::executePrintf([[maybe_unused]] StringRef Name,
     case 'X':
     case 'c': {
       std::string HostFmt = CleanChunk + "ll" + Specifier;
-      snprintf(Buf, sizeof(Buf), HostFmt.c_str(),
-               static_cast<unsigned long long>(Arg.asInteger().getZExtValue()));
-      Output += Buf;
+      OS << format(HostFmt.c_str(),
+                   static_cast<unsigned long long>(Arg.asInteger().getZExtValue()));
       break;
     }
     case 'f':
@@ -220,18 +220,14 @@ AnyValue Library::executePrintf([[maybe_unused]] StringRef Name,
     case 'g':
     case 'G': {
       std::string HostFmt = CleanChunk + Specifier;
-      snprintf(Buf, sizeof(Buf), HostFmt.c_str(),
-               Arg.asFloat().convertToDouble());
-      Output += Buf;
+      OS << format(HostFmt.c_str(), Arg.asFloat().convertToDouble());
       break;
     }
     case 'p': {
       std::string HostFmt = CleanChunk + "llx";
-      snprintf(Buf, sizeof(Buf), HostFmt.c_str(),
-               static_cast<unsigned long long>(
-                   Arg.asPointer().address().getZExtValue()));
-      Output += "0x";
-      Output += Buf;
+      OS << "0x" << format(HostFmt.c_str(),
+                           static_cast<unsigned long long>(
+                               Arg.asPointer().address().getZExtValue()));
       break;
     }
     case 's': {
@@ -239,8 +235,7 @@ AnyValue Library::executePrintf([[maybe_unused]] StringRef Name,
       if (!StrOpt)
         return AnyValue::poison();
       std::string HostFmt = CleanChunk + "s";
-      snprintf(Buf, sizeof(Buf), HostFmt.c_str(), StrOpt->c_str());
-      Output += Buf;
+      OS << format(HostFmt.c_str(), StrOpt->c_str());
       break;
     }
     default:
@@ -249,6 +244,7 @@ AnyValue Library::executePrintf([[maybe_unused]] StringRef Name,
     }
   }
 
+  OS.flush();
   Handler.onPrint(Output);
   return AnyValue(APInt(32, Output.size()));
 }

>From ad00984173afd312bd114a26660068e3fde9eab3 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Fri, 3 Apr 2026 15:10:11 +0800
Subject: [PATCH 08/21] [llubi] Format fix

---
 llvm/tools/llubi/lib/Library.cpp | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index e81c2aac25862..085bc1b34bc2c 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -210,8 +210,8 @@ AnyValue Library::executePrintf([[maybe_unused]] StringRef Name,
     case 'X':
     case 'c': {
       std::string HostFmt = CleanChunk + "ll" + Specifier;
-      OS << format(HostFmt.c_str(),
-                   static_cast<unsigned long long>(Arg.asInteger().getZExtValue()));
+      OS << format(HostFmt.c_str(), static_cast<unsigned long long>(
+                                        Arg.asInteger().getZExtValue()));
       break;
     }
     case 'f':
@@ -225,9 +225,10 @@ AnyValue Library::executePrintf([[maybe_unused]] StringRef Name,
     }
     case 'p': {
       std::string HostFmt = CleanChunk + "llx";
-      OS << "0x" << format(HostFmt.c_str(),
-                           static_cast<unsigned long long>(
-                               Arg.asPointer().address().getZExtValue()));
+      OS << "0x"
+         << format(HostFmt.c_str(),
+                   static_cast<unsigned long long>(
+                       Arg.asPointer().address().getZExtValue()));
       break;
     }
     case 's': {

>From 56a7f683560659eb52bcd0e979a0fe056ffba1ef Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Fri, 3 Apr 2026 16:06:08 +0800
Subject: [PATCH 09/21] [llubi] Add support for format specifiers b, B, a, A, n

---
 llvm/test/tools/llubi/lib_printf_format.ll | 49 ++++++++++++++++------
 llvm/tools/llubi/lib/Library.cpp           | 12 +++++-
 2 files changed, 47 insertions(+), 14 deletions(-)

diff --git a/llvm/test/tools/llubi/lib_printf_format.ll b/llvm/test/tools/llubi/lib_printf_format.ll
index 24cc5f2bd2b40..09061bb4761b4 100644
--- a/llvm/test/tools/llubi/lib_printf_format.ll
+++ b/llvm/test/tools/llubi/lib_printf_format.ll
@@ -5,8 +5,8 @@ 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_int = alloca [44 x i8]
+  store [44 x i8] c"Ints: %d, %i, %u, %o, %x, %X, %b, %B, %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
@@ -23,36 +23,59 @@ entry:
   %fmt_float = alloca [20 x i8]
   store [20 x i8] c"Floats: %f, %e, %g\0A\00", ptr %fmt_float
 
-  call i32 (ptr, ...) @printf(ptr %fmt_int, i32 42, i32 -42, i32 255, i32 255, i32 255, i32 255, i32 42)
+  %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 10, i32 10, 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:   %fmt_int = alloca [44 x i8], align 1 => ptr 0x8 [fmt_int]
+; CHECK-NEXT:   store [44 x i8] c"Ints: %d, %i, %u, %o, %x, %X, %b, %B, %05d\0A\00", ptr %fmt_int, align 1
+; CHECK-NEXT:   %fmt_len = alloca [35 x i8], align 1 => ptr 0x34 [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:   %fmt_str_ptr = alloca [18 x i8], align 1 => ptr 0x57 [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:   %fmt_pct = alloca [15 x i8], align 1 => ptr 0x69 [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:   %dummy_str = alloca [6 x i8], align 1 => ptr 0x78 [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:   %fmt_float = alloca [20 x i8], align 1 => ptr 0x7E [fmt_float]
 ; CHECK-NEXT:   store [20 x i8] c"Floats: %f, %e, %g\0A\00", ptr %fmt_float, align 1
-; 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:   %fmt_n = alloca [15 x i8], align 1 => ptr 0x92 [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 0xA1 [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 0xA8 [n_count]
+; CHECK-NEXT:   store i32 0, ptr %n_count, align 4
+; CHECK-NEXT:   %0 = call i32 (ptr, ...) @printf(ptr %fmt_int, i32 42, i32 -42, i32 255, i32 255, i32 255, i32 255, i32 10, i32 10, i32 42) => i32 51
 ; CHECK-NEXT:   %1 = call i32 (ptr, ...) @printf(ptr %fmt_len, i64 123456789, i64 987654321, i32 100, i32 50, i32 65) => i32 42
 ; CHECK-NEXT:   %2 = call i32 (ptr, ...) @printf(ptr %fmt_str_ptr, ptr %dummy_str, ptr %dummy_str) => i32 22
 ; CHECK-NEXT:   %3 = call i32 (ptr, ...) @printf(ptr %fmt_pct, i32 100) => i32 14
 ; 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:   %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:   %6 = call i32 (ptr, ...) @printf(ptr %fmt_n_out, i32 %n_loaded) => i32 4
 ; CHECK-NEXT:   ret i32 0
 ; CHECK-NEXT: Exiting function: main
-; CHECK-NEXT: Ints: 42, -42, 255, 377, ff, FF, 00042
+; CHECK-NEXT: Ints: 42, -42, 255, 377, ff, FF, 1010, 1010, 00042
 ; CHECK-NEXT: Lengths: 123456789, 987654321, 100, 50, A
-; CHECK-NEXT: Str: llubi, Ptr: 0x70
+; CHECK-NEXT: Str: llubi, Ptr: 0x78
 ; CHECK-NEXT: Percent: 100%
 ; CHECK-NEXT: Floats: 3.141590, 3.141590e+00, 3.14159
+; CHECK-NEXT: Count: Done
+; CHECK-NEXT: N=7
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index 085bc1b34bc2c..fa9f558e11b2c 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -208,6 +208,8 @@ AnyValue Library::executePrintf([[maybe_unused]] StringRef Name,
     case 'o':
     case 'x':
     case 'X':
+    case 'b':
+    case 'B':
     case 'c': {
       std::string HostFmt = CleanChunk + "ll" + Specifier;
       OS << format(HostFmt.c_str(), static_cast<unsigned long long>(
@@ -218,11 +220,19 @@ AnyValue Library::executePrintf([[maybe_unused]] StringRef Name,
     case 'e':
     case 'E':
     case 'g':
-    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"

>From f8eff937443127bdcffc7a7f44df6a4ced8f7806 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Mon, 6 Apr 2026 17:36:55 +0800
Subject: [PATCH 10/21] [llubi] Delete unused parameters

---
 llvm/tools/llubi/lib/Context.h   |  2 +-
 llvm/tools/llubi/lib/Library.cpp | 36 +++++++++++---------------------
 llvm/tools/llubi/lib/Library.h   | 13 ++++++------
 3 files changed, 19 insertions(+), 32 deletions(-)

diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 0b848bb548c2d..2caccc3d0f3de 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -287,7 +287,7 @@ 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. The exit information is
-  /// store in \p ExitInfo.
+  /// stored in \p ExitInfo.
   /// Returns true if the function executed successfully without calls to
   /// exit()/abort()/terminate(). False indicates an error occurred during
   /// execution.
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index fa9f558e11b2c..d1debcd788d66 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -104,9 +104,7 @@ AnyValue Library::executeCalloc(StringRef Name, Type *Type,
   return Ctx.deriveFromMemoryObject(Obj);
 }
 
-AnyValue Library::executeFree([[maybe_unused]] StringRef Name,
-                              [[maybe_unused]] Type *Type,
-                              ArrayRef<AnyValue> Args) {
+AnyValue Library::executeFree(ArrayRef<AnyValue> Args) {
   const auto &PtrVal = Args[0];
 
   auto &Ptr = PtrVal.asPointer();
@@ -123,9 +121,7 @@ AnyValue Library::executeFree([[maybe_unused]] StringRef Name,
   return AnyValue();
 }
 
-AnyValue Library::executePuts([[maybe_unused]] StringRef Name,
-                              [[maybe_unused]] Type *Type,
-                              ArrayRef<AnyValue> Args) {
+AnyValue Library::executePuts(ArrayRef<AnyValue> Args) {
   const auto &PtrVal = Args[0];
 
   const auto StrOpt = readStringFromMemory(PtrVal.asPointer());
@@ -136,9 +132,7 @@ AnyValue Library::executePuts([[maybe_unused]] StringRef Name,
   return AnyValue(APInt(32, 1));
 }
 
-AnyValue Library::executePrintf([[maybe_unused]] StringRef Name,
-                                [[maybe_unused]] Type *Type,
-                                ArrayRef<AnyValue> Args) {
+AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
   const auto &FormatPtrVal = Args[0];
 
   const auto FormatStrOpt = readStringFromMemory(FormatPtrVal.asPointer());
@@ -260,9 +254,7 @@ AnyValue Library::executePrintf([[maybe_unused]] StringRef Name,
   return AnyValue(APInt(32, Output.size()));
 }
 
-AnyValue Library::executeExit([[maybe_unused]] StringRef Name,
-                              [[maybe_unused]] Type *Type,
-                              ArrayRef<AnyValue> Args) {
+AnyValue Library::executeExit(ArrayRef<AnyValue> Args) {
   const auto &RetCodeVal = Args[0];
 
   Executor.requestProgramExit(ProgramExitInfo::ProgramExitKind::Exited,
@@ -270,16 +262,12 @@ AnyValue Library::executeExit([[maybe_unused]] StringRef Name,
   return AnyValue();
 }
 
-AnyValue Library::executeAbort([[maybe_unused]] StringRef Name,
-                               [[maybe_unused]] Type *Type,
-                               [[maybe_unused]] ArrayRef<AnyValue> Args) {
+AnyValue Library::executeAbort() {
   Executor.requestProgramExit(ProgramExitInfo::ProgramExitKind::Aborted);
   return AnyValue();
 }
 
-AnyValue Library::executeTerminate([[maybe_unused]] StringRef Name,
-                                   [[maybe_unused]] Type *Type,
-                                   [[maybe_unused]] ArrayRef<AnyValue> Args) {
+AnyValue Library::executeTerminate() {
   Executor.requestProgramExit(ProgramExitInfo::ProgramExitKind::Terminated);
   return AnyValue();
 }
@@ -306,22 +294,22 @@ std::optional<AnyValue> Library::executeLibcall(LibFunc LF, StringRef Name,
   case LibFunc_free:
   case LibFunc_ZdaPv:
   case LibFunc_ZdlPv:
-    return executeFree(Name, Type, Args);
+    return executeFree(Args);
 
   case LibFunc_puts:
-    return executePuts(Name, Type, Args);
+    return executePuts(Args);
 
   case LibFunc_printf:
-    return executePrintf(Name, Type, Args);
+    return executePrintf(Args);
 
   case LibFunc_exit:
-    return executeExit(Name, Type, Args);
+    return executeExit(Args);
 
   case LibFunc_abort:
-    return executeAbort(Name, Type, Args);
+    return executeAbort();
 
   case LibFunc_terminate:
-    return executeTerminate(Name, Type, Args);
+    return executeTerminate();
 
   default:
     return std::nullopt;
diff --git a/llvm/tools/llubi/lib/Library.h b/llvm/tools/llubi/lib/Library.h
index 3ff880324b5a7..1ce13e3bb6f6f 100644
--- a/llvm/tools/llubi/lib/Library.h
+++ b/llvm/tools/llubi/lib/Library.h
@@ -31,13 +31,12 @@ class Library {
 
   AnyValue executeMalloc(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
   AnyValue executeCalloc(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
-  AnyValue executeFree(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
-  AnyValue executePuts(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
-  AnyValue executePrintf(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
-  AnyValue executeExit(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
-  AnyValue executeAbort(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
-  AnyValue executeTerminate(StringRef Name, Type *Type,
-                            ArrayRef<AnyValue> Args);
+  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,

>From 39d0445a99bf62cd77d5f2bf1ea885a1416992a0 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Mon, 6 Apr 2026 23:05:00 +0800
Subject: [PATCH 11/21] [llubi] Remove the implementation of format specifiers
 b/B

---
 llvm/test/tools/llubi/lib_printf_format.ll | 32 +++++++++++-----------
 llvm/tools/llubi/lib/Library.cpp           |  7 +++--
 2 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/llvm/test/tools/llubi/lib_printf_format.ll b/llvm/test/tools/llubi/lib_printf_format.ll
index 09061bb4761b4..8f527ebe314f2 100644
--- a/llvm/test/tools/llubi/lib_printf_format.ll
+++ b/llvm/test/tools/llubi/lib_printf_format.ll
@@ -5,8 +5,8 @@ declare i32 @printf(ptr, ...)
 
 define i32 @main() {
 entry:
-  %fmt_int = alloca [44 x i8]
-  store [44 x i8] c"Ints: %d, %i, %u, %o, %x, %X, %b, %B, %05d\0A\00", ptr %fmt_int
+  %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
@@ -32,7 +32,7 @@ entry:
   %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 10, i32 10, i32 42)
+  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)
@@ -44,25 +44,25 @@ entry:
   ret i32 0
 }
 ; CHECK: Entering function: main
-; CHECK-NEXT:   %fmt_int = alloca [44 x i8], align 1 => ptr 0x8 [fmt_int]
-; CHECK-NEXT:   store [44 x i8] c"Ints: %d, %i, %u, %o, %x, %X, %b, %B, %05d\0A\00", ptr %fmt_int, align 1
-; CHECK-NEXT:   %fmt_len = alloca [35 x i8], align 1 => ptr 0x34 [fmt_len]
+; 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 0x57 [fmt_str_ptr]
+; 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 0x69 [fmt_pct]
+; 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 0x78 [dummy_str]
+; 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 0x7E [fmt_float]
+; 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 0x92 [fmt_n]
+; 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 0xA1 [fmt_n_out]
+; 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 0xA8 [n_count]
+; CHECK-NEXT:   %n_count = alloca i32, align 4 => ptr 0xA0 [n_count]
 ; CHECK-NEXT:   store i32 0, ptr %n_count, align 4
-; CHECK-NEXT:   %0 = call i32 (ptr, ...) @printf(ptr %fmt_int, i32 42, i32 -42, i32 255, i32 255, i32 255, i32 255, i32 10, i32 10, i32 42) => i32 51
+; 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:   %1 = call i32 (ptr, ...) @printf(ptr %fmt_len, i64 123456789, i64 987654321, i32 100, i32 50, i32 65) => i32 42
 ; CHECK-NEXT:   %2 = call i32 (ptr, ...) @printf(ptr %fmt_str_ptr, ptr %dummy_str, ptr %dummy_str) => i32 22
 ; CHECK-NEXT:   %3 = call i32 (ptr, ...) @printf(ptr %fmt_pct, i32 100) => i32 14
@@ -72,9 +72,9 @@ entry:
 ; 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
-; CHECK-NEXT: Ints: 42, -42, 255, 377, ff, FF, 1010, 1010, 00042
+; CHECK-NEXT: Ints: 42, -42, 255, 377, ff, FF, 00042
 ; CHECK-NEXT: Lengths: 123456789, 987654321, 100, 50, A
-; CHECK-NEXT: Str: llubi, Ptr: 0x78
+; CHECK-NEXT: Str: llubi, Ptr: 0x70
 ; CHECK-NEXT: Percent: 100%
 ; CHECK-NEXT: Floats: 3.141590, 3.141590e+00, 3.14159
 ; CHECK-NEXT: Count: Done
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index d1debcd788d66..222e36568a1d0 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -202,9 +202,9 @@ AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
     case 'o':
     case 'x':
     case 'X':
-    case 'b':
-    case 'B':
     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()));
@@ -244,7 +244,8 @@ AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
       break;
     }
     default:
-      Executor.reportImmediateUB("Unknown format specifier in printf.");
+      Executor.reportImmediateUB(
+          "Unknown or unsupported format specifier in printf.");
       return AnyValue::poison();
     }
   }

>From d8b5da0031e3a34730b55449097ac9515ade705d Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Tue, 7 Apr 2026 10:48:02 +0800
Subject: [PATCH 12/21] [llubi] Small fixes to libcalls

---
 llvm/tools/llubi/lib/Context.h        | 16 ++++++++-------
 llvm/tools/llubi/lib/ExecutorBase.cpp |  4 ++++
 llvm/tools/llubi/lib/ExecutorBase.h   |  2 ++
 llvm/tools/llubi/lib/Interpreter.cpp  | 10 ++++-----
 llvm/tools/llubi/lib/Library.cpp      | 29 ++++++++++++---------------
 llvm/tools/llubi/llubi.cpp            |  7 ++++---
 6 files changed, 36 insertions(+), 32 deletions(-)

diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 2caccc3d0f3de..f0883d74bd341 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -286,13 +286,15 @@ class Context {
   /// initialization).
   bool initGlobalValues();
   /// Execute the function \p F with arguments \p Args, and store the return
-  /// value in \p RetVal if the function is not void. The exit information is
-  /// stored in \p ExitInfo.
-  /// Returns true if the function executed successfully without calls to
-  /// exit()/abort()/terminate(). False indicates an error occurred during
-  /// execution.
-  bool runFunction(Function &F, ArrayRef<AnyValue> Args, AnyValue &RetVal,
-                   EventHandler &Handler, ProgramExitInfo &ExitInfo);
+  /// value in \p RetVal if the function is not void.
+  /// 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 e340ac8c1e1f4..762e211fdddb7 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -138,4 +138,8 @@ void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
 bool ExecutorBase::getExecutionStatus() const { return Status; }
 
 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 b5db5cd6fea44..9b151d9586b10 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -103,6 +103,8 @@ class ExecutorBase {
 
   bool getExecutionStatus() const;
   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 24e1e2cdb0136..a7542ec2867fe 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -1042,13 +1042,11 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   }
 };
 
-bool Context::runFunction(Function &F, ArrayRef<AnyValue> Args,
-                          AnyValue &RetVal, EventHandler &Handler,
-                          ProgramExitInfo &ExitInfo) {
+ProgramExitInfo Context::runFunction(Function &F, ArrayRef<AnyValue> Args,
+                                     AnyValue &RetVal, EventHandler &Handler) {
   InstExecutor Executor(*this, Handler, F, Args, RetVal);
-  bool Result = Executor.runMainLoop();
-  ExitInfo = Executor.getExitInfo();
-  return Result;
+  Executor.runMainLoop();
+  return Executor.getExitInfo();
 }
 
 } // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index 222e36568a1d0..fafcd2562bd4d 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -37,13 +37,12 @@ std::optional<std::string> Library::readStringFromMemory(const Pointer &Ptr) {
   }
 
   std::string Result;
-  const uint64_t Address = Ptr.address().getZExtValue();
+  const APInt &Address = Ptr.address();
   uint64_t Offset = 0;
 
   while (true) {
-    auto ValidOffset = Executor.verifyMemAccess(
-        *MO, APInt(DL.getPointerSizeInBits(0), Address + Offset), 1, Align(1),
-        false);
+    auto ValidOffset =
+        Executor.verifyMemAccess(*MO, Address + Offset, 1, Align(1), false);
     if (!ValidOffset)
       return std::nullopt;
 
@@ -69,10 +68,9 @@ AnyValue Library::executeMalloc(StringRef Name, Type *Type,
   const auto &SizeVal = Args[0];
 
   const uint64_t AllocSize = SizeVal.asInteger().getLimitedValue();
-  const uint64_t MaxAlign = getMaxAlign(DL);
 
-  const auto Obj =
-      Ctx.allocate(AllocSize, MaxAlign, Name, 0, MemInitKind::Uninitialized);
+  const IntrusiveRefCntPtr<MemoryObject> Obj = Ctx.allocate(
+      AllocSize, getMaxAlign(DL), Name, 0, MemInitKind::Uninitialized);
 
   if (!Obj)
     return AnyValue::getNullValue(Ctx, Type);
@@ -85,18 +83,17 @@ AnyValue Library::executeCalloc(StringRef Name, Type *Type,
   const auto &CountVal = Args[0];
   const auto &SizeVal = Args[1];
 
-  const uint64_t Count = CountVal.asInteger().getLimitedValue();
-  const uint64_t Size = SizeVal.asInteger().getLimitedValue();
+  const APInt &Count = CountVal.asInteger();
+  const APInt &Size = SizeVal.asInteger();
 
   bool Overflow = false;
-  const uint64_t AllocSize = SaturatingMultiply(Count, Size, &Overflow);
+  const APInt AllocSize = Count.umul_ov(Size, Overflow);
   if (Overflow)
     return AnyValue::getNullValue(Ctx, Type);
 
-  const uint64_t MaxAlign = getMaxAlign(DL);
-
-  const auto Obj =
-      Ctx.allocate(AllocSize, MaxAlign, Name, 0, MemInitKind::Zeroed);
+  const IntrusiveRefCntPtr<MemoryObject> Obj =
+      Ctx.allocate(AllocSize.getLimitedValue(), getMaxAlign(DL), Name, 0,
+                   MemInitKind::Zeroed);
 
   if (!Obj)
     return AnyValue::getNullValue(Ctx, Type);
@@ -129,7 +126,7 @@ AnyValue Library::executePuts(ArrayRef<AnyValue> Args) {
     return AnyValue::poison();
 
   Handler.onPrint(*StrOpt + "\n");
-  return AnyValue(APInt(32, 1));
+  return AnyValue(APInt(Executor.getIntSize(), 1));
 }
 
 AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
@@ -252,7 +249,7 @@ AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
 
   OS.flush();
   Handler.onPrint(Output);
-  return AnyValue(APInt(32, Output.size()));
+  return AnyValue(APInt(Executor.getIntSize(), Output.size()));
 }
 
 AnyValue Library::executeExit(ArrayRef<AnyValue> Args) {
diff --git a/llvm/tools/llubi/llubi.cpp b/llvm/tools/llubi/llubi.cpp
index 88c5fe1cdc2e4..1c4cc12882542 100644
--- a/llvm/tools/llubi/llubi.cpp
+++ b/llvm/tools/llubi/llubi.cpp
@@ -260,9 +260,9 @@ int main(int argc, char **argv) {
   ubi::EventHandler NoopHandler;
   VerboseEventHandler VerboseHandler;
   ubi::AnyValue RetVal;
-  ubi::ProgramExitInfo ExitInfo;
-  if (!Ctx.runFunction(*EntryFn, Args, RetVal,
-                       Verbose ? VerboseHandler : NoopHandler, ExitInfo)) {
+  ubi::ProgramExitInfo ExitInfo = Ctx.runFunction(
+      *EntryFn, Args, RetVal, Verbose ? VerboseHandler : NoopHandler);
+  if (ExitInfo.Kind != ubi::ProgramExitInfo::ProgramExitKind::Returned) {
     if (!ExitInfo.isExitedByLibcall()) {
       WithColor::error() << "Execution of function '" << EntryFunc
                          << "' failed.\n";
@@ -272,6 +272,7 @@ int main(int argc, char **argv) {
     case ubi::ProgramExitInfo::ProgramExitKind::Exited:
       return static_cast<int>(ExitInfo.ExitCode & 0xFF);
     case ubi::ProgramExitInfo::ProgramExitKind::Aborted:
+      return 134;
     case ubi::ProgramExitInfo::ProgramExitKind::Terminated:
       return 1;
     default:

>From 53e8ffe8d864cb55d80c375b840737774de0d786 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Tue, 7 Apr 2026 10:54:38 +0800
Subject: [PATCH 13/21] [llubi] Correctly set the exit kind when exiting
 runMainLoop()

---
 llvm/tools/llubi/lib/Interpreter.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index a7542ec2867fe..4efaeeafde0ce 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -1038,6 +1038,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
                "Expected to enter a callee.");
       }
     }
+    if (getExecutionStatus())
+      ExitInfo.Kind = ProgramExitInfo::ProgramExitKind::Returned;
     return getExecutionStatus();
   }
 };

>From cfe4715c16c4cf8288ee64613fffa6b0c45501ce Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Tue, 7 Apr 2026 11:37:45 +0800
Subject: [PATCH 14/21] [llubi] Make Context::free() check the associated
 MemoryObject of the freed pointer

---
 llvm/tools/llubi/lib/Context.cpp     | 13 ++++++++++---
 llvm/tools/llubi/lib/Context.h       |  2 +-
 llvm/tools/llubi/lib/Interpreter.cpp |  2 +-
 llvm/tools/llubi/lib/Library.cpp     |  2 +-
 4 files changed, 13 insertions(+), 6 deletions(-)

diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 968ac4f561558..b924ae36a369d 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -456,11 +456,18 @@ IntrusiveRefCntPtr<MemoryObject> Context::allocate(uint64_t Size,
   return MemObj;
 }
 
-bool Context::free(uint64_t Address) {
+bool Context::free(const Pointer &Ptr) {
+  uint64_t Address = Ptr.address().getZExtValue();
+  MemoryObject *Obj = Ptr.getMemoryObject();
+
+  if (!Obj || Address != Obj->getAddress())
+    return false;
+
   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;
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index f0883d74bd341..3cab8fe8521b1 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -257,7 +257,7 @@ class Context {
   IntrusiveRefCntPtr<MemoryObject> allocate(uint64_t Size, uint64_t Align,
                                             StringRef Name, unsigned AS,
                                             MemInitKind InitKind);
-  bool free(uint64_t Address);
+  bool free(const Pointer &Ptr);
   /// Derive a pointer from a memory object with offset 0.
   /// Please use Pointer's interface for further manipulations.
   Pointer deriveFromMemoryObject(IntrusiveRefCntPtr<MemoryObject> Obj);
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 4efaeeafde0ce..2f1bf10524c9f 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -1031,7 +1031,7 @@ 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(Ctx.deriveFromMemoryObject(Obj));
         CallStack.pop_back();
       } else {
         assert(Top.State == FrameState::Pending &&
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index fafcd2562bd4d..e22366ab8901a 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -109,7 +109,7 @@ AnyValue Library::executeFree(ArrayRef<AnyValue> Args) {
   if (Ptr.address().isZero())
     return AnyValue();
 
-  if (!Ctx.free(Ptr.address().getZExtValue())) {
+  if (!Ctx.free(Ptr)) {
     Executor.reportImmediateUB(
         "freeing an invalid, unallocated, or already freed pointer.");
     return AnyValue::poison();

>From 685b9b9ffdbee796e143853158516743175ef515 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Tue, 7 Apr 2026 17:49:38 +0800
Subject: [PATCH 15/21] [llubi] Remove ExecutorBase::Status as it can be
 replaced by ExitInfo

---
 llvm/tools/llubi/lib/Context.h        |  7 ++-----
 llvm/tools/llubi/lib/ExecutorBase.cpp | 22 ++++++++++------------
 llvm/tools/llubi/lib/ExecutorBase.h   | 14 +++++---------
 llvm/tools/llubi/lib/Interpreter.cpp  | 25 ++++++++++++-------------
 llvm/tools/llubi/llubi.cpp            |  7 +++----
 5 files changed, 32 insertions(+), 43 deletions(-)

diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 3cab8fe8521b1..02e13531d040d 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -49,7 +49,6 @@ enum class UndefValueBehavior {
 
 struct ProgramExitInfo {
   enum class ProgramExitKind {
-    Invalid,
     // Program exited via a normal return
     Returned,
     // Program exited with an interpreter error (UB/Unsupported
@@ -63,10 +62,8 @@ struct ProgramExitInfo {
     Terminated,
   };
 
-  ProgramExitKind Kind = ProgramExitKind::Invalid;
-  uint64_t ExitCode = 0;
-
-  explicit operator bool() const { return Kind != ProgramExitKind::Invalid; }
+  ProgramExitKind Kind;
+  uint64_t ExitCode;
 
   bool isExitedByLibcall() const {
     return Kind == ProgramExitKind::Exited ||
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index 762e211fdddb7..2834f0a45a35e 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 (isProgramExited())
     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 (isProgramExited())
     return;
-  Status = false;
+  requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
   Handler.onError(Msg);
 }
 
@@ -127,17 +127,15 @@ void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
 
 void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
                                       uint64_t ExitCode) {
-  if (Kind == ProgramExitInfo::ProgramExitKind::Invalid)
-    llvm_unreachable("Invalid program exit kind");
-  Status = false;
-  ExitInfo.Kind = Kind;
-  ExitInfo.ExitCode = ExitCode;
-  Handler.onProgramExit(ExitInfo);
+  ExitInfo = ProgramExitInfo{Kind, ExitCode};
+  Handler.onProgramExit(*ExitInfo);
 }
 
-bool ExecutorBase::getExecutionStatus() const { return Status; }
+bool ExecutorBase::isProgramExited() const { return ExitInfo.has_value(); }
 
-ProgramExitInfo ExecutorBase::getExitInfo() const { return ExitInfo; }
+std::optional<ProgramExitInfo> ExecutorBase::getExitInfo() const {
+  return ExitInfo;
+}
 
 unsigned ExecutorBase::getIntSize() const {
   return CurrentFrame->TLI.getIntSize();
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index 9b151d9586b10..e35ed58e10e7e 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 {
 
@@ -72,15 +73,10 @@ class ExecutorBase {
   Context &Ctx;
   EventHandler &Handler;
   Frame *CurrentFrame = nullptr;
-  ProgramExitInfo ExitInfo;
+  std::optional<ProgramExitInfo> ExitInfo;
 
-private:
-  // Used to indicate whether the interpreter should continue execution.
-  bool Status;
-
-protected:
   ExecutorBase(Context &C, EventHandler &H)
-      : Ctx(C), Handler(H), Status(true) {}
+      : Ctx(C), Handler(H), ExitInfo(std::nullopt) {}
   ~ExecutorBase() = default;
 
 public:
@@ -101,8 +97,8 @@ class ExecutorBase {
   void requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
                           uint64_t ExitCode = 0);
 
-  bool getExecutionStatus() const;
-  ProgramExitInfo getExitInfo() const;
+  bool isProgramExited() const;
+  std::optional<ProgramExitInfo> getExitInfo() const;
 
   unsigned getIntSize() const;
 };
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 2f1bf10524c9f..c60c0e38fe4b9 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -78,7 +78,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   }
 
   void setResult(Instruction &I, AnyValue V) {
-    if (getExecutionStatus())
+    if (!isProgramExited())
       if (!Handler.onInstructionExecuted(I, V))
         requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
     CurrentFrame->ValueMap.insert_or_assign(&I, std::move(V));
@@ -270,7 +270,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     if (auto *RV = RI.getReturnValue())
       CurrentFrame->RetVal = getValue(RV);
     CurrentFrame->State = FrameState::Exit;
-    if (getExecutionStatus())
+    if (!isProgramExited())
       if (!Handler.onInstructionExecuted(RI, None))
         requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
   }
@@ -887,7 +887,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     // TODO: track volatile stores
     // TODO: handle metadata
     store(Ptr, SI.getAlign(), Val, SI.getValueOperand()->getType());
-    if (getExecutionStatus())
+    if (!isProgramExited())
       if (!Handler.onInstructionExecuted(SI, AnyValue()))
         requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
   }
@@ -981,10 +981,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 (getExecutionStatus() && !CallStack.empty()) {
+    while (!isProgramExited() && !CallStack.empty()) {
       Frame &Top = CallStack.back();
       CurrentFrame = &Top;
       if (Top.State == FrameState::Entry) {
@@ -997,7 +997,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
 
       Top.State = FrameState::Running;
       // Interpreter loop inside a function
-      while (getExecutionStatus()) {
+      while (!isProgramExited()) {
         assert(Top.State == FrameState::Running &&
                "Expected to be in running state.");
         if (MaxSteps != 0 && Steps >= MaxSteps) {
@@ -1008,7 +1008,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
 
         Instruction &I = *Top.PC;
         visit(&I);
-        if (!getExecutionStatus())
+        if (isProgramExited())
           break;
 
         // A function call or return has occurred.
@@ -1022,7 +1022,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
           ++Top.PC;
       }
 
-      if (!getExecutionStatus())
+      if (isProgramExited())
         break;
 
       if (Top.State == FrameState::Exit) {
@@ -1038,17 +1038,16 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
                "Expected to enter a callee.");
       }
     }
-    if (getExecutionStatus())
-      ExitInfo.Kind = ProgramExitInfo::ProgramExitKind::Returned;
-    return getExecutionStatus();
+    if (!isProgramExited())
+      requestProgramExit(ProgramExitInfo::ProgramExitKind::Returned);
+    return *getExitInfo();
   }
 };
 
 ProgramExitInfo Context::runFunction(Function &F, ArrayRef<AnyValue> Args,
                                      AnyValue &RetVal, EventHandler &Handler) {
   InstExecutor Executor(*this, Handler, F, Args, RetVal);
-  Executor.runMainLoop();
-  return Executor.getExitInfo();
+  return Executor.runMainLoop();
 }
 
 } // namespace llvm::ubi
diff --git a/llvm/tools/llubi/llubi.cpp b/llvm/tools/llubi/llubi.cpp
index 1c4cc12882542..4f042afc1dc10 100644
--- a/llvm/tools/llubi/llubi.cpp
+++ b/llvm/tools/llubi/llubi.cpp
@@ -146,9 +146,9 @@ class VerboseEventHandler : public ubi::EventHandler {
     case ubi::ProgramExitInfo::ProgramExitKind::Terminated:
       errs() << "Program terminated.\n";
       return true;
-    default:
-      llvm_unreachable("Unknown ProgramExitKind");
     }
+
+    llvm_unreachable("Unknown ProgramExitKind");
   }
 
   void onUnrecognizedInstruction(Instruction &I) override {
@@ -272,9 +272,8 @@ int main(int argc, char **argv) {
     case ubi::ProgramExitInfo::ProgramExitKind::Exited:
       return static_cast<int>(ExitInfo.ExitCode & 0xFF);
     case ubi::ProgramExitInfo::ProgramExitKind::Aborted:
-      return 134;
     case ubi::ProgramExitInfo::ProgramExitKind::Terminated:
-      return 1;
+      return 134;
     default:
       llvm_unreachable("Unexpected returned kind for ProgramExited status");
     }

>From 998dd40389fb5076ac0d31b2f8f4da935a6b909f Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Tue, 7 Apr 2026 17:53:23 +0800
Subject: [PATCH 16/21] [llubi] Use optional::emplace() instead of assignment
 operator

---
 llvm/tools/llubi/lib/Context.h        | 3 +++
 llvm/tools/llubi/lib/ExecutorBase.cpp | 2 +-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 02e13531d040d..e1a27e92daa34 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -65,6 +65,9 @@ struct ProgramExitInfo {
   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 ||
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index 2834f0a45a35e..daca5ea9f567c 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -127,7 +127,7 @@ void ExecutorBase::store(const AnyValue &Ptr, Align Alignment,
 
 void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
                                       uint64_t ExitCode) {
-  ExitInfo = ProgramExitInfo{Kind, ExitCode};
+  ExitInfo.emplace(Kind, ExitCode);
   Handler.onProgramExit(*ExitInfo);
 }
 

>From 045b280223a37d36f816ad99d56485aa8f7a592b Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Tue, 7 Apr 2026 22:39:31 +0800
Subject: [PATCH 17/21] [llubi] New libcall test cases and minor fixes

---
 .../tools/llubi/lib_calloc_size_overflow.ll   | 13 ++++
 llvm/test/tools/llubi/lib_double_free.ll      |  2 +-
 .../test/tools/llubi/lib_malloc_large_size.ll | 13 ++++
 llvm/test/tools/llubi/lib_poison_argument.ll  | 13 ++++
 .../tools/llubi/lib_read_nullary_string.ll    |  5 +-
 llvm/tools/llubi/lib/Context.cpp              | 11 +---
 llvm/tools/llubi/lib/Context.h                |  4 +-
 llvm/tools/llubi/lib/ExecutorBase.cpp         |  6 +-
 llvm/tools/llubi/lib/ExecutorBase.h           |  2 +-
 llvm/tools/llubi/lib/Interpreter.cpp          | 21 +++----
 llvm/tools/llubi/lib/Library.cpp              | 59 +++++++++++++----
 llvm/tools/llubi/llubi.cpp                    | 63 +++++++++----------
 12 files changed, 138 insertions(+), 74 deletions(-)
 create mode 100644 llvm/test/tools/llubi/lib_calloc_size_overflow.ll
 create mode 100644 llvm/test/tools/llubi/lib_malloc_large_size.ll
 create mode 100644 llvm/test/tools/llubi/lib_poison_argument.ll

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..6f53cdb43487f
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_calloc_size_overflow.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 ptr @calloc(i64, i64)
+
+define void @main() {
+entry:
+  %ptr = call ptr @calloc(i64 -1, i64 2)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Immediate UB detected: calloc() with allocation size that overflows uint64_t.
+; 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
index 2441d69f6628a..db0a684e74193 100644
--- a/llvm/test/tools/llubi/lib_double_free.ll
+++ b/llvm/test/tools/llubi/lib_double_free.ll
@@ -17,5 +17,5 @@ entry:
 ; 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: freeing an invalid, unallocated, or already freed pointer.
+; 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_malloc_large_size.ll b/llvm/test/tools/llubi/lib_malloc_large_size.ll
new file mode 100644
index 0000000000000..d4ba9b88477e6
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_malloc_large_size.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 --max-mem=100 < %s 2>&1 | FileCheck %s
+
+declare ptr @malloc(i64)
+
+define void @main() {
+entry:
+  %ptr = call ptr @malloc(i64 100)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: Error: Insufficient stack space.
+; 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_read_nullary_string.ll b/llvm/test/tools/llubi/lib_read_nullary_string.ll
index a9d489d6bc55b..2c1e354a3850e 100644
--- a/llvm/test/tools/llubi/lib_read_nullary_string.ll
+++ b/llvm/test/tools/llubi/lib_read_nullary_string.ll
@@ -4,13 +4,10 @@
 declare i32 @puts(ptr)
 
 define i32 @main() {
-  %puts.str = inttoptr i64 0 to ptr
-
-  %1 = call i32 @puts(ptr %puts.str)
+  %1 = call i32 @puts(ptr null)
 
   ret i32 0
 }
 ; CHECK: Entering function: main
-; CHECK-NEXT:   %puts.str = inttoptr i64 0 to ptr => ptr 0x0 [dangling]
 ; 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/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index b924ae36a369d..2f1cf3b06473b 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -456,15 +456,10 @@ IntrusiveRefCntPtr<MemoryObject> Context::allocate(uint64_t Size,
   return MemObj;
 }
 
-bool Context::free(const Pointer &Ptr) {
-  uint64_t Address = Ptr.address().getZExtValue();
-  MemoryObject *Obj = Ptr.getMemoryObject();
-
-  if (!Obj || Address != Obj->getAddress())
-    return false;
-
+bool Context::free(const MemoryObject &Obj) {
+  uint64_t Address = Obj.getAddress();
   auto It = MemoryObjects.find(Address);
-  if (It == MemoryObjects.end() || It->second.get() != Obj)
+  if (It == MemoryObjects.end() || It->second.get() != &Obj)
     return false;
 
   UsedMem -= std::max(It->second->getSize(), static_cast<uint64_t>(1));
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index e1a27e92daa34..1db78107329c3 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -138,7 +138,7 @@ class EventHandler {
   virtual bool onFunctionExit(Function &F, const AnyValue &RetVal) {
     return true;
   }
-  virtual bool onProgramExit(const ProgramExitInfo &ExitInfo) { return true; }
+  virtual void onProgramExit(const ProgramExitInfo &ExitInfo) {}
   virtual bool onPrint(StringRef Msg) {
     outs() << Msg;
     return true;
@@ -257,7 +257,7 @@ class Context {
   IntrusiveRefCntPtr<MemoryObject> allocate(uint64_t Size, uint64_t Align,
                                             StringRef Name, unsigned AS,
                                             MemInitKind InitKind);
-  bool free(const Pointer &Ptr);
+  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);
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index daca5ea9f567c..76f6c5e0a7c6c 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -29,7 +29,7 @@ Frame::Frame(Function &F, CallBase *CallSite, Frame *LastFrame,
 
 void ExecutorBase::reportImmediateUB(StringRef Msg) {
   // Check if we have already reported an immediate UB.
-  if (isProgramExited())
+  if (hasProgramExited())
     return;
   requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
   // TODO: Provide stack trace information.
@@ -38,7 +38,7 @@ void ExecutorBase::reportImmediateUB(StringRef Msg) {
 
 void ExecutorBase::reportError(StringRef Msg) {
   // Check if we have already reported an error message.
-  if (isProgramExited())
+  if (hasProgramExited())
     return;
   requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
   Handler.onError(Msg);
@@ -131,7 +131,7 @@ void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
   Handler.onProgramExit(*ExitInfo);
 }
 
-bool ExecutorBase::isProgramExited() const { return ExitInfo.has_value(); }
+bool ExecutorBase::hasProgramExited() const { return ExitInfo.has_value(); }
 
 std::optional<ProgramExitInfo> ExecutorBase::getExitInfo() const {
   return ExitInfo;
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index e35ed58e10e7e..70aa29404e3a9 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -97,7 +97,7 @@ class ExecutorBase {
   void requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
                           uint64_t ExitCode = 0);
 
-  bool isProgramExited() const;
+  bool hasProgramExited() const;
   std::optional<ProgramExitInfo> getExitInfo() const;
 
   unsigned getIntSize() const;
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index c60c0e38fe4b9..91ca779609c59 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -78,7 +78,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   }
 
   void setResult(Instruction &I, AnyValue V) {
-    if (!isProgramExited())
+    if (!hasProgramExited())
       if (!Handler.onInstructionExecuted(I, V))
         requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
     CurrentFrame->ValueMap.insert_or_assign(&I, std::move(V));
@@ -270,9 +270,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     if (auto *RV = RI.getReturnValue())
       CurrentFrame->RetVal = getValue(RV);
     CurrentFrame->State = FrameState::Exit;
-    if (!isProgramExited())
-      if (!Handler.onInstructionExecuted(RI, None))
-        requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
+    if (!Handler.onInstructionExecuted(RI, None))
+      requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
   }
 
   void visitUncondBrInst(UncondBrInst &BI) { jumpTo(BI, BI.getSuccessor()); }
@@ -887,7 +886,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     // TODO: track volatile stores
     // TODO: handle metadata
     store(Ptr, SI.getAlign(), Val, SI.getValueOperand()->getType());
-    if (!isProgramExited())
+    if (!hasProgramExited())
       if (!Handler.onInstructionExecuted(SI, AnyValue()))
         requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
   }
@@ -984,7 +983,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   ProgramExitInfo runMainLoop() {
     uint32_t MaxSteps = Ctx.getMaxSteps();
     uint32_t Steps = 0;
-    while (!isProgramExited() && !CallStack.empty()) {
+    while (!hasProgramExited() && !CallStack.empty()) {
       Frame &Top = CallStack.back();
       CurrentFrame = &Top;
       if (Top.State == FrameState::Entry) {
@@ -997,7 +996,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
 
       Top.State = FrameState::Running;
       // Interpreter loop inside a function
-      while (!isProgramExited()) {
+      while (!hasProgramExited()) {
         assert(Top.State == FrameState::Running &&
                "Expected to be in running state.");
         if (MaxSteps != 0 && Steps >= MaxSteps) {
@@ -1008,7 +1007,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
 
         Instruction &I = *Top.PC;
         visit(&I);
-        if (isProgramExited())
+        if (hasProgramExited())
           break;
 
         // A function call or return has occurred.
@@ -1022,7 +1021,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
           ++Top.PC;
       }
 
-      if (isProgramExited())
+      if (hasProgramExited())
         break;
 
       if (Top.State == FrameState::Exit) {
@@ -1031,14 +1030,14 @@ 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(Ctx.deriveFromMemoryObject(Obj));
+          Ctx.free(*Obj);
         CallStack.pop_back();
       } else {
         assert(Top.State == FrameState::Pending &&
                "Expected to enter a callee.");
       }
     }
-    if (!isProgramExited())
+    if (!hasProgramExited())
       requestProgramExit(ProgramExitInfo::ProgramExitKind::Returned);
     return *getExitInfo();
   }
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index e22366ab8901a..287720c6415b4 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -11,6 +11,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "Library.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/Analysis/TargetLibraryInfo.h"
 #include "llvm/IR/InstrTypes.h"
 #include "llvm/Support/Format.h"
@@ -67,13 +68,21 @@ AnyValue Library::executeMalloc(StringRef Name, Type *Type,
                                 ArrayRef<AnyValue> Args) {
   const auto &SizeVal = Args[0];
 
-  const uint64_t AllocSize = SizeVal.asInteger().getLimitedValue();
+  if (SizeVal.asInteger().getActiveBits() > 64) {
+    Executor.reportImmediateUB(
+        "malloc() with allocation size that overflows uint64_t.");
+    return AnyValue::poison();
+  }
+
+  const uint64_t AllocSize = SizeVal.asInteger().getZExtValue();
 
   const IntrusiveRefCntPtr<MemoryObject> Obj = Ctx.allocate(
       AllocSize, getMaxAlign(DL), Name, 0, MemInitKind::Uninitialized);
 
-  if (!Obj)
-    return AnyValue::getNullValue(Ctx, Type);
+  if (!Obj) {
+    Executor.reportError("Insufficient stack space.");
+    return AnyValue::poison();
+  }
 
   return Ctx.deriveFromMemoryObject(Obj);
 }
@@ -88,15 +97,20 @@ AnyValue Library::executeCalloc(StringRef Name, Type *Type,
 
   bool Overflow = false;
   const APInt AllocSize = Count.umul_ov(Size, Overflow);
-  if (Overflow)
-    return AnyValue::getNullValue(Ctx, Type);
+  if (Overflow) {
+    Executor.reportImmediateUB(
+        "calloc() with allocation size that overflows uint64_t.");
+    return AnyValue::poison();
+  }
 
   const IntrusiveRefCntPtr<MemoryObject> Obj =
       Ctx.allocate(AllocSize.getLimitedValue(), getMaxAlign(DL), Name, 0,
                    MemInitKind::Zeroed);
 
-  if (!Obj)
-    return AnyValue::getNullValue(Ctx, Type);
+  if (!Obj) {
+    Executor.reportError("Insufficient stack space.");
+    return AnyValue::poison();
+  }
 
   return Ctx.deriveFromMemoryObject(Obj);
 }
@@ -109,9 +123,26 @@ AnyValue Library::executeFree(ArrayRef<AnyValue> Args) {
   if (Ptr.address().isZero())
     return AnyValue();
 
-  if (!Ctx.free(Ptr)) {
+  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 an invalid, unallocated, or already freed pointer.");
+        "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 (!Ctx.free(*Obj)) {
+    Executor.reportImmediateUB("freeing an invalid pointer.");
     return AnyValue::poison();
   }
 
@@ -171,8 +202,8 @@ AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
     char Specifier = FormatStr[I++];
     std::string CleanChunk = FormatStr.substr(Start, I - Start - 1);
     CleanChunk.erase(
-        std::remove_if(CleanChunk.begin(), CleanChunk.end(),
-                       [](char c) { return StringRef("hljzt").contains(c); }),
+        llvm::remove_if(CleanChunk,
+                        [](char C) { return StringRef("hljzt").contains(C); }),
         CleanChunk.end());
 
     if (ArgIndex >= Args.size()) {
@@ -247,6 +278,12 @@ AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
     }
   }
 
+  if (ArgIndex < Args.size()) {
+    Executor.reportImmediateUB(
+        "Too many arguments provided for the format string.");
+    return AnyValue::poison();
+  }
+
   OS.flush();
   Handler.onPrint(Output);
   return AnyValue(APInt(Executor.getIntSize(), Output.size()));
diff --git a/llvm/tools/llubi/llubi.cpp b/llvm/tools/llubi/llubi.cpp
index 4f042afc1dc10..72ba6f563014b 100644
--- a/llvm/tools/llubi/llubi.cpp
+++ b/llvm/tools/llubi/llubi.cpp
@@ -131,21 +131,21 @@ class VerboseEventHandler : public ubi::EventHandler {
     return true;
   }
 
-  bool onProgramExit(const ubi::ProgramExitInfo &Info) override {
+  void onProgramExit(const ubi::ProgramExitInfo &Info) override {
     switch (Info.Kind) {
     case ubi::ProgramExitInfo::ProgramExitKind::Returned:
-      return true;
+      return;
     case ubi::ProgramExitInfo::ProgramExitKind::Failed:
-      return true;
+      return;
     case ubi::ProgramExitInfo::ProgramExitKind::Exited:
       errs() << "Program exited with code " << Info.ExitCode << '\n';
-      return true;
+      return;
     case ubi::ProgramExitInfo::ProgramExitKind::Aborted:
       errs() << "Program aborted.\n";
-      return true;
+      return;
     case ubi::ProgramExitInfo::ProgramExitKind::Terminated:
       errs() << "Program terminated.\n";
-      return true;
+      return;
     }
 
     llvm_unreachable("Unknown ProgramExitKind");
@@ -262,34 +262,31 @@ int main(int argc, char **argv) {
   ubi::AnyValue RetVal;
   ubi::ProgramExitInfo ExitInfo = Ctx.runFunction(
       *EntryFn, Args, RetVal, Verbose ? VerboseHandler : NoopHandler);
-  if (ExitInfo.Kind != ubi::ProgramExitInfo::ProgramExitKind::Returned) {
-    if (!ExitInfo.isExitedByLibcall()) {
-      WithColor::error() << "Execution of function '" << EntryFunc
-                         << "' failed.\n";
-      return 1;
-    }
-    switch (ExitInfo.Kind) {
-    case ubi::ProgramExitInfo::ProgramExitKind::Exited:
-      return static_cast<int>(ExitInfo.ExitCode & 0xFF);
-    case ubi::ProgramExitInfo::ProgramExitKind::Aborted:
-    case ubi::ProgramExitInfo::ProgramExitKind::Terminated:
-      return 134;
-    default:
-      llvm_unreachable("Unexpected returned kind for ProgramExited status");
+  switch (ExitInfo.Kind) {
+  case ubi::ProgramExitInfo::ProgramExitKind::Failed:
+    WithColor::error() << "Execution of function '" << EntryFunc
+                       << "' failed.\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);
     }
+    return 0;
   }
 
-  // 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);
-  }
-  return 0;
+  llvm_unreachable("Unknown ProgramExitKind");
 }

>From f12f5b2c7cd47275b7d3e7038e8cd502d1aedbf1 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Tue, 7 Apr 2026 22:52:23 +0800
Subject: [PATCH 18/21] [llubi] Immediately flush stdout in onPrint().

---
 llvm/test/tools/llubi/lib_abort.ll         |  2 +-
 llvm/test/tools/llubi/lib_exit.ll          |  2 +-
 llvm/test/tools/llubi/lib_io.ll            |  4 ++--
 llvm/test/tools/llubi/lib_printf_format.ll | 14 +++++++-------
 llvm/test/tools/llubi/lib_terminate.ll     |  2 +-
 llvm/tools/llubi/lib/Context.h             |  1 +
 6 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/llvm/test/tools/llubi/lib_abort.ll b/llvm/test/tools/llubi/lib_abort.ll
index 84327fdc99c5d..78ed03c6c70ca 100644
--- a/llvm/test/tools/llubi/lib_abort.ll
+++ b/llvm/test/tools/llubi/lib_abort.ll
@@ -25,6 +25,6 @@ entry:
 ; 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.
-; CHECK-NEXT: Before
diff --git a/llvm/test/tools/llubi/lib_exit.ll b/llvm/test/tools/llubi/lib_exit.ll
index d6a7037c50043..7239352f18182 100644
--- a/llvm/test/tools/llubi/lib_exit.ll
+++ b/llvm/test/tools/llubi/lib_exit.ll
@@ -25,6 +25,6 @@ entry:
 ; 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
-; CHECK-NEXT: Before
diff --git a/llvm/test/tools/llubi/lib_io.ll b/llvm/test/tools/llubi/lib_io.ll
index 5b5c861f5d237..157d11942a64c 100644
--- a/llvm/test/tools/llubi/lib_io.ll
+++ b/llvm/test/tools/llubi/lib_io.ll
@@ -24,13 +24,13 @@ entry:
 ; 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
-; CHECK-NEXT: Hello, puts!
-; CHECK-NEXT: Int: 42, Str: test
diff --git a/llvm/test/tools/llubi/lib_printf_format.ll b/llvm/test/tools/llubi/lib_printf_format.ll
index 8f527ebe314f2..e4d5dea1b6a27 100644
--- a/llvm/test/tools/llubi/lib_printf_format.ll
+++ b/llvm/test/tools/llubi/lib_printf_format.ll
@@ -62,20 +62,20 @@ entry:
 ; 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
-; CHECK-NEXT: Ints: 42, -42, 255, 377, ff, FF, 00042
-; CHECK-NEXT: Lengths: 123456789, 987654321, 100, 50, A
-; CHECK-NEXT: Str: llubi, Ptr: 0x70
-; CHECK-NEXT: Percent: 100%
-; CHECK-NEXT: Floats: 3.141590, 3.141590e+00, 3.14159
-; CHECK-NEXT: Count: Done
-; CHECK-NEXT: N=7
diff --git a/llvm/test/tools/llubi/lib_terminate.ll b/llvm/test/tools/llubi/lib_terminate.ll
index 6d7821584e1a4..8026e9aa24497 100644
--- a/llvm/test/tools/llubi/lib_terminate.ll
+++ b/llvm/test/tools/llubi/lib_terminate.ll
@@ -25,6 +25,6 @@ entry:
 ; 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.
-; CHECK-NEXT: Before
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 1db78107329c3..ea38eac356d08 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -141,6 +141,7 @@ class EventHandler {
   virtual void onProgramExit(const ProgramExitInfo &ExitInfo) {}
   virtual bool onPrint(StringRef Msg) {
     outs() << Msg;
+    outs().flush();
     return true;
   }
 };

>From 82f7641c56b841051cbf4c74bde87e30f5d5245f Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Wed, 8 Apr 2026 12:09:18 +0800
Subject: [PATCH 19/21] [llubi] New test cases for printf, malloc, calloc, and
 free

---
 .../test/tools/llubi/lib_calloc_large_size.ll | 18 +++++++
 .../tools/llubi/lib_calloc_size_overflow.ll   |  2 +-
 .../tools/llubi/lib_free_nullary_pointer.ll   | 15 ++++++
 .../test/tools/llubi/lib_free_out_of_bound.ll | 18 +++++++
 llvm/test/tools/llubi/lib_free_stack.ll       | 15 ++++++
 .../test/tools/llubi/lib_malloc_large_size.ll | 13 +++--
 .../llubi/lib_printf_not_enough_argument.ll   | 18 +++++++
 .../llubi/lib_printf_too_many_argument.ll     | 18 +++++++
 .../llubi/lib_printf_unknown_specifier.ll     | 18 +++++++
 llvm/tools/llubi/lib/Context.cpp              | 44 +++++++++++++----
 llvm/tools/llubi/lib/Context.h                | 19 +++++++-
 llvm/tools/llubi/lib/Interpreter.cpp          |  3 +-
 llvm/tools/llubi/lib/Library.cpp              | 48 ++++++++++---------
 llvm/tools/llubi/lib/Library.h                |  6 ++-
 llvm/tools/llubi/llubi.cpp                    |  6 ++-
 15 files changed, 217 insertions(+), 44 deletions(-)
 create mode 100644 llvm/test/tools/llubi/lib_calloc_large_size.ll
 create mode 100644 llvm/test/tools/llubi/lib_free_nullary_pointer.ll
 create mode 100644 llvm/test/tools/llubi/lib_free_out_of_bound.ll
 create mode 100644 llvm/test/tools/llubi/lib_free_stack.ll
 create mode 100644 llvm/test/tools/llubi/lib_printf_not_enough_argument.ll
 create mode 100644 llvm/test/tools/llubi/lib_printf_too_many_argument.ll
 create mode 100644 llvm/test/tools/llubi/lib_printf_unknown_specifier.ll

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
index 6f53cdb43487f..0e2d9c511cc18 100644
--- a/llvm/test/tools/llubi/lib_calloc_size_overflow.ll
+++ b/llvm/test/tools/llubi/lib_calloc_size_overflow.ll
@@ -9,5 +9,5 @@ entry:
   ret void
 }
 ; CHECK: Entering function: main
-; CHECK-NEXT: Immediate UB detected: calloc() with allocation size that overflows uint64_t.
+; CHECK-NEXT: Immediate UB detected: calloc() with allocation size that overflows size_t.
 ; CHECK-NEXT: error: Execution of function 'main' failed.
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..d888deb808c42
--- /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 an stack allocation.
+; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/test/tools/llubi/lib_malloc_large_size.ll b/llvm/test/tools/llubi/lib_malloc_large_size.ll
index d4ba9b88477e6..8a1800f53af92 100644
--- a/llvm/test/tools/llubi/lib_malloc_large_size.ll
+++ b/llvm/test/tools/llubi/lib_malloc_large_size.ll
@@ -1,13 +1,18 @@
 ; 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
+; RUN: llubi --verbose --max-mem=100 < %s 2>&1 | FileCheck %s
 
 declare ptr @malloc(i64)
 
 define void @main() {
 entry:
-  %ptr = call ptr @malloc(i64 100)
+  %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: Error: Insufficient stack space.
-; CHECK-NEXT: error: Execution of function 'main' failed.
+; 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_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_too_many_argument.ll b/llvm/test/tools/llubi/lib_printf_too_many_argument.ll
new file mode 100644
index 0000000000000..654f351199882
--- /dev/null
+++ b/llvm/test/tools/llubi/lib_printf_too_many_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, i32 255, i32 255)
+
+  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: Too many 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/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 2f1cf3b06473b..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,18 +441,17 @@ 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;
@@ -506,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 ea38eac356d08..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:
@@ -83,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;
@@ -100,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; }
 
@@ -115,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.
@@ -257,7 +271,8 @@ class Context {
   const AnyValue &getConstantValue(Constant *C);
   IntrusiveRefCntPtr<MemoryObject> allocate(uint64_t Size, uint64_t Align,
                                             StringRef Name, unsigned AS,
-                                            MemInitKind InitKind);
+                                            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.
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 91ca779609c59..76037d180512e 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -779,7 +779,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;
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index 287720c6415b4..5e29970279761 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -65,30 +65,25 @@ std::optional<std::string> Library::readStringFromMemory(const Pointer &Ptr) {
 }
 
 AnyValue Library::executeMalloc(StringRef Name, Type *Type,
-                                ArrayRef<AnyValue> Args) {
+                                ArrayRef<AnyValue> Args,
+                                MemAllocKind AllocKind) {
   const auto &SizeVal = Args[0];
 
-  if (SizeVal.asInteger().getActiveBits() > 64) {
-    Executor.reportImmediateUB(
-        "malloc() with allocation size that overflows uint64_t.");
-    return AnyValue::poison();
-  }
-
   const uint64_t AllocSize = SizeVal.asInteger().getZExtValue();
 
-  const IntrusiveRefCntPtr<MemoryObject> Obj = Ctx.allocate(
-      AllocSize, getMaxAlign(DL), Name, 0, MemInitKind::Uninitialized);
+  const IntrusiveRefCntPtr<MemoryObject> Obj =
+      Ctx.allocate(AllocSize, getMaxAlign(DL), Name, 0,
+                   MemInitKind::Uninitialized, AllocKind);
 
-  if (!Obj) {
-    Executor.reportError("Insufficient stack space.");
-    return AnyValue::poison();
-  }
+  if (!Obj)
+    return AnyValue::getNullValue(Ctx, Type);
 
   return Ctx.deriveFromMemoryObject(Obj);
 }
 
 AnyValue Library::executeCalloc(StringRef Name, Type *Type,
-                                ArrayRef<AnyValue> Args) {
+                                ArrayRef<AnyValue> Args,
+                                MemAllocKind AllocKind) {
   const auto &CountVal = Args[0];
   const auto &SizeVal = Args[1];
 
@@ -99,18 +94,16 @@ AnyValue Library::executeCalloc(StringRef Name, Type *Type,
   const APInt AllocSize = Count.umul_ov(Size, Overflow);
   if (Overflow) {
     Executor.reportImmediateUB(
-        "calloc() with allocation size that overflows uint64_t.");
+        "calloc() with allocation size that overflows size_t.");
     return AnyValue::poison();
   }
 
   const IntrusiveRefCntPtr<MemoryObject> Obj =
       Ctx.allocate(AllocSize.getLimitedValue(), getMaxAlign(DL), Name, 0,
-                   MemInitKind::Zeroed);
+                   MemInitKind::Zeroed, AllocKind);
 
-  if (!Obj) {
-    Executor.reportError("Insufficient stack space.");
-    return AnyValue::poison();
-  }
+  if (!Obj)
+    return AnyValue::getNullValue(Ctx, Type);
 
   return Ctx.deriveFromMemoryObject(Obj);
 }
@@ -141,6 +134,15 @@ AnyValue Library::executeFree(ArrayRef<AnyValue> Args) {
     return AnyValue::poison();
   }
 
+  if (!Obj->isHeapAllocated()) {
+    Executor.reportImmediateUB("freeing an stack allocation.");
+    return AnyValue::poison();
+  }
+
+  // Currently we don't for cases where a memory allocated with C
+  // allocation family (malloc, calloc, etc.) is freed with a different free
+  // function comes from a different family (C++ delete, etc.)
+
   if (!Ctx.free(*Obj)) {
     Executor.reportImmediateUB("freeing an invalid pointer.");
     return AnyValue::poison();
@@ -319,12 +321,14 @@ std::optional<AnyValue> Library::executeLibcall(LibFunc LF, StringRef Name,
 
   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);
+    return executeMalloc(Name, Type, Args, MemAllocKind::NewArray);
 
   case LibFunc_calloc:
-    return executeCalloc(Name, Type, Args);
+    return executeCalloc(Name, Type, Args, MemAllocKind::Malloc);
 
   case LibFunc_free:
   case LibFunc_ZdaPv:
diff --git a/llvm/tools/llubi/lib/Library.h b/llvm/tools/llubi/lib/Library.h
index 1ce13e3bb6f6f..604415d545dd0 100644
--- a/llvm/tools/llubi/lib/Library.h
+++ b/llvm/tools/llubi/lib/Library.h
@@ -29,8 +29,10 @@ class Library {
 
   std::optional<std::string> readStringFromMemory(const Pointer &Ptr);
 
-  AnyValue executeMalloc(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
-  AnyValue executeCalloc(StringRef Name, Type *Type, ArrayRef<AnyValue> Args);
+  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);
diff --git a/llvm/tools/llubi/llubi.cpp b/llvm/tools/llubi/llubi.cpp
index 72ba6f563014b..0ec2e236049dd 100644
--- a/llvm/tools/llubi/llubi.cpp
+++ b/llvm/tools/llubi/llubi.cpp
@@ -227,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;
@@ -235,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;

>From 58e9f7c82e5c88e04b0232cd5b701322001446bf Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Wed, 8 Apr 2026 14:09:27 +0800
Subject: [PATCH 20/21] [llubi] Make new/new[] fail instead of returning null
 on allocation failure

---
 .../tools/llubi/lib_cxx_memory_large_size.ll     | 15 +++++++++++++++
 llvm/tools/llubi/lib/Library.cpp                 | 16 +++++++++++++++-
 2 files changed, 30 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/tools/llubi/lib_cxx_memory_large_size.ll

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/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index 5e29970279761..6bbc52f623f9c 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -67,6 +67,10 @@ std::optional<std::string> Library::readStringFromMemory(const Pointer &Ptr) {
 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();
@@ -75,8 +79,15 @@ AnyValue Library::executeMalloc(StringRef Name, Type *Type,
       Ctx.allocate(AllocSize, getMaxAlign(DL), Name, 0,
                    MemInitKind::Uninitialized, AllocKind);
 
-  if (!Obj)
+  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);
 }
@@ -84,6 +95,9 @@ AnyValue Library::executeMalloc(StringRef Name, Type *Type,
 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];
 

>From af4d7349c8900c2edda0d6d19774e1a8a169092e Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Fri, 10 Apr 2026 12:48:23 +0800
Subject: [PATCH 21/21] [llubi] Small fixes to libcalls

---
 .../tools/llubi/lib_calloc_size_overflow.ll   |  7 ++---
 llvm/test/tools/llubi/lib_free_stack.ll       |  2 +-
 .../llubi/lib_printf_too_many_argument.ll     | 18 -------------
 llvm/tools/llubi/lib/ExecutorBase.cpp         |  4 +++
 llvm/tools/llubi/lib/ExecutorBase.h           |  1 +
 llvm/tools/llubi/lib/Interpreter.cpp          | 26 +++++++++----------
 llvm/tools/llubi/lib/Library.cpp              | 17 +++---------
 7 files changed, 26 insertions(+), 49 deletions(-)
 delete mode 100644 llvm/test/tools/llubi/lib_printf_too_many_argument.ll

diff --git a/llvm/test/tools/llubi/lib_calloc_size_overflow.ll b/llvm/test/tools/llubi/lib_calloc_size_overflow.ll
index 0e2d9c511cc18..ea06f2bde1018 100644
--- a/llvm/test/tools/llubi/lib_calloc_size_overflow.ll
+++ b/llvm/test/tools/llubi/lib_calloc_size_overflow.ll
@@ -1,5 +1,5 @@
 ; 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
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
 
 declare ptr @calloc(i64, i64)
 
@@ -9,5 +9,6 @@ entry:
   ret void
 }
 ; CHECK: Entering function: main
-; CHECK-NEXT: Immediate UB detected: calloc() with allocation size that overflows size_t.
-; CHECK-NEXT: error: Execution of function 'main' failed.
+; 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_free_stack.ll b/llvm/test/tools/llubi/lib_free_stack.ll
index d888deb808c42..ca4fc72101a1c 100644
--- a/llvm/test/tools/llubi/lib_free_stack.ll
+++ b/llvm/test/tools/llubi/lib_free_stack.ll
@@ -11,5 +11,5 @@ define i32 @main() {
 }
 ; CHECK: Entering function: main
 ; CHECK-NEXT:   %p = alloca i32, align 4 => ptr 0x8 [p]
-; CHECK-NEXT: Immediate UB detected: freeing an stack allocation.
+; 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_printf_too_many_argument.ll b/llvm/test/tools/llubi/lib_printf_too_many_argument.ll
deleted file mode 100644
index 654f351199882..0000000000000
--- a/llvm/test/tools/llubi/lib_printf_too_many_argument.ll
+++ /dev/null
@@ -1,18 +0,0 @@
-; 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, i32 255, i32 255)
-
-  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: Too many arguments provided for the format string.
-; CHECK-NEXT: error: Execution of function 'main' failed.
diff --git a/llvm/tools/llubi/lib/ExecutorBase.cpp b/llvm/tools/llubi/lib/ExecutorBase.cpp
index 76f6c5e0a7c6c..f1247cd6bf69e 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.cpp
+++ b/llvm/tools/llubi/lib/ExecutorBase.cpp
@@ -131,6 +131,10 @@ void ExecutorBase::requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
   Handler.onProgramExit(*ExitInfo);
 }
 
+void ExecutorBase::setFailed() {
+  requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
+}
+
 bool ExecutorBase::hasProgramExited() const { return ExitInfo.has_value(); }
 
 std::optional<ProgramExitInfo> ExecutorBase::getExitInfo() const {
diff --git a/llvm/tools/llubi/lib/ExecutorBase.h b/llvm/tools/llubi/lib/ExecutorBase.h
index 70aa29404e3a9..0fa73d9294e07 100644
--- a/llvm/tools/llubi/lib/ExecutorBase.h
+++ b/llvm/tools/llubi/lib/ExecutorBase.h
@@ -96,6 +96,7 @@ class ExecutorBase {
 
   void requestProgramExit(ProgramExitInfo::ProgramExitKind Kind,
                           uint64_t ExitCode = 0);
+  void setFailed();
 
   bool hasProgramExited() const;
   std::optional<ProgramExitInfo> getExitInfo() const;
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 76037d180512e..1118320c7f254 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -78,9 +78,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   }
 
   void setResult(Instruction &I, AnyValue V) {
-    if (!hasProgramExited())
-      if (!Handler.onInstructionExecuted(I, V))
-        requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
+    if (!hasProgramExited() && !Handler.onInstructionExecuted(I, V))
+      setFailed();
     CurrentFrame->ValueMap.insert_or_assign(&I, std::move(V));
   }
 
@@ -145,7 +144,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
 
   void jumpTo(Instruction &Terminator, BasicBlock *DestBB) {
     if (!Handler.onBBJump(Terminator, *DestBB)) {
-      requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
+      setFailed();
       return;
     }
     BasicBlock *From = CurrentFrame->BB;
@@ -271,7 +270,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       CurrentFrame->RetVal = getValue(RV);
     CurrentFrame->State = FrameState::Exit;
     if (!Handler.onInstructionExecuted(RI, None))
-      requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
+      setFailed();
   }
 
   void visitUncondBrInst(UncondBrInst &BI) { jumpTo(BI, BI.getSuccessor()); }
@@ -316,7 +315,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     }
 
     Handler.onUnrecognizedInstruction(CI);
-    requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
+    setFailed();
   }
 
   void visitIndirectBrInst(IndirectBrInst &IBI) {
@@ -384,7 +383,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     }
     default:
       Handler.onUnrecognizedInstruction(CB);
-      requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
+      setFailed();
       return AnyValue();
     }
   }
@@ -396,7 +395,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     if (CB.isNoBuiltin() ||
         !CurrentFrame->TLI.getLibFunc(*ResolvedCallee, LF)) {
       Handler.onUnrecognizedInstruction(CB);
-      requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
+      setFailed();
       return AnyValue();
     }
 
@@ -408,7 +407,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       return AnyValue();
 
     Handler.onUnrecognizedInstruction(CB);
-    requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
+    setFailed();
     return AnyValue();
   }
 
@@ -433,7 +432,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
 
       if (isa<InlineAsm>(CalledOperand)) {
         Handler.onUnrecognizedInstruction(CB);
-        requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
+        setFailed();
         return;
       }
 
@@ -887,14 +886,13 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     // TODO: track volatile stores
     // TODO: handle metadata
     store(Ptr, SI.getAlign(), Val, SI.getValueOperand()->getType());
-    if (!hasProgramExited())
-      if (!Handler.onInstructionExecuted(SI, AnyValue()))
-        requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
+    if (!hasProgramExited() && !Handler.onInstructionExecuted(SI, AnyValue()))
+      setFailed();
   }
 
   void visitInstruction(Instruction &I) {
     Handler.onUnrecognizedInstruction(I);
-    requestProgramExit(ProgramExitInfo::ProgramExitKind::Failed);
+    setFailed();
   }
 
   void visitExtractValueInst(ExtractValueInst &EVI) {
diff --git a/llvm/tools/llubi/lib/Library.cpp b/llvm/tools/llubi/lib/Library.cpp
index 6bbc52f623f9c..b0b5e3eb2c919 100644
--- a/llvm/tools/llubi/lib/Library.cpp
+++ b/llvm/tools/llubi/lib/Library.cpp
@@ -106,11 +106,8 @@ AnyValue Library::executeCalloc(StringRef Name, Type *Type,
 
   bool Overflow = false;
   const APInt AllocSize = Count.umul_ov(Size, Overflow);
-  if (Overflow) {
-    Executor.reportImmediateUB(
-        "calloc() with allocation size that overflows size_t.");
-    return AnyValue::poison();
-  }
+  if (Overflow)
+    return AnyValue::getNullValue(Ctx, Type);
 
   const IntrusiveRefCntPtr<MemoryObject> Obj =
       Ctx.allocate(AllocSize.getLimitedValue(), getMaxAlign(DL), Name, 0,
@@ -149,11 +146,11 @@ AnyValue Library::executeFree(ArrayRef<AnyValue> Args) {
   }
 
   if (!Obj->isHeapAllocated()) {
-    Executor.reportImmediateUB("freeing an stack allocation.");
+    Executor.reportImmediateUB("freeing a non-heap allocation.");
     return AnyValue::poison();
   }
 
-  // Currently we don't for cases where a memory allocated with C
+  // Currently we don't check for cases where a memory allocated with C
   // allocation family (malloc, calloc, etc.) is freed with a different free
   // function comes from a different family (C++ delete, etc.)
 
@@ -294,12 +291,6 @@ AnyValue Library::executePrintf(ArrayRef<AnyValue> Args) {
     }
   }
 
-  if (ArgIndex < Args.size()) {
-    Executor.reportImmediateUB(
-        "Too many arguments provided for the format string.");
-    return AnyValue::poison();
-  }
-
   OS.flush();
   Handler.onPrint(Output);
   return AnyValue(APInt(Executor.getIntSize(), Output.size()));



More information about the llvm-commits mailing list